Compare commits

...

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

* Change bautrate type

* Fix data baudrate once more

* alternative way of setting level

* fix windows serial

* fix u_int16_t

* reorder header files

* Fix header setup

* multiple command refresh

* increment allSettingsCount

* Fix android build

* update kalman filter parameters

* formatting fixes

* More formatting fixes

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

* Revert "Android 6 crash"

This reverts commit 6d4bfab1c9.

* Update MediaButtonReceiver.java

* Update MediaButtonReceiver.java

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

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

* Update qmqttsubscription_p.h

* Update qmqttclient_p.h

* Revert "Update qmqttclient_p.h"

This reverts commit 7629972927.

* Revert "Update qmqttsubscription_p.h"

This reverts commit 9c52f7363e.

* Update qdomyos-zwift.pri

* adding class for mqtt

* Update mqttpublisher.cpp

* Update mqttpublisher.cpp

* fixing build

* adding settings and also works on raspberry

* working also on ios!

* done!

* trying to fix windows build

* Update qmqttmessage.h

* fixing windows build

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

* Update project.pbxproj

* Update virtualbike_zwift.swift

* fixing formula

* fixing casting to double

* need to center the values in the table

* Update gears.qml

* Revert "Update gears.qml"

This reverts commit 0f149448b3.

* Update gears.qml

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

* qml finally saves the settings correctly

* completed?

* Update project.pbxproj

* fixing gear conversion

* adding max and minGears

* fixing UI and settings

* kickr core to wahookickr class

* ftms wheel circumference for gears

* implementing

* Update wahookickrsnapbike.cpp

* Update ftmsbike.cpp

* first custom gear test

* adding inclination custom message too

* Update ftmsbike.cpp

* implemented protobuf

* protobuf also for the gears

* Update ftmsbike.cpp

* Update project.pbxproj

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

* fixing mingears and maxgears

* adding android part

* Update main.cpp

* Update main.cpp

* Update main.cpp

* Update project.pbxproj

* fixing android build

* fixing build

* fixing android build

* Update main.cpp

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

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

* Update HeartChannelController.java

* Update build.gradle

* Update HeartChannelController.java

* Update build.gradle

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

* Update HeartChannelController.java

* Update HeartChannelController.java

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

* comment removed

* Update proformtreadmill.cpp

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

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

* Update README.md

updated readme in readable format

* Update README.md

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

* Update virtualtreadmill_zwift.swift

* trying on android

* should be ok on android so

* Revert "should be ok on android so"

This reverts commit 638c99ba83.

* adding inclination parsing

* Revert "Update virtualtreadmill_zwift.swift"

This reverts commit b3a388199e.

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

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

* Update homeform.cpp

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

* Update virtualbike_zwift.swift

* dynamic gears

* trying also on dircon but it doesn't work

* adding the android part

* gears works on android too!

* Update virtualbike.cpp

* setting added

* char 1224 removed

* Update project.pbxproj

* Update dirconmanager.cpp

* Update virtualbike.cpp

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

* Update project.pbxproj

* Update virtualbike_zwift.swift

* fixing formula

* fixing casting to double

* need to center the values in the table

* Update gears.qml

* Revert "Update gears.qml"

This reverts commit 0f149448b3.

* Update gears.qml

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

* qml finally saves the settings correctly

* completed?

* Update project.pbxproj

* fixing gear conversion

* adding max and minGears

* fixing UI and settings

* kickr core to wahookickr class
2024-10-31 05:21:43 +01:00
Roberto Viola
6759fb9ec0 I_ROWER fix #842 2024-10-30 14:21:51 +01:00
Roberto Viola
c7dad4f1ad Keiser M3i Heart Rate is Erratic (Issue #2711) 2024-10-30 09:38:00 +01:00
Roberto Viola
d29726632b handling resistance_lvl_mode on ftmsbike with zwift for don't affect wahoo climb 2024-10-29 14:15:26 +01:00
Roberto Viola
95e5c58a92 I_ROWER fix #842 2024-10-29 13:56:34 +01:00
Roberto Viola
8c5a3693c8 trying handling workout state for PM4 (#2398) 2024-10-28 20:21:51 +01:00
Roberto Viola
7aa1061b06 zonehr="0" ignored from xml training program file (Issue #2703) 2024-10-28 13:38:24 +01:00
Roberto Viola
46bd172d59 zonehr="0" ignored from xml training program file (Issue #2703) 2024-10-28 11:31:37 +01:00
Roberto Viola
d4dbaf5c57 Adidas Treadmill #1705 (#2705) 2024-10-28 09:45:49 +01:00
Roberto Viola
6b4d47c79d virtufit etappe 2.0i #2706 2024-10-28 08:59:02 +01:00
Roberto Viola
1bd32ade9f virtufit iConsole HTR 2.1 #2704 2024-10-27 20:22:16 +01:00
Roberto Viola
5e1f3abd14 virtufit iConsole HTR 2.1 #2704 2024-10-27 19:55:36 +01:00
Roberto Viola
5bf7ecab64 Update horizontreadmill.cpp 2024-10-27 06:54:42 +01:00
Roberto Viola
dd75df0af8 TRX4500 iOS connecting issue? 2024-10-27 06:47:10 +01:00
Roberto Viola
72de08f9a3 Schwinn 590U / 190U #2701 2024-10-27 06:42:17 +01:00
Roberto Viola
13213edb4f Elite Qubo Digital Smart B+ #1834 2024-10-27 06:38:22 +01:00
Roberto Viola
872b618ea1 Update stagesbike.cpp 2024-10-25 12:08:41 +02:00
Roberto Viola
78e7fe76c6 Elite Qubo Digital Smart B+ #1834 2024-10-25 11:30:57 +02:00
Roberto Viola
980245bbfc Wellfit treadmill support (issue #2659) 2024-10-24 19:15:40 +02:00
Roberto Viola
2fd98a0be0 adding some debug log on the peloton methods 2024-10-24 16:01:39 +02:00
Roberto Viola
700f5debe5 Update virtualbike.cpp 2024-10-24 15:47:41 +02:00
Roberto Viola
06b4604e59 HOI Cross+ from Kettler #2694 2024-10-24 15:24:22 +02:00
Roberto Viola
a6fd6b71d6 Inconsistent resistance with 'Get Gears from Zwift' setting in latest version of QZ. (Issue #2680) 2024-10-24 15:17:31 +02:00
Roberto Viola
da194caf7c Raspberry Binaries and Images (#2673) 2024-10-24 13:43:34 +02:00
Roberto Viola
fa45e1040f HOI Cross+ from Kettler #2694 2024-10-24 11:46:57 +02:00
Roberto Viola
09f0357763 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-10-24 11:38:00 +02:00
Roberto Viola
f82e106fc1 Wellfit treadmill support (issue #2659) 2024-10-24 10:58:35 +02:00
Roberto Viola
906431b3a6 Controls not working on mobvoi treadmill plus #2683 2024-10-24 10:29:13 +02:00
Roberto Viola
e82a76492a Inconsistent resistance with 'Get Gears from Zwift' setting in latest version of QZ. (Issue #2680) 2024-10-23 20:53:20 +02:00
Roberto Viola
8c75c01017 Workoutdoors treadmill compatibility (#2693) 2024-10-23 20:05:34 +02:00
Roberto Viola
dfabd2b414 Update project.pbxproj 2024-10-23 11:42:13 +02:00
Roberto Viola
8199dea809 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-23 11:25:48 +02:00
Roberto Viola
1cb20088b2 I_ROWER fix #842 2024-10-23 10:58:34 +02:00
Roberto Viola
203a9e5ca5 resistance against negative gradient #2677 2024-10-23 10:15:34 +02:00
Roberto Viola
5a70586756 resistance against negative gradient #2677 2024-10-23 10:02:20 +02:00
Roberto Viola
478beca96d JUSTO device added 2024-10-23 08:18:35 +02:00
Roberto Viola
637b57158a Wellfit treadmill support #2659 (#2686)
* n Wellfit treadmill support #2659

* Update horizontreadmill.cpp
2024-10-22 17:34:05 +02:00
Roberto Viola
a2009fa91f Inconsistent resistance with 'Get Gears from Zwift' setting in latest version of QZ. #2680 2024-10-22 16:40:35 +02:00
Roberto Viola
4ee5fa3b00 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-22 15:32:41 +02:00
Roberto Viola
36dde79dac Zwift Cog Virtual Gearing in ERG mode (Issue #2685) 2024-10-22 10:03:24 +02:00
Roberto Viola
9071d8a000 Update project.pbxproj 2024-10-21 17:35:18 +02:00
Roberto Viola
1c815e9d47 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-21 17:29:31 +02:00
Roberto Viola
9b16779293 Update project.pbxproj 2024-10-21 15:41:55 +02:00
Roberto Viola
c702477dc8 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-21 15:38:03 +02:00
Roberto Viola
b54df1d299 Proform 575i (Issue #2652) 2024-10-21 12:06:57 +02:00
Roberto Viola
548a254262 fixing crash on "TREADMILL" 2024-10-21 07:37:51 +02:00
Roberto Viola
d7330ad654 Elite Kura #2679 2024-10-20 15:56:01 +02:00
Roberto Viola
cfe02d3489 Cannot connect to ESLinker treadmill #2628 2024-10-18 15:00:28 +02:00
Roberto Viola
973bc4309d Cannot connect to ESLinker treadmill #2628 2024-10-18 14:53:31 +02:00
Roberto Viola
2112ed111f 2.18.1 2024-10-18 10:58:49 +02:00
Roberto Viola
95d714ea0c Update project.pbxproj 2024-10-18 09:48:07 +02:00
Roberto Viola
cad60e3343 SHAWN PAVLIN ASCEND S2 Erg Mode (Issue #2660) 2024-10-18 09:47:11 +02:00
Roberto Viola
c1f0640eda Update project.pbxproj 2024-10-18 09:39:31 +02:00
Roberto Viola
d511f0ea95 Zwift play emulator #2391 (#2613)
* trying on ios

* Update virtualbike_zwift.swift

* dynamic gears

* trying also on dircon but it doesn't work

* adding the android part

* gears works on android too!

* Update virtualbike.cpp

* adding zwift play service

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* fixing iOS UUID?

* porting to android too

* Update virtualbike.cpp

* Update virtualbike.cpp

* Update virtualbike.cpp

* Update virtualbike.cpp

* Update virtualbike.cpp

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* zwift play ask 1 passed!

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* seems to work apart the wattage to zwift

* Update virtualbike_zwift.swift

* accolumulated torque but it doesn't seem necessary

* the gearing is working!

* reverting torque not necessary

* increasing UI speed for the gear changing

* Update project.pbxproj

* handling slope

* fixing difficulty

* trying to use the wahoo service for gears also on dircon

* changing gears quickly

* merging modification on android/linux

* Update virtualbike.cpp

* fixing android

* fixing starting gears with the new zwift version

* adding setting for enabling it

* Update project.pbxproj

* Update virtualbike_zwift.swift

* Update virtualbike_zwift.swift

* fixing gears formatting

* Update project.pbxproj
2024-10-18 09:03:12 +02:00
Roberto Viola
96ad01f78c Update project.pbxproj 2024-10-17 17:21:35 +02:00
Roberto Viola
bdb5ed9ec1 Cannot connect to ESLinker treadmill #2628 2024-10-17 17:17:11 +02:00
Roberto Viola
49b054330c Update esliCannot connect to ESLinker treadmill (Issue #2628)nkertreadmill.cpp 2024-10-17 17:07:14 +02:00
Roberto Viola
40feaa010d Update project.pbxproj 2024-10-17 16:14:49 +02:00
Roberto Viola
fbae0a48dc Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-17 15:04:22 +02:00
Roberto Viola
84a0f93cc1 Update project.pbxproj 2024-10-17 14:16:07 +02:00
Roberto Viola
642a89548c Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-17 14:08:20 +02:00
Roberto Viola
6ac19bd6b5 life fitness ic7 spin bike distance and kcal error 2024-10-17 14:03:56 +02:00
Roberto Viola
5eaf54ccf1 Proform 575i #2652 2024-10-17 09:23:20 +02:00
Roberto Viola
78f64180c7 Proform 575i #2652 2024-10-16 08:29:15 +02:00
Roberto Viola
d921c426e4 Wellfit treadmill support #2659 2024-10-15 17:51:34 +02:00
Roberto Viola
efb09e7a81 adding description in the hr settings 2024-10-15 15:35:57 +02:00
David Mason
cb8939849b Manufacturer update of test project (#2654)
* #2653 refactored test project

* #2653 doc file update

* #2653 added documentation on class members
Changed some variable names.
Deleted member object in destructor
2024-10-15 09:30:48 +02:00
Roberto Viola
60e990a6c4 2.18.0 android 2024-10-14 16:43:27 +02:00
Roberto Viola
7c258dc4a4 Update project.pbxproj 2024-10-14 16:40:50 +02:00
Roberto Viola
bae7abb765 Proform 575i (Issue #2652) 2024-10-14 16:31:43 +02:00
Roberto Viola
9b5eee64d8 Use Volume buttons for manually changing speed #2657 2024-10-14 13:49:50 +02:00
Roberto Viola
edfdc0ae6c Use Volume buttons for manually changing speed (Issue #2657) 2024-10-14 13:35:58 +02:00
Roberto Viola
ff7bc5dbec Tacx Neo 2 freewheel keeps spinning #2650 2024-10-14 10:24:40 +02:00
Sunguk Lee
cd918f3664 Initial implement start/stop control of KingSmith R2 when press start/pause/stop buttons (#813) 2024-10-12 07:02:40 +02:00
Roberto Viola
88ba9563ad Xiaomi treadmill x21 new type #2649 2024-10-11 18:54:16 +02:00
Roberto Viola
b12a3d39a7 Xiaomi treadmill x21 new type #2649 2024-10-11 18:19:07 +02:00
Roberto Viola
0bc0885439 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-10-10 09:30:04 +02:00
Roberto Viola
9b38e93cf4 Kingsmith K12 #2642 2024-10-10 09:29:58 +02:00
Roberto Viola
e87687f175 Master T40 treadmill #2640 2024-10-09 16:40:43 +02:00
Roberto Viola
1e681de8a3 Update project.pbxproj 2024-10-09 16:07:36 +02:00
Roberto Viola
27bf0667fa Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-09 15:52:45 +02:00
Roberto Viola
732cfce4a0 fixing gears on proformwifibike 2024-10-09 14:14:32 +02:00
Roberto Viola
516f301822 Update project.pbxproj 2024-10-09 14:14:07 +02:00
Roberto Viola
21a1a7b765 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-09 09:39:04 +02:00
Roberto Viola
f1e57967d3 Jetblack Vitctory Compatibility added 2024-10-08 16:33:53 +02:00
Roberto Viola
c6bf70b3e1 added the ability to use resistance instead of inclination on ftmsbike 2024-10-08 15:42:18 +02:00
Roberto Viola
f2e9f5b28a Update project.pbxproj 2024-10-08 14:42:47 +02:00
Roberto Viola
02737c8b41 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-10-08 13:56:42 +02:00
Roberto Viola
2455298bb1 Update project.pbxproj 2024-10-08 13:56:30 +02:00
Roberto Viola
779afb5b17 DeerRun Treadmill integration #2621 2024-10-08 09:01:35 +02:00
Roberto Viola
969843dde4 Adding “Next Rows” metric on QZ AI app (Issue #2584) 2024-10-07 15:42:06 +02:00
Roberto Viola
f371a5337d Adding “Next Rows” metric on QZ AI app (Issue #2584) 2024-10-07 15:30:43 +02:00
Roberto Viola
ef9e97a588 Update project.pbxproj 2024-10-07 14:57:46 +02:00
Roberto Viola
41de930b49 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-10-07 14:51:54 +02:00
Roberto Viola
4e1adee102 Training programs UI improvement (Issue #1537) 2024-10-07 14:49:57 +02:00
Roberto Viola
c66f623173 Update project.pbxproj 2024-10-07 14:29:34 +02:00
Roberto Viola
625f62b057 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-07 14:22:30 +02:00
Roberto Viola
a996cb32b9 add support for Proform Sport 7.0 treadmill (Issue #2635) 2024-10-07 11:58:50 +02:00
Roberto Viola
d1fd8f6a70 Step count must be the double to match Apple Watch counts 2024-10-05 20:52:40 +02:00
Roberto Viola
4a33008c61 Renpho smart bike r-q002 n (Issue #2401) (#2409) 2024-10-05 07:45:19 +02:00
Roberto Viola
9d808b28a4 Update project.pbxproj 2024-10-04 12:08:57 +02:00
Roberto Viola
f69aee817c fixing timeout for proformwifibike 2024-10-04 11:13:35 +02:00
Roberto Viola
b07a75df90 Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-04 10:38:33 +02:00
Roberto Viola
1e2af212ca Cannot connect to ESLinker treadmill (Issue #2628) 2024-10-04 10:37:46 +02:00
Roberto Viola
c36afc3173 Update project.pbxproj 2024-10-03 18:33:30 +02:00
Roberto Viola
1d5d29bf1d Cannot connect to ESLinker treadmill #2628 2024-10-03 16:32:37 +02:00
Roberto Viola
deb5eab79e Speed Adjustment Issue on FS 465D50 Walking Pad (Issue #2630) (#2631)
* Speed Adjustment Issue on FS 465D50 Walking Pad (Issue #2630)

* Revert "Speed Adjustment Issue on FS 465D50 Walking Pad (Issue #2630)"

This reverts commit 4b0a36bb8a.

* Update fitshowtreadmill.cpp

* Update project.pbxproj

* Revert "Update fitshowtreadmill.cpp"

This reverts commit 10d859fa70.

* better fix
2024-10-03 13:13:51 +02:00
Roberto Viola
e767e964ab ]DeerRun Treadmill integration #2621
i need to add it to the bluetooth.cpp
2024-10-03 11:43:35 +02:00
Roberto Viola
eb540dc579 removing gears_zwift_ratio for device that are not supporting it #2608 2024-10-02 21:07:17 +02:00
Roberto Viola
01cd02ef94 Felvon v2 #2627 2024-10-02 16:00:25 +02:00
Roberto Viola
77b2ec46d1 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-10-01 14:36:33 +02:00
Roberto Viola
cf6b1953e0 Update project.pbxproj 2024-09-30 18:14:36 +02:00
Roberto Viola
533fba4c6e Revert "Can not auto adjust downhill inclination in Kinomap- Sole TT8 treadmill #2625"
This reverts commit 1b597c16dd.
2024-09-30 18:13:25 +02:00
Roberto Viola
60068dea5b Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-30 17:34:21 +02:00
Roberto Viola
1b597c16dd Can not auto adjust downhill inclination in Kinomap- Sole TT8 treadmill #2625 2024-09-30 16:13:13 +02:00
Roberto Viola
5d4b2a1fe1 Update project.pbxproj 2024-09-30 16:12:47 +02:00
Roberto Viola
c39f80bdeb Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-30 15:50:56 +02:00
Roberto Viola
a220efa9a4 Update zwiftclickremote.cpp 2024-09-30 14:32:45 +02:00
Roberto Viola
23c803add1 Update project.pbxproj 2024-09-30 14:31:51 +02:00
Roberto Viola
e38d8f24b6 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541
https://github.com/cagnulein/qdomyos-zwift/issues/2541#issuecomment-2383003316
2024-09-30 14:29:48 +02:00
Roberto Viola
5eae092c52 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-29 16:04:17 +02:00
Roberto Viola
fb374d966d Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-29 08:02:13 +02:00
Roberto Viola
04141fbb9f Can not auto adjust downhill inclination in Kinomap (Issue #2625) 2024-09-28 20:02:45 +02:00
Roberto Viola
36ab693ae6 Bluetooth volume controls on Android not working to change gears (Issue #2614) (#2616)
* let's try

* Update MediaButtonReceiver.java

* Update MediaButtonReceiver.java

* fixing crash

* Update MediaButtonReceiver.java

* Update AndroidManifest.xml

* Update homeform.cpp

* Update MediaButtonReceiver.java

* Update homeform.cpp

* Update homeform.h

* Update MediaButtonReceiver.java

* Update homeform.cpp

* Update homeform.cpp
2024-09-27 14:49:50 +02:00
Roberto Viola
ad19afcb8f 2.17.0 - build 880 2024-09-27 12:31:30 +02:00
Roberto Viola
f0828fb66a Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-27 12:29:08 +02:00
Roberto Viola
17f4bd4d63 2.17.0 2024-09-27 12:14:27 +02:00
Roberto Viola
968c724480 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-27 12:12:45 +02:00
Roberto Viola
533328dabc Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-27 12:12:07 +02:00
Roberto Viola
b11db80e1c fixing debouncing for the zwift ride 2024-09-26 10:14:11 +02:00
Roberto Viola
bacc84a25b new zwift play gear down event 2024-09-26 09:18:13 +02:00
Roberto Viola
d37939ee37 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-26 08:22:39 +02:00
Roberto Viola
716e99c943 fixing debouncing issue on zwift ride 2024-09-25 20:55:02 +02:00
Roberto Viola
93a4ef1771 2.16.70 2024-09-24 08:51:08 +02:00
Roberto Viola
8bf9feacf1 Bluetooth Name Android (#2611)
* Bluetooth Name Android

* Update homeform.h

* Update homeform.cpp

* New echelon stride treadmill #2555
2024-09-21 08:37:37 +02:00
Roberto Viola
4441090687 Update project.pbxproj 2024-09-20 15:43:05 +02:00
Roberto Viola
eff72fddc1 Update settings.qml 2024-09-20 15:38:24 +02:00
Roberto Viola
e54a9e5961 New echelon stride treadmill (Issue #2555) 2024-09-20 13:35:52 +02:00
Roberto Viola
eb4e320679 Proform Carbon TL Connects But Peloton Cannot Force Speed and Incline #2571 2024-09-20 11:30:53 +02:00
Roberto Viola
bbe0a4091c Update project.pbxproj 2024-09-19 16:17:29 +02:00
Roberto Viola
15f013071c Update bluetooth.cpp 2024-09-19 15:47:43 +02:00
Roberto Viola
7ea23b0ddc Update echelonstride.cpp 2024-09-19 15:11:01 +02:00
Roberto Viola
f132a00d30 gears on erg mode for ftmsbike 2024-09-19 14:07:57 +02:00
Roberto Viola
70c0bd9120 fixing distance on tacx flux s 2024-09-19 13:24:12 +02:00
Roberto Viola
45d0b78ec2 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-19 11:08:52 +02:00
Roberto Viola
a276861729 older power zone class does not grab homefitnessbuddy erg #2606 2024-09-19 11:08:46 +02:00
Roberto Viola
9846dc65a4 Revert "Update proformtreadmill.cpp (#2596)"
This reverts commit 5d66c6c513.
2024-09-18 20:03:38 +02:00
Roberto Viola
c1bcdc045c Update project.pbxproj 2024-09-17 19:53:23 +02:00
Roberto Viola
f18cd53c80 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-17 17:14:57 +02:00
Roberto Viola
e6b70c03a4 Vany Rysel D100 smart trainer #2603 2024-09-17 15:43:23 +02:00
Roberto Viola
30562f0ed4 removing ergtable log 2024-09-17 15:33:38 +02:00
Roberto Viola
5d66c6c513 Update proformtreadmill.cpp (#2596) 2024-09-17 13:51:38 +02:00
Roberto Viola
762c33440e Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-17 10:14:02 +02:00
Roberto Viola
c96ab6b86a Update project.pbxproj 2024-09-17 10:13:47 +02:00
Roberto Viola
b96cc70d51 Elite Aleno Turbo Trainer #2601 2024-09-17 09:39:59 +02:00
Roberto Viola
7f987c110a Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-17 08:40:40 +02:00
Roberto Viola
20473f1b31 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-16 17:38:49 +02:00
Roberto Viola
cd5ce73913 Update project.pbxproj 2024-09-16 16:32:03 +02:00
Roberto Viola
5b3e089b40 trying to implement zwift gear changes directly 2024-09-16 16:29:20 +02:00
Roberto Viola
a3252bd47d Update project.pbxproj 2024-09-16 14:57:14 +02:00
Roberto Viola
1bf4c33a7a adding gear offset 2024-09-16 14:46:39 +02:00
Roberto Viola
e1748022f2 Update bluetooth.cpp 2024-09-16 10:08:33 +02:00
Roberto Viola
ea93ab925c Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-16 09:47:31 +02:00
Roberto Viola
77a361905b iConsole + Torneo C-720BL won't connect #2442 2024-09-16 09:47:22 +02:00
Roberto Viola
bc9e33aead Update project.pbxproj 2024-09-14 20:49:00 +02:00
Roberto Viola
7305e4fab6 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-14 20:40:29 +02:00
Roberto Viola
3b3d893447 New echelon stride treadmill #2555 2024-09-13 15:05:54 +02:00
Roberto Viola
bb69c9a86a Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-13 12:41:41 +02:00
Roberto Viola
5b6012ebbc SS2K + Peloton Bike (Low Impact Rides) #2527 2024-09-13 12:41:38 +02:00
Roberto Viola
d27335410b Bluetooth Remote on Linux CLI (#2553)
* let's see if build

* fixing linker error?

* Update EventHandler.h

* fixing build

* Update EventHandler.h

* Update EventHandler.h

* Update EventHandler.h

* Update main.cpp

* Update EventHandler.h

* Update EventHandler.h

* Update EventHandler.h

* Update EventHandler.h

* Update EventHandler.h

* -bluetooth-event-gear-device /dev/input/event2

* Update main.cpp
2024-09-13 12:06:35 +02:00
Roberto Viola
8801f1d6cf Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-13 11:35:48 +02:00
Roberto Viola
114ee5317a Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-13 11:32:45 +02:00
Roberto Viola
a675451f5e Update main.yml 2024-09-11 10:36:33 +02:00
Roberto Viola
7cdf9782af trying to fix artifacts 2024-09-11 09:55:47 +02:00
Roberto Viola
1a2c5683ad Update main.yml (#2585) 2024-09-10 12:25:32 +02:00
Roberto Viola
99bb36b7f5 Update project.pbxproj 2024-09-10 08:58:56 +02:00
Roberto Viola
9b0971973f Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-10 08:58:03 +02:00
Roberto Viola
f8a1a33144 Update project.pbxproj 2024-09-10 08:57:44 +02:00
Roberto Viola
73ef8b4f03 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-10 08:56:48 +02:00
Roberto Viola
47fea6ee8e Revert "Xterra 4500 Incline Issue #2582"
This reverts commit b4a81243b8.
2024-09-09 16:14:22 +02:00
Roberto Viola
9ba5bdbb1b Update horizontreadmilltestdata.h 2024-09-09 15:58:06 +02:00
Roberto Viola
79a898d32b Wahoo Kickr not changing resistance when QZ acting as bridge for MYWHOOSH #2574 2024-09-09 11:51:10 +02:00
Roberto Viola
b4a81243b8 Xterra 4500 Incline Issue #2582 2024-09-09 11:02:16 +02:00
Roberto Viola
573394366b Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-09 10:54:18 +02:00
Roberto Viola
bce2d2605c WalkingPad MC21 treadmill #2580 2024-09-07 07:23:13 +02:00
Roberto Viola
3b943784c0 fixing crash on echelon in case of missing main service 2024-09-06 22:27:51 +02:00
Roberto Viola
52d91de7e4 Can't connect QZ to Zwift (Issue #2579) 2024-09-06 21:49:25 +02:00
Roberto Viola
ca830a988b Zwift Click connection issue #2576 2024-09-06 16:57:34 +02:00
Roberto Viola
2955ac9751 Power profile Domyos bike 500 (Issue #2573) 2024-09-06 12:02:50 +02:00
Roberto Viola
930cc4e016 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-09-06 09:35:45 +02:00
Roberto Viola
9bac3f8e7c Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-09-06 09:00:43 +02:00
Roberto Viola
02fbff3f84 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-09-06 09:00:39 +02:00
Roberto Viola
e8fb563b77 Domyos TC 540 Dirk Püschmann (Issue #2568) fe801547d 2024-09-06 08:58:59 +02:00
dadude2607
b9d5e6141f Skandika Morpheus: calc speed based on watts fixed (#2572)
The speed could not be calculated based on watt, because the code was in an unsolvable if query. I have changed the if query to "cadence", like on the Schwinn bike. Now the speed is calculated correctly as soon as you pedal. Before this fix the option "calculate speed based on watt" had be turned off
2024-09-06 07:15:58 +02:00
Roberto Viola
83185e0e95 Update project.pbxproj 2024-09-05 17:58:35 +02:00
Roberto Viola
fe801547dc Domyos TC 540 Dirk Püschmann (Issue #2568) 2024-09-05 17:53:33 +02:00
Roberto Viola
1ade078827 The zwift play controllers don't connect and the app crashes (Issue #2531) 2024-09-05 17:39:28 +02:00
dadude2607
36cf326fa4 Added HR for Skandika Morpheus issue #2566 (#2567)
* Added HR for Skandika Morpheus issue #2566

Because the morpheus behaves like a mixture of the x-2000 and the wiry, I have introduced a new variable. Speed, watts and rpm are read out like the wiry, but the heart rate is read out like the x-2000. With this code the morpheus works for me. I have attached a screenshot in the issue #2566. to distinguish an x-2000 from a Morpheus it seems to be enough to pay attention to the number of characters of the bluetooth name

* Update skandikawiribike.cpp

---------

Co-authored-by: Roberto Viola <Cagnulein@gmail.com>
2024-09-05 05:19:00 +02:00
Roberto Viola
0812f419c6 Skandika Morpheus Support (Issue #2566) 2024-09-04 08:14:07 +02:00
Roberto Viola
ee3f6a1d1f build 858 2024-09-03 11:30:26 +02:00
Roberto Viola
5e9e82e3f5 trying to fix layout on settings 2024-09-03 11:23:02 +02:00
Roberto Viola
f1636f7915 adding popup to power, cadence and hr sensor 2024-09-01 14:22:53 +02:00
Roberto Viola
c8555e543a Update homeform.cpp 2024-09-01 08:55:38 +02:00
Roberto Viola
496ea9f2be stopping homeform session when the trainprogram ends and the ends setting is enabled 2024-09-01 08:37:55 +02:00
Roberto Viola
892d949e72 NPE Runn sensor fantasy values #2558 2024-09-01 07:04:29 +02:00
Roberto Viola
5981e7cd00 BH i-Nexor Bike support? #2441 2024-09-01 07:02:22 +02:00
Roberto Viola
a6cc8b5e87 added inclination to the 3d maps for bikes 2024-08-30 09:34:27 +02:00
Roberto Viola
f07e7f9c1f ASCEND S2 Spin bike #2556 2024-08-30 08:23:55 +02:00
Roberto Viola
7e3eab5b8c New echelon stride treadmill #2555 2024-08-29 21:24:21 +02:00
Roberto Viola
a720717d5c Qz sets inclination to -15 on sole f85 treadmill, whenever zwift sends negative inclination #2552 2024-08-29 15:19:26 +02:00
Roberto Viola
ff16880fae Kettler Ergometer #2551 2024-08-29 14:43:46 +02:00
Roberto Viola
aba3ff502c Zu250s from ziYou bike #2548 2024-08-28 16:30:59 +02:00
Roberto Viola
4fe689ac55 font in the settings fixed 2024-08-28 16:03:03 +02:00
Roberto Viola
6b0777233d removed log of username settings for privacy reasons 2024-08-28 13:45:37 +02:00
Roberto Viola
c8c32a0860 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-08-28 11:14:21 +02:00
Roberto Viola
f4b6663c5d bluetooth connection issue of keep bike #2543 2024-08-28 11:14:00 +02:00
Roberto Viola
328f0992b6 Kettler Spin Bike Racer S #2542 2024-08-28 08:52:57 +02:00
Roberto Viola
f942282b7e bluetooth connection issue of keep bike #2543 2024-08-28 07:54:33 +02:00
Roberto Viola
c90849063e Revert " Treadmill stops automatically in Zwift #2539"
This reverts commit 4bb4aba109.
2024-08-27 15:46:18 +02:00
Roberto Viola
4f057bb88f increasing setting info text 2024-08-27 15:46:07 +02:00
Roberto Viola
4cae862455 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-08-27 10:02:20 +02:00
Roberto Viola
4b6da2bb95 big gears buttons added 2024-08-27 10:02:14 +02:00
Roberto Viola
7f0c589c50 Update project.pbxproj 2024-08-27 09:34:42 +02:00
Roberto Viola
df928caf99 Skipping gears in Zwift on IOS #2540 2024-08-27 09:12:36 +02:00
Roberto Viola
8142e75323 Skipping gears in Zwift on IOS #2540 2024-08-27 09:03:13 +02:00
Roberto Viola
95f51682c9 Several Issues Using QZ with Rouvy and Zwift Play Controllers #2541 2024-08-27 08:43:53 +02:00
Roberto Viola
4b74c22f95 Hammer Finnlo BF 90 (Issue #2538) 2024-08-27 08:29:50 +02:00
Roberto Viola
4bb4aba109 Treadmill stops automatically in Zwift #2539 2024-08-27 08:23:38 +02:00
Roberto Viola
10f39dac68 version 2.16.69 2024-08-26 08:09:56 +02:00
Roberto Viola
352aa40d0b Add Support for Echelon Stride 6S #2534 2024-08-25 16:16:58 +02:00
Roberto Viola
b6c2704e4f QZ calculates calories too low inaccurately. (Issue #2537) 2024-08-25 09:55:16 +02:00
Roberto Viola
fc7b043c8f Update project.pbxproj 2024-08-22 07:05:15 +02:00
Roberto Viola
f365fb3423 Domyos eb 900 still connects but stopped showing pedalling data (Issue #2533) 2024-08-22 07:01:28 +02:00
Roberto Viola
f6ffb08ed6 Add Support for Echelon Stride 6S #2534 2024-08-22 06:54:25 +02:00
Roberto Viola
78101b6191 Technogym Group Cycle Connect via ant+ #2528 2024-08-21 15:25:35 +02:00
Roberto Viola
d4f74c3287 Technogym Group Cycle Connect via ant+ #2528 2024-08-21 14:58:04 +02:00
Roberto Viola
e09afb91db The zwift play controllers don't connect and the app crashes (Issue #2531) 2024-08-21 14:35:42 +02:00
Roberto Viola
f87c08d580 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-08-21 12:32:39 +02:00
Roberto Viola
2029579c87 Technogym Group Cycle Connect via ant+ #2528 2024-08-21 12:32:21 +02:00
Roberto Viola
d8a9e88736 Update Garmin.java 2024-08-21 11:43:12 +02:00
Roberto Viola
ee1ed94692 Update project.pbxproj 2024-08-21 11:19:22 +02:00
Roberto Viola
73798b87db Technogym Group Cycle Connect via ant+ #2528 2024-08-21 11:17:25 +02:00
Roberto Viola
c98bf5ca35 Treadmill incline multiplied on QZ output [BUG] (Issue #2511) 2024-08-18 13:49:08 +02:00
Roberto Viola
f7a2d30554 2.16.68 2024-08-17 13:30:04 +02:00
Roberto Viola
b826e93644 New Zwift Play Model #2520 2024-08-17 10:20:03 +02:00
Roberto Viola
e9a446a2e7 Virtual shifting not working (Issue #2519) 2024-08-16 21:52:17 +02:00
Roberto Viola
7aab56ea93 wizard shouldn't show if it's a wifi device 2024-08-16 21:46:42 +02:00
Roberto Viola
3abf955713 Pro-form Carbon E7 not connecting #2515 2024-08-16 07:09:33 +02:00
Roberto Viola
430f41e5b9 HARISON-X15 #2517 2024-08-16 06:29:51 +02:00
Roberto Viola
fdbb70fd73 Treadmill incline multiplied on QZ output [BUG] #2511 2024-08-16 06:24:48 +02:00
Roberto Viola
bddd8cfaae Update Wizard.qml 2024-08-15 11:00:13 +02:00
Roberto Viola
b81656c369 Kingsmith WalkingPad Z1 not connecting to QZ (Issue #2506) 2024-08-13 20:46:59 +02:00
Roberto Viola
4fe2cf6ea6 Name device change (Issue #2508) 2024-08-12 11:54:14 +02:00
Roberto Viola
c23b936eca Update project.pbxproj 2024-08-12 09:19:36 +02:00
Roberto Viola
445e8691f6 Kingsmith WalkingPad Z1 not connecting to QZ (Issue #2506) 2024-08-12 09:15:56 +02:00
Roberto Viola
a100d4cc96 Update project.pbxproj 2024-08-10 20:28:46 +02:00
Roberto Viola
6384268aff Kingsmith WalkingPad Z1 not connecting to QZ (Issue #2506) 2024-08-10 20:20:49 +02:00
Roberto Viola
b5c68f5e6c Update project.pbxproj 2024-08-09 16:13:38 +02:00
Roberto Viola
6092bbd3c3 Virtual iFit - Auto-resistance/incline not working #2467 2024-08-09 15:24:17 +02:00
Roberto Viola
3e6c170289 Revert "Virtual iFit - Auto-resistance/incline not working #2467"
This reverts commit 3327b8b1e6.
2024-08-09 15:21:00 +02:00
Roberto Viola
24fe3c625d Shifter stopped working after Qdomyos #561
https://github.com/doudar/SmartSpin2k/issues/561
2024-08-09 15:10:41 +02:00
Roberto Viola
8dd03df9a2 Nordictrack EXP 5i #2502 2024-08-07 14:23:34 +02:00
Roberto Viola
f81929fe60 fixing crash on trainprogram overflow 2024-08-07 14:07:10 +02:00
Roberto Viola
dd7a5cb82b Unable to connect my Zwift Play to QZ (Issue #2458) 2024-08-06 23:59:36 +02:00
Roberto Viola
d233f04f67 Unable to connect my Zwift Play to QZ (Issue #2458) 2024-08-06 23:18:56 +02:00
Roberto Viola
09e51d6013 Unable to connect my Zwift Play to QZ (Issue #2458) 2024-08-06 22:26:31 +02:00
Roberto Viola
06f72ba937 Unable to connect my Zwift Play to QZ (Issue #2458) 2024-08-06 22:00:36 +02:00
Roberto Viola
13f9502592 Unable to connect my Zwift Play to QZ #2458 2024-08-06 20:29:16 +02:00
Roberto Viola
71afb2881f Update project.pbxproj 2024-08-06 07:18:28 +02:00
Roberto Viola
2094222a54 fix typo in the settings 2024-08-05 21:06:46 +02:00
Roberto Viola
369653bd24 trying to fix again fgchecker (#2503)
* trying to fix again fgchecker

* adding classes

* Update main.yml

* Update main.yml

* Update main.yml
2024-08-05 19:07:38 +02:00
Roberto Viola
3327b8b1e6 Virtual iFit - Auto-resistance/incline not working #2467 2024-08-05 11:16:09 +02:00
Roberto Viola
fc59813aef Fixing fgchecker (#2500)
* fixing fgchecker

* Update build.gradle

* Update build.gradle
2024-08-05 11:11:29 +02:00
Roberto Viola
01cf3a1f95 No metrics showing on Apex Rides bike (Issue #2459) 2024-08-05 09:41:55 +02:00
Roberto Viola
faa62aae2b 3G Cardio Elite Recumbent Bike X - Resistance not working #2488 2024-08-05 08:58:30 +02:00
Roberto Viola
0e22f92002 YOSUDA rower #2498 2024-08-05 08:53:27 +02:00
Roberto Viola
9fdf9326c5 Yesoul G1M Plus #2497 2024-08-04 20:21:15 +02:00
Roberto Viola
ff538529ec Can’t link to my machine /watch #2495 2024-08-04 18:11:44 +02:00
Roberto Viola
cc517fc7ee No metrics showing on Apex Rides bike #2459 2024-08-02 14:19:49 +02:00
Roberto Viola
0fc300c451 Virtual iFit - Auto-resistance/incline not working #2467 2024-08-02 08:52:47 +02:00
Roberto Viola
233862ec99 Virtual iFit - Auto-resistance/incline not working #2467 2024-08-01 22:17:10 +02:00
Roberto Viola
a44158730a Virtual iFit - Auto-resistance/incline not working #2467 2024-08-01 21:13:46 +02:00
Roberto Viola
e97c2e4a89 Update project.pbxproj 2024-08-01 14:46:58 +02:00
Roberto Viola
d3ba36ac53 Virtual iFit - Auto-resistance/incline not working (Issue #2467) 2024-08-01 09:03:56 +02:00
Roberto Viola
a4b54a5669 Virtual iFit - Auto-resistance/incline not working (Issue #2467) 2024-07-31 17:36:03 +02:00
Roberto Viola
8365d4dae6 Virtual iFit - Auto-resistance/incline not working (Issue #2467) 2024-07-31 13:47:50 +02:00
Roberto Viola
76dcac37d6 Android location services request (#1942)
* Update main.cpp

* adding java class

* Update LocationHelper.java

* Update LocationHelper.java

* Update LocationHelper.java

* Update LocationHelper.java

* Update LocationHelper.java

* Update LocationHelper.java

* Update LocationHelper.java

* Revert "Update LocationHelper.java"

This reverts commit 17878ea855.

* showing question

* Revert "showing question"

This reverts commit c7b24c7c07.

* Reapply "Update LocationHelper.java"

This reverts commit e90ce3c457.

* adding methods

* message boxex

* fixed everything

* Update Home.qml
2024-07-31 13:05:28 +02:00
Roberto Viola
c44bc1087d Startup wizard v2 (#2473)
* adding wizard

* Update Wizard.qml

* fixing close

* first run doesn't work yet

* fixing layout

* Update Wizard.qml

* adding spacer to the back button

* adding 2 page for peloton credentials and difficulty

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml

* Revert "Update Wizard.qml"

This reverts commit e875aa93e6.

* Update Wizard.qml

* Update Wizard.qml

* Update Wizard.qml
2024-07-31 09:59:03 +02:00
Roberto Viola
38cde07626 Virtual iFit - Auto-resistance/incline not working #2467 2024-07-31 09:27:25 +02:00
Roberto Viola
8a849c093c fixing onServiceAdded on android for all the virtual devices 2024-07-30 09:35:02 +02:00
Roberto Viola
ef7cae7b38 Android, finally, can send both hr and cadence via bluetooth 2024-07-29 22:11:58 +02:00
Roberto Viola
b7bcfddcee Virtual iFit - Auto-resistance/incline not working #2467 2024-07-29 22:02:24 +02:00
Roberto Viola
4e702a62d4 Pro Form CarbonT7 Not Connecting #2474 2024-07-29 21:27:37 +02:00
Roberto Viola
0c990135eb Virtual iFit - Auto-resistance/incline not working #2467 2024-07-29 18:58:17 +02:00
Roberto Viola
25cb605d6d Virtual iFit - Auto-resistance/incline not working #2467 2024-07-29 16:46:28 +02:00
Roberto Viola
4717a79b5a Update project.pbxproj 2024-07-29 14:26:21 +02:00
Roberto Viola
8d39ace35b Tunturi FitCycle i70 - Incorrect RPM data after 99. #2490 2024-07-29 14:23:05 +02:00
Roberto Viola
236c3bc7d0 DFC Power Sensor #2491 2024-07-29 14:07:22 +02:00
Roberto Viola
e93caebe2a Pro Form CarbonT7 Not Connecting #2474 2024-07-29 09:13:18 +02:00
Roberto Viola
291d09ce41 3G Cardio Elite Recumbent Bike X - Resistance not working #2488 2024-07-29 08:55:19 +02:00
Roberto Viola
98128f3fa9 reverting ios files 2024-07-27 16:25:36 +02:00
Roberto Viola
148bcb3548 Reapply "Autopause Thread Conflict #2487"
This reverts commit f3bcbd3312.
2024-07-27 16:25:05 +02:00
Roberto Viola
f3bcbd3312 Revert "Autopause Thread Conflict #2487"
This reverts commit 736dfefc31.
2024-07-27 16:21:49 +02:00
Roberto Viola
736dfefc31 Autopause Thread Conflict #2487 2024-07-27 16:14:00 +02:00
Roberto Viola
91217a51c9 Jasport C3 #2484 2024-07-26 18:31:01 +02:00
Roberto Viola
de8fcada5b Update project.pbxproj 2024-07-26 18:01:42 +02:00
Roberto Viola
afffaa6a85 Yesoul FTMS Bike #2444 2024-07-26 18:00:38 +02:00
Roberto Viola
b54d4cea42 kickr core with ftms was still using the stagesbike profile because it doesn't advertise the ftms service 2024-07-26 14:03:09 +02:00
Roberto Viola
fc62fcf461 No metrics showing on Apex Rides bike #2459 2024-07-25 21:44:11 +02:00
Roberto Viola
57ef6071b7 Virtual iFit - Auto-resistance/incline not working #2467 2024-07-25 21:14:25 +02:00
Roberto Viola
ad86c1abff Fassi 9.4 hrc #2480 2024-07-25 20:50:37 +02:00
Roberto Viola
9963508b79 Fassi 9.4 hrc #2480 2024-07-25 18:23:05 +02:00
Roberto Viola
5f958b4618 Update qzsettings.cpp 2024-07-25 16:43:10 +02:00
Roberto Viola
e4930ecbcb fixing next row for treadmill 2024-07-25 16:38:06 +02:00
Roberto Viola
9d8be8ae4f fixing pace color issue on treadmill when you are using a zwo file 2024-07-25 16:34:51 +02:00
Roberto Viola
6ddbfe4a86 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-07-25 16:22:27 +02:00
Roberto Viola
ebf49d20db Virtual iFit - Auto-resistance/incline not working #2467 2024-07-25 16:22:21 +02:00
Roberto Viola
5196db1a84 Update project.pbxproj 2024-07-24 21:58:54 +02:00
Roberto Viola
491f05dc14 Domyos Bike FTMS #2479 2024-07-24 21:54:39 +02:00
Roberto Viola
51dabda33e Fassi 9.4 hrc #2480 2024-07-24 21:49:29 +02:00
Roberto Viola
1286c2105d Home fitness buddy incompatibility? (Issue #2472) 2024-07-24 21:41:31 +02:00
Roberto Viola
6accc034ab Domyos Bike FTMS #2479 2024-07-24 19:37:30 +02:00
Roberto Viola
6de18ee563 Not collecting data from Bowflex IC SE bike #2477 2024-07-24 17:46:26 +02:00
Roberto Viola
a2dcda53df FP-TECH FITNESS Treadmill #2478 2024-07-24 17:44:09 +02:00
Roberto Viola
84d60f0301 Virtual shifting lags (Issue #2470) 2024-07-24 07:25:56 +02:00
Roberto Viola
49c91df0b7 Life Fitness T5 no stats showing (Issue #2476) 2024-07-24 07:13:22 +02:00
Roberto Viola
e82d2de889 Update project.pbxproj 2024-07-23 13:52:51 +02:00
Roberto Viola
c558aadc8f Peloton Treadmill Pace Levels #2469 2024-07-23 11:59:10 +02:00
Roberto Viola
21c5b62d71 Virtual shifting lags (Issue #2470) 2024-07-23 10:12:47 +02:00
Roberto Viola
8ae32e7daf Virtual iFit - Auto-resistance/incline not working #2467 2024-07-22 19:14:37 +02:00
Roberto Viola
f195ef1c30 Unable to connect my Zwift Play to QZ (Issue #2458) 2024-07-22 18:39:33 +02:00
Roberto Viola
1bd865f142 fixing footer always visible 2024-07-22 18:31:02 +02:00
Roberto Viola
de5c37189b ability to resize the footer 2024-07-18 20:23:06 +02:00
Roberto Viola
d4595c7bdb Unable to connect my Zwift Play to QZ #2458 2024-07-18 16:42:33 +02:00
Roberto Viola
608e240046 iFit looses connection to qdomyos #2119 2024-07-18 10:40:38 +02:00
Roberto Viola
2b76b27006 No metrics treadmill doesnt react to app BUG] #2453 (#2454)
* Update fitshowtreadmill.cpp

* adding setting
2024-07-15 22:26:41 +02:00
Roberto Viola
288709ca27 2.1.6.66 2024-07-15 22:02:22 +02:00
Roberto Viola
28a629fa62 fixing crash on garmin null recv on android 2024-07-15 21:58:19 +02:00
Roberto Viola
69f440ecee trying to definitely fixing the registerReceiver issue
(i got a confirmation for the ANT+ one, so let's adopt this for all the rest)
2024-07-15 21:08:45 +02:00
Roberto Viola
fb9e0c285e Update ChannelService.java 2024-07-15 17:45:52 +02:00
Roberto Viola
954948de9e Update ChannelService.java 2024-07-15 16:37:27 +02:00
Roberto Viola
b0b702733f Update ChannelService.java 2024-07-15 16:31:42 +02:00
Roberto Viola
3a88433d1c trying to fix registerReceiver 2024-07-15 16:30:37 +02:00
Roberto Viola
a1095b4219 Zwift api Windows MSVC (#2450)
* first test on CI

* Update main.yml

* Update qdomyos-zwift.pri

* mingw-w64-x86_64-protobuf

* Update qdomyos-zwift.pri

* Update main.yml

* adding protobuf to src folder

* Update main.yml

* Update main.yml

* adding absl

* absl from mingw

* building protobuf

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* adding mingw64 binary

* adding mingw libabsl

* Update main.yml

* builds on mingw

* required lib for libabsl

* trying to fix mingw build

* Update main.yml

* trying to msvc first

* Update qdomyos-zwift.pri

* trying to put the protobuf lib on the same src dir

* again msvc first

* JLL T550 #2161

* Update main.yml

* Update main.yml

* Update qdomyos-zwift.pri

* Revert "Update qdomyos-zwift.pri"

This reverts commit 7e12ca0476.

* adding absl on msvc

* building absl inside protobuf for debug

* Update qdomyos-zwift.pri

* adding vcpkg

* adding submodule

* vcpkg

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* using vcpkg

* Update main.yml

* Update qdomyos-zwift.pri

* Update qdomyos-zwift.pri

* include removing, using vcpkg

* Update main.yml

* Update main.yml

* protobuf

* Update trainprogram.cpp

* Update main.yml

* starting to port the same on linux

* Update trainprogram.cpp

* Update main.yml

* Update main.yml

* Update trainprogram.cpp

* Update main.yml

* Update qdomyos-zwift.pri

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update qdomyos-zwift.pri

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* fixing ai build

* Update main.yml

* Update main.yml

* fixing linux build

* Update main.yml

* Update main.yml

* fixing iOS build

* Update main.yml

* Update bluetooth.cpp

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update qdomyos-zwift.pri

* Update qdomyos-zwift.pri

* Update main.yml

* check_op

* Update main.yml

* removing zlib1.dll ?

* fixing noblepro issue

* removing vcpkg, let's build protobuf from scratch

* Update main.yml

* Update main.yml

* Update main.yml

* Revert "Update main.yml"

This reverts commit 9f40ddbb9d.

* Revert "Update main.yml"

This reverts commit 074b4509f6.

* Revert "Update main.yml"

This reverts commit 81798642af.

* Revert "removing vcpkg, let's build protobuf from scratch"

This reverts commit 1e3cde341d.

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Removing FTMS only limitation for windows?

* Update trainprogram.cpp

* forcing specific version of protoc on vcpkg

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* reverting protobuf, let's compile using the 4 next

* built with the newer protoc

* using 4.25.1 also for protoc

* ready for merge?

* fixing build

* Update trainprogram.cpp

* Update qdomyos-zwift.pri
2024-07-15 12:30:24 +02:00
Roberto Viola
7a7619438c adding import on ChannelService 2024-07-15 11:23:15 +02:00
Roberto Viola
12dff6404c trying to fix registerreceiver on android < 14 2024-07-15 10:59:24 +02:00
Roberto Viola
a8e3a672d4 Update main.cpp 2024-07-15 09:57:42 +02:00
Roberto Viola
3ebd94a278 Does not connect to Proform TDF with Pi 0 W (Issue #2387) 2024-07-15 08:50:11 +02:00
Roberto Viola
4a7f22f699 2.16.64 2024-07-14 09:22:36 +02:00
Roberto Viola
e0ac6c2ec4 adding implementation "androidx.core:core:1.12.0" just to be sure 2024-07-13 22:59:28 +02:00
Roberto Viola
7723be4356 BH Osaka bike #2425 2024-07-13 22:27:21 +02:00
Roberto Viola
c53bf1a2ab BH i-Nexor Bike support? (Issue #2441) 2024-07-13 21:37:41 +02:00
Roberto Viola
6f8d1fefac updating garmin sdk 2024-07-13 00:11:29 +02:00
Roberto Viola
9edea7d50d reverting error 2024-07-12 21:07:41 +02:00
Roberto Viola
17e6afd09d Android crash on notification
Exception java.lang.RuntimeException:
  at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5286)
  at android.app.ActivityThread.-$$Nest$mhandleServiceArgs
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2531)
  at android.os.Handler.dispatchMessage (Handler.java:106)
  at android.os.Looper.loopOnce (Looper.java:230)
  at android.os.Looper.loop (Looper.java:319)
  at android.app.ActivityThread.main (ActivityThread.java:8919)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:578)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)
Caused by android.app.MissingForegroundServiceTypeException:
  at android.app.MissingForegroundServiceTypeException$1.createFromParcel (MissingForegroundServiceTypeException.java:53)
  at android.app.MissingForegroundServiceTypeException$1.createFromParcel (MissingForegroundServiceTypeException.java:49)
  at android.os.Parcel.readParcelableInternal (Parcel.java:4882)
  at android.os.Parcel.readParcelable (Parcel.java:4864)
  at android.os.Parcel.createExceptionOrNull (Parcel.java:3064)
  at android.os.Parcel.createException (Parcel.java:3053)
  at android.os.Parcel.readException (Parcel.java:3036)
  at android.os.Parcel.readException (Parcel.java:2978)
  at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:7234)
  at android.app.Service.startForeground (Service.java:775)
  at org.cagnulen.qdomyoszwift.ForegroundService.onStartCommand (ForegroundService.java:32)
  at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5268)
2024-07-12 21:04:47 +02:00
Roberto Viola
27d58a6f26 Update project.pbxproj 2024-07-12 19:56:28 +02:00
Roberto Viola
d4028290ed Live Chart for Power and Heart Rate (Issue #2159) 2024-07-12 19:55:42 +02:00
Roberto Viola
5cf21ee3ce NordicTrack S15i bike with MyWoosh (Issue #2436) 2024-07-12 19:55:26 +02:00
Roberto Viola
671be1d3ab Update project.pbxproj 2024-07-11 20:49:24 +02:00
Roberto Viola
846f921c8f NordicTrack S15i bike with MyWoosh (Issue #2436) 2024-07-11 20:41:39 +02:00
Roberto Viola
ec67d56de3 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-07-11 20:39:13 +02:00
Roberto Viola
b13d3b907b Update project.pbxproj 2024-07-11 18:17:29 +02:00
Roberto Viola
79054d4ef2 Connects to BH Fitness iConcept Boxster Dual but nothing works (Issue #2443) 2024-07-11 18:14:42 +02:00
Roberto Viola
233f9e27b2 BH i-Nexor Bike support? #2441 2024-07-11 18:03:30 +02:00
Roberto Viola
5709b9570f Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-07-11 17:00:49 +02:00
Roberto Viola
ef0152da09 BH i-Nexor Bike support? (Issue #2441) 2024-07-11 17:00:36 +02:00
Roberto Viola
d5c7fc894e Live Chart for Power and Heart Rate #2159 (#2440)
* Live Chart for Power and Heart Rate #2159

* Update project.pbxproj

* Update qml.qrc

* Update dochartliveheart.js

* Update project.pbxproj

* Update dochartliveheart.js

* fixing layout

* Update project.pbxproj

* Update chartlive.htm

* BH i-Nexor Bike support? #2441

* adjusting padding

* fixing

* Update project.pbxproj
2024-07-11 16:56:37 +02:00
Roberto Viola
7a1a4f7a61 BH i-Nexor Bike support? #2441 2024-07-11 16:17:48 +02:00
Roberto Viola
bebed7e6ca Update bluetooth.cpp 2024-07-11 16:07:37 +02:00
Roberto Viola
487a6da9d4 JetBlack Volt V1 #2445 2024-07-11 15:59:30 +02:00
Roberto Viola
9cee8ea043 FlexNest bike added #2444 2024-07-11 15:38:22 +02:00
Roberto Viola
5f83862ad7 iConsole + Torneo C-720BL won't connect #2442 2024-07-11 15:07:00 +02:00
Roberto Viola
42545caa21 Connects to BH Fitness iConcept Boxster Dual but nothing works #2443 2024-07-11 14:53:23 +02:00
Roberto Viola
1a6ca728d5 Toorx srx 500 connects but no metrics load #2428 2024-07-11 09:50:30 +02:00
Roberto Viola
71fc9218c2 BH i-Nexor Bike support? #2441 2024-07-11 09:30:55 +02:00
Roberto Viola
5116a1260a Auto adjust running power from Stryd when using a treadmill incline #2437 2024-07-10 09:09:39 +02:00
Roberto Viola
4a70fdf554 Wahoo Headwind, HRM (Issue #2343) 2024-07-10 08:51:37 +02:00
Roberto Viola
807e144c5e BH Osaka bike #2425 2024-07-09 19:29:55 +02:00
Roberto Viola
08fb218f8c Cross Rope #2381 2024-07-09 17:11:01 +02:00
Roberto Viola
7787e6468a Change speed treadmill #1988 2024-07-09 12:12:38 +02:00
Roberto Viola
2a059cf493 FOREGROUND_SERVICE_CONNECTED_DEVICE added for target api 34 on android 2024-07-08 20:10:19 +02:00
Roberto Viola
d84e52073a fixing android 14 targetting 2024-07-08 19:10:08 +02:00
Roberto Viola
3c34ec7d45 build 812 for android 2024-07-08 17:10:55 +02:00
Roberto Viola
2c54b30974 BH Osaka bike #2425 2024-07-08 17:08:24 +02:00
Roberto Viola
6631f7d1ba com.android.billingclient:billing:6.0.1 2024-07-08 16:52:17 +02:00
Roberto Viola
01a7bb8e49 2.16.61 2024-07-08 16:10:20 +02:00
Roberto Viola
d226c65cc5 api target to 34 2024-07-08 16:00:57 +02:00
Roberto Viola
4eeac31d06 Support for Proform 705 CST treadmill (firmware V80) (Issue #2430) 2024-07-08 14:44:51 +02:00
Roberto Viola
f8c3415db3 Update project.pbxproj 2024-07-08 14:27:03 +02:00
Roberto Viola
bded1f8697 iFit looses connection to qdomyos (Issue #2119) 2024-07-08 13:46:03 +02:00
Roberto Viola
4ff72dccbf Update project.pbxproj 2024-07-08 12:47:59 +02:00
Roberto Viola
afae83e923 Update floating.htm 2024-07-08 12:34:37 +02:00
Roberto Viola
6851eaf386 Floating window: adding total remaining time after the elapsed time (Issue #1863) 2024-07-08 12:28:13 +02:00
Roberto Viola
025384ef5b Update floating.htm 2024-07-08 11:19:07 +02:00
Roberto Viola
b37fef3bbd Update floating.htm 2024-07-08 10:56:19 +02:00
Roberto Viola
034e1dafb5 Floating window: adding total remaining time after the elapsed time (Issue #1863) 2024-07-08 10:06:48 +02:00
Roberto Viola
f95713dc80 Peloton Power Zone / Zwift ERG Auto Resistance Broken for EX3 w/ Power Meter Pedals (Issue #2431) 2024-07-08 09:36:45 +02:00
Roberto Viola
90ece532db Update project.pbxproj 2024-07-05 10:22:58 +02:00
Roberto Viola
8a5d418ada Unable to direct the treadmill from the application #2421
https://github.com/cagnulein/qdomyos-zwift/issues/2421#issuecomment-2210353875
2024-07-05 10:21:36 +02:00
Roberto Viola
5ea354fd19 BH Osaka bike #2425 2024-07-04 21:11:17 +02:00
Roberto Viola
fdf5eca726 BH Osaka bike #2425 2024-07-04 17:49:19 +02:00
Roberto Viola
70f5dc2cc4 BH Osaka bike #2425 2024-07-04 15:22:31 +02:00
Roberto Viola
f48e774d31 BH Osaka bike #2425 2024-07-04 11:49:44 +02:00
Roberto Viola
2195dcb1cb Unable to direct the treadmill from the application (Issue #2421) 2024-07-04 09:23:45 +02:00
Roberto Viola
e5e7ccb777 BH Osaka bike #2425 2024-07-04 09:21:44 +02:00
Roberto Viola
efbf6148ac BH Osaka bike #2425 2024-07-03 15:48:57 +02:00
Roberto Viola
1380d79909 wavefit B300 bike #2423 2024-07-03 06:54:32 +02:00
Roberto Viola
f5d1e6f7d2 Cecotec DrumFit Indoor 10000 MagnoMotor Connected #2420 2024-07-02 08:59:58 +02:00
Roberto Viola
d7acfc4dea fixing incline in zwo 2024-07-01 18:40:21 +02:00
Roberto Viola
352049a4c1 fixing peloton image 2024-07-01 14:32:55 +02:00
Roberto Viola
002ae1d62b Miweba MC700 ellipital Trainer #2419 2024-07-01 12:10:22 +02:00
Roberto Viola
674f01b9f4 removing peloton jpg after sending the email 2024-07-01 08:58:32 +02:00
Roberto Viola
93255eee35 Multiplicator for speed gain is not working in QZ when increasing speed on the treadmill (Issue #2417) 2024-06-30 10:54:37 +01:00
Roberto Viola
53aa1b361b Sport Synology Z5 Treadmill #2415 2024-06-28 09:25:34 +02:00
Roberto Viola
c6485f891c Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-06-28 08:18:54 +02:00
Roberto Viola
ab73c97c36 Cross Rope #2381 2024-06-28 08:18:15 +02:00
Roberto Viola
49d56b1fc6 sports tech f37s treadmill #2412 2024-06-27 18:14:11 +02:00
Roberto Viola
5a6afbb500 adding Labgrey Magnetic Exercise Bike 2024-06-25 15:50:28 +02:00
Roberto Viola
b0a76d1aa0 Cross Rope #2381 2024-06-25 09:13:50 +02:00
Roberto Viola
9a99d0fc7b Strava Upload Prompt and Setting (#2406)
* i need to test it on android and ios

* Update project.pbxproj
2024-06-24 17:13:35 +02:00
Roberto Viola
2b43974dae Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-06-24 12:09:58 +02:00
Roberto Viola
33e5eaeb41 Update project.pbxproj 2024-06-24 12:09:45 +02:00
Roberto Viola
75c04e3f6b Flow Fitness - DCT2000i #2403 2024-06-24 12:09:31 +02:00
Roberto Viola
6a74c2015e Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-06-24 11:23:26 +02:00
Roberto Viola
595a472780 Watt bike #2405 2024-06-24 10:55:40 +02:00
Roberto Viola
e937cd7481 Update project.pbxproj 2024-06-24 10:39:07 +02:00
Roberto Viola
5a72504eba Zwift Play, remap shifting buttons #2399
hanlding ftms char for tacx neo
2024-06-24 10:35:36 +02:00
Roberto Viola
acb1f3964a Zwift Play, remap shifting buttons #2399 2024-06-24 10:26:40 +02:00
Roberto Viola
00afd12f34 Change keyboard shortcut for gear shifting on Windows (Issue #2404) 2024-06-24 10:08:17 +02:00
Roberto Viola
54abdcda76 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-06-24 08:34:27 +02:00
Roberto Viola
7c1280c4e1 QZ app crashes when pairing with Technogym Run treadmill (Issue #2380) 2024-06-24 08:34:22 +02:00
Roberto Viola
50df26cbb9 GLT Bike #2402 2024-06-23 09:55:15 +02:00
Roberto Viola
db1de5ef31 Renpho smart bike r-q002 n (Issue #2401) 2024-06-22 21:04:35 +02:00
Roberto Viola
461068532a saving laps for jumprope #2381 2024-06-21 15:56:46 +02:00
Roberto Viola
4a23556257 Cross Rope #2381 2024-06-21 11:37:39 +02:00
Roberto Viola
a6ebc614e3 Cross Rope #2381 2024-06-21 11:15:29 +02:00
Roberto Viola
a6b530dc60 Floating window: adding total remaining time after the elapsed time (Issue #1863) 2024-06-21 09:06:06 +02:00
Roberto Viola
96c5b7e595 2.16.58 2024-06-20 11:32:59 +02:00
Roberto Viola
51413841cc Peloton FTMS Treadmill QZ on Android (#2397)
* let's try

* Update virtualtreadmill.cpp

* Update BleAdvertiser.java
2024-06-20 11:31:18 +02:00
Roberto Viola
fae69702fb Cross Rope #2381 2024-06-19 14:26:58 +02:00
Roberto Viola
f0fdf428d2 Cross Rope #2381 2024-06-19 12:16:37 +02:00
456 changed files with 46393 additions and 9767 deletions

View File

@@ -1,3 +1,4 @@
# This is a basic workflow to help you get started with Actions
name: CI
@@ -140,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" .
@@ -167,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" .
@@ -195,14 +196,14 @@ jobs:
if: ${{ ! matrix.config.python }}
- name: Archive windows binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: windows-binary
path: windows-binary.zip
if: matrix.config.python
- name: Archive windows binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: windows-binary-no-python
path: windows-binary-no-python.zip
@@ -337,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:
@@ -433,7 +434,7 @@ jobs:
run: qmake; make -j8
- name: Archive linux-desktop binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: linux-desktop-binary
path: src/qdomyos-zwift
@@ -442,7 +443,7 @@ jobs:
run: cd tst; GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests; cd ..
- name: Upload test results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
if: failure()
with:
name: test_results_xml
@@ -585,7 +586,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '11'
java-version: '11.0.23+9'
- name: patching qt for bluetooth
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
@@ -593,14 +594,6 @@ jobs:
- name: download 3rd party files for qthttpserver
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
- name: Build qthttpserver
run: |
cd src/qthttpserver
qmake
make -j8
make install
cd ../..
- name: Set Android NDK 21 && build
run: |
# Install NDK 21 after GitHub update
@@ -622,6 +615,14 @@ jobs:
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
# QTHTTPSERVER must use the same NDK
cd src/qthttpserver
qmake
make -j8
make install
cd ../..
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json
cat src/android-qdomyos-zwift-deployment-settings.json
@@ -630,7 +631,7 @@ jobs:
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
- name: Archive apk binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: fdroid-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
@@ -651,7 +652,7 @@ jobs:
ios-build:
# The type of runner that the job will run on
runs-on: macos-12
runs-on: macos-latest
permissions:
contents: write
@@ -824,14 +825,44 @@ jobs:
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
- name: Clone vcpkg
run: git clone https://github.com/microsoft/vcpkg.git
working-directory: ${{ runner.workspace }}
- name: Bootstrap vcpkg
run: .\vcpkg\bootstrap-vcpkg.bat
working-directory: ${{ runner.workspace }}
- name: Create vcpkg.json
working-directory: ${{ runner.workspace }}
run: |
echo '{
"name": "qdomyos-zwift",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"dependencies": [
"protobuf",
"protobuf-c",
"abseil"
],
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
}' > vcpkg.json
- name: Install dependencies
run: |
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
run: |
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
qmake
nmake
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 .
@@ -839,6 +870,7 @@ jobs:
cp ../../windows/*.py .
cp ../../windows/*.bat .
cp ../../../windows_openssl/*.* .
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
mkdir adb
mkdir python
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
@@ -849,21 +881,25 @@ jobs:
if: matrix.config.python
- name: Build without python
run: |
run: |
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
qmake
nmake
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" .
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
cp "C:/mingw64/bin/libstdc++-6.dll" .
cp "C:/mingw64/bin/libstdc++-6.dll" .
cp ../../../icons/iOS/iTunesArtwork@2x.png .
cp ../../AppxManifest.xml .
cp ../../../windows_openssl/*.* .
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
mkdir adb
cp ../../adb/* adb/
cd ..
@@ -883,14 +919,14 @@ jobs:
if: ${{ ! matrix.config.python }}
- name: Archive windows binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: windows-msvc2019-binary
path: windows-msvc2019-binary.zip
if: matrix.config.python
- name: Archive windows binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: windows-msvc2019-binary-no-python
path: windows-msvc2019-binary-no-python.zip
@@ -930,6 +966,9 @@ jobs:
repository: qt-labs/qthttpserver
path: "src/qthttpserver"
- name: Install CMake
uses: lukka/get-cmake@latest
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
@@ -973,8 +1012,38 @@ jobs:
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
- name: Clone vcpkg
run: git clone https://github.com/microsoft/vcpkg.git
working-directory: ${{ runner.workspace }}
- name: Bootstrap vcpkg
run: .\vcpkg\bootstrap-vcpkg.bat
working-directory: ${{ runner.workspace }}
- name: Create vcpkg.json
working-directory: ${{ runner.workspace }}
run: |
echo '{
"name": "qdomyos-zwift",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"dependencies": [
"protobuf",
"protobuf-c",
"abseil"
],
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
}' > vcpkg.json
- name: Install dependencies
run: |
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
run: |
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
cd src
echo "#define AISERVER" >> aiserver.h
cd ..
@@ -983,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 .
@@ -993,6 +1062,7 @@ jobs:
cp ../../windows/zwift-workout-ai-server.py zwift-workout.py
cp ../../windows/*.bat .
cp ../../../windows_openssl/*.* .
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
mkdir adb
cp ../../adb/* adb/
cd ..
@@ -1006,19 +1076,203 @@ jobs:
run: Compress-Archive src/debug/output windows-msvc2019-ai-server-binary.zip
- name: Archive windows binary
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: windows-msvc2019-ai-server-binary
path: windows-msvc2019-ai-server-binary.zip
raspberry-pi-build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Secrets
run: |
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
- name: Build for Raspberry Pi
uses: docker://arm32v7/debian:bullseye-20241016
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
cat qdomyos-zwift.pro &&
qmake &&
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-32bit
raspberry-pi-build-and-image-64bit:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Secrets
run: |
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
- name: Build for Raspberry Pi 64-bit
uses: docker://arm64v8/debian:bullseye-20241016
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
cat qdomyos-zwift.pro &&
qmake &&
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-64bit
- name: Download and expand Raspberry Pi OS image
run: |
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz
xz -d 2024-10-22-raspios-bookworm-arm64-lite.img.xz
ORIGINAL_SIZE=$(stat -c %s 2024-10-22-raspios-bookworm-arm64-lite.img)
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
truncate -s $NEW_SIZE 2024-10-22-raspios-bookworm-arm64-lite.img
sudo apt-get update
sudo apt-get install -y parted
sudo parted 2024-10-22-raspios-bookworm-arm64-lite.img resizepart 2 100%
- name: Mount Raspberry Pi image
run: |
sudo apt-get install -y kpartx qemu-user-static
LOOP_DEVICE=$(sudo losetup -f --show 2024-10-22-raspios-bookworm-arm64-lite.img)
echo "Loop device is $LOOP_DEVICE"
sudo kpartx -av $LOOP_DEVICE
sudo mkdir -p /mnt/raspbian
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
sudo mkdir -p /mnt/raspbian_p1
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p1 /mnt/raspbian_p1
- name: Install Qt and dependencies on Raspberry Pi image
run: |
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
apt-get update
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
EOF
- name: Copy binary to Raspberry Pi image
run: |
sudo cp src/qdomyos-zwift-64bit /mnt/raspbian/home/pi/qdomyos-zwift
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
- name: Setup auto-start for qdomyos-zwift
run: |
echo '[Unit]
Description=QDomyos-Zwift
After=multi-user.target
[Service]
ExecStart=/home/pi/qdomyos-zwift
User=pi
Environment=DISPLAY=:0
[Install]
WantedBy=multi-user.target' | sudo tee /mnt/raspbian/etc/systemd/system/qdomyos-zwift.service
sudo chroot /mnt/raspbian systemctl enable qdomyos-zwift.service
- name: Modify boot config to enable bluetooth
run: |
# The following line makes it specific for Raspberry Pi Zero 2W.
# though I expect it is needed for Raspberry Pi 3B and maybe others as well
echo "[pi02]" | sudo tee -a /mnt/raspbian_p1/config.txt
echo "dtoverlay=miniuart-bt" | sudo tee -a /mnt/raspbian_p1/config.txt
- name: Unmount Raspberry Pi image
run: |
sudo umount /mnt/raspbian
sudo umount /mnt/raspbian_p1
sudo kpartx -d ${{ env.LOOP_DEVICE }}
sudo losetup -d ${{ env.LOOP_DEVICE }}
- name: Compress modified Raspberry Pi image
run: |
xz -z 2024-10-22-raspios-bookworm-arm64-lite.img
- name: Upload Raspberry Pi image as artifact
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-image
path: 2024-10-22-raspios-bookworm-arm64-lite.img.xz
upload_to_release:
permissions: write-all
runs-on: ubuntu-20.04
if: github.event_name == 'schedule'
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build] # 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@v3
uses: actions/download-artifact@v4
- name: Update nightly release
uses: andelf/nightly-release@main
env:
@@ -1044,3 +1298,6 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-64bit-binary/qdomyos-zwift-64bit
2024-10-22-raspios-bookworm-arm64-lite.img.xz

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

@@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
"sourceFileMap": {
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/"
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
}
}
]
}

View File

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

View File

@@ -6,21 +6,6 @@
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 6CC3B5D2136C7CD6A5CF5A59 /* Build configuration list for PBXNativeTarget "qdomyoszwift" */;
buildPhases = (
5E618435888B9D49F8540165 /* Qt Qmake */,
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */,
);
dependencies = (
);
name = "Qt Preprocess";
productName = "Qt Preprocess";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
023642106C14651D2E1F4D5D /* dialogplugin in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = B9CCC4658EA620ABBD832E71 /* dialogplugin */; };
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 3088C65BF5992B009FFC93B5 /* virtualtreadmill.cpp */; settings = {ATTRIBUTES = (); }; };
@@ -143,6 +128,8 @@
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87083D9526678EFA0072410D /* zwiftworkout.cpp */; };
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */; };
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */; };
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB42CEF8FD200839641 /* technogymbike.cpp */; };
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706B29C48AEA0094D0F3 /* handleurl.cpp */; };
8710706E29C48AF30094D0F3 /* moc_handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */; };
8710707329C4A5E70094D0F3 /* GarminConnect.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710707229C4A5E70094D0F3 /* GarminConnect.swift */; };
@@ -152,6 +139,8 @@
871189192893CECF006A04D1 /* libqavfmediaplayer.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189182893CECC006A04D1 /* libqavfmediaplayer.a */; };
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */; };
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */; };
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */; };
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E82C75E6DB009BAC05 /* antbike.cpp */; };
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A08276BBAF600141463 /* virtualrower.cpp */; };
87182A0B276BBB1200141463 /* moc_virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A0A276BBB1200141463 /* moc_virtualrower.cpp */; };
8718CBA2263063BD004BF4EE /* soleelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8718CB9A263063BC004BF4EE /* soleelliptical.cpp */; };
@@ -166,6 +155,24 @@
871B9FD2265E6A8800DB41F4 /* powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */; };
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */; };
871E4CD125A6FB5A00E18D6D /* BLEPeripheralManager.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */; };
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */; };
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */; };
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */; };
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */; };
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */; };
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */; };
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */; };
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F52CE65451008C2C17 /* qmqttconnection.cpp */; };
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089042CE65451008C2C17 /* qmqttsubscription.cpp */; };
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F32CE65451008C2C17 /* qmqttclient.cpp */; };
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */; };
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890D2CE65451008C2C17 /* qmqtttype.cpp */; };
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */; };
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FE2CE65451008C2C17 /* qmqttmessage.cpp */; };
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */; };
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */; };
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */; };
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */; };
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */; };
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
@@ -174,6 +181,8 @@
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */; };
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */; };
8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */; };
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */; };
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */; };
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
@@ -254,6 +263,8 @@
873CD23027EF8EF5000131BC /* iosinapppurchasetransaction.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873CD22727EF8E4B000131BC /* iosinapppurchasetransaction.mm */; };
873D388B29B0D745006A2611 /* ConnectIQ.xcframework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 873D388A29B0D744006A2611 /* ConnectIQ.xcframework */; };
873D388C29B0D745006A2611 /* ConnectIQ.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 873D388A29B0D744006A2611 /* ConnectIQ.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
873D3C4A2C296B0100770CB9 /* moc_jumprope.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873D3C492C296B0100770CB9 /* moc_jumprope.cpp */; };
873D3C4D2C296B3800770CB9 /* jumprope.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873D3C4B2C296B3700770CB9 /* jumprope.cpp */; };
873F022F274BE471002D0349 /* mcfbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873F022D274BE471002D0349 /* mcfbike.cpp */; };
873F0231274BE47D002D0349 /* moc_mcfbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873F0230274BE47D002D0349 /* moc_mcfbike.cpp */; };
87420DF6269D770F000C5EC6 /* libQt5WebView.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87420DF5269D770F000C5EC6 /* libQt5WebView.a */; };
@@ -277,6 +288,10 @@
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
87586A4125B8340E00A243C4 /* proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4025B8340E00A243C4 /* proformbike.cpp */; };
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4225B8341B00A243C4 /* moc_proformbike.cpp */; };
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */; };
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */; };
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA94B2D130F8100667EE6 /* moc_osc.cpp */; };
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9502D130FBC00667EE6 /* osc.cpp */; };
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69B826342E8D0009FD78 /* spirittreadmill.cpp */; };
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */; };
8762D50F2601F7EA00F6F049 /* M3iNS.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D50B2601F7EA00F6F049 /* M3iNS.mm */; };
@@ -350,8 +365,13 @@
876F9B61275385D8006AE6FA /* moc_fitmetria_fanfit.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876F9B60275385D8006AE6FA /* moc_fitmetria_fanfit.cpp */; };
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
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 */; };
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B52C98629B00BB1697 /* sportstechelliptical.cpp */; };
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74122893D705009A64C8 /* CoreVideo.framework */; };
877A7609269D8E9F0024DD2C /* WebKit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 877A7608269D8E9F0024DD2C /* WebKit.framework */; };
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */; };
@@ -370,6 +390,8 @@
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531672711A3EB004B153D /* moc_fakebike.cpp */; };
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */; };
8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */; };
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
@@ -417,7 +439,11 @@
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */; };
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */; };
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */; };
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A4B75F25AF27CB0027EF3C /* metric.cpp */; };
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */; };
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */; };
87ADD2BB27634C1500B7A0AB /* technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */; };
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */; };
87AE0CB227760DCB00E547E9 /* virtualtreadmill_zwift.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87AE0CB127760DCB00E547E9 /* virtualtreadmill_zwift.swift */; };
@@ -429,6 +455,8 @@
87B617F225F260150094A1CB /* moc_fitshowtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */; };
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F025F260140094A1CB /* moc_snodebike.cpp */; };
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F125F260150094A1CB /* moc_screencapture.cpp */; };
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */; };
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B871922CE1E94D009B06CA /* zwifthubbike.swift */; };
87BAC3BF2BA497160003E925 /* PrivacyInfo.xcprivacy in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */; };
87BAC3C12BA497350003E925 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */; };
87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */; };
@@ -518,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 */; };
@@ -542,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 */; };
@@ -614,13 +648,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
876E4E0E2594739400BD5714 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = E9F0AFC024A6F2D65CE84E08;
remoteInfo = "Qt Preprocess";
};
876E4E1C2594748000BD5714 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
@@ -903,6 +930,8 @@
87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportsplusbike.cpp; path = ../src/devices/sportsplusbike/sportsplusbike.cpp; sourceTree = "<group>"; };
87097D2E275EA9A20020EE6F /* sportsplusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportsplusbike.h; path = ../src/devices/sportsplusbike/sportsplusbike.h; sourceTree = "<group>"; };
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymbike.cpp; sourceTree = "<group>"; };
870A5DB42CEF8FD200839641 /* technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = technogymbike.cpp; path = ../src/devices/technogymbike/technogymbike.cpp; sourceTree = SOURCE_ROOT; };
8710706A29C48AE90094D0F3 /* handleurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = handleurl.h; path = ../src/handleurl.h; sourceTree = "<group>"; };
8710706B29C48AEA0094D0F3 /* handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = handleurl.cpp; path = ../src/handleurl.cpp; sourceTree = "<group>"; };
8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_handleurl.cpp; sourceTree = "<group>"; };
@@ -914,6 +943,9 @@
871235BD26B297660012D0F2 /* kingsmithr1protreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kingsmithr1protreadmill.h; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h; sourceTree = "<group>"; };
871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = kingsmithr1protreadmill.cpp; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_antbike.cpp; sourceTree = "<group>"; };
8715A3E82C75E6DB009BAC05 /* antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = antbike.cpp; path = ../src/devices/antbike/antbike.cpp; sourceTree = "<group>"; };
8715A3E92C75E6DB009BAC05 /* antbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = antbike.h; path = ../src/devices/antbike/antbike.h; sourceTree = "<group>"; };
87182A07276BBAF600141463 /* virtualrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = virtualrower.h; path = ../src/virtualdevices/virtualrower.h; sourceTree = "<group>"; };
87182A08276BBAF600141463 /* virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = virtualrower.cpp; path = ../src/virtualdevices/virtualrower.cpp; sourceTree = "<group>"; };
87182A0A276BBB1200141463 /* moc_virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_virtualrower.cpp; sourceTree = "<group>"; };
@@ -936,6 +968,42 @@
871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = powerzonepack.cpp; path = ../src/powerzonepack.cpp; sourceTree = "<group>"; };
871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_powerzonepack.cpp; sourceTree = "<group>"; };
871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BLEPeripheralManager.swift; path = ../src/ios/BLEPeripheralManager.swift; sourceTree = "<group>"; };
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mqttpublisher.cpp; sourceTree = "<group>"; };
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttclient.cpp; sourceTree = "<group>"; };
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttconnection_p.cpp; sourceTree = "<group>"; };
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttmessage.cpp; sourceTree = "<group>"; };
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttsubscription.cpp; sourceTree = "<group>"; };
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttauthenticationproperties.h; path = ../src/mqtt/qmqttauthenticationproperties.h; sourceTree = SOURCE_ROOT; };
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttauthenticationproperties.cpp; path = ../src/mqtt/qmqttauthenticationproperties.cpp; sourceTree = SOURCE_ROOT; };
872088F22CE65451008C2C17 /* qmqttclient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient.h; path = ../src/mqtt/qmqttclient.h; sourceTree = SOURCE_ROOT; };
872088F32CE65451008C2C17 /* qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttclient.cpp; path = ../src/mqtt/qmqttclient.cpp; sourceTree = SOURCE_ROOT; };
872088F42CE65451008C2C17 /* qmqttclient_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient_p.h; path = ../src/mqtt/qmqttclient_p.h; sourceTree = SOURCE_ROOT; };
872088F52CE65451008C2C17 /* qmqttconnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnection.cpp; path = ../src/mqtt/qmqttconnection.cpp; sourceTree = SOURCE_ROOT; };
872088F62CE65451008C2C17 /* qmqttconnection_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnection_p.h; path = ../src/mqtt/qmqttconnection_p.h; sourceTree = SOURCE_ROOT; };
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties.h; path = ../src/mqtt/qmqttconnectionproperties.h; sourceTree = SOURCE_ROOT; };
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnectionproperties.cpp; path = ../src/mqtt/qmqttconnectionproperties.cpp; sourceTree = SOURCE_ROOT; };
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties_p.h; path = ../src/mqtt/qmqttconnectionproperties_p.h; sourceTree = SOURCE_ROOT; };
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttcontrolpacket.cpp; path = ../src/mqtt/qmqttcontrolpacket.cpp; sourceTree = SOURCE_ROOT; };
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttcontrolpacket_p.h; path = ../src/mqtt/qmqttcontrolpacket_p.h; sourceTree = SOURCE_ROOT; };
872088FC2CE65451008C2C17 /* qmqttglobal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttglobal.h; path = ../src/mqtt/qmqttglobal.h; sourceTree = SOURCE_ROOT; };
872088FD2CE65451008C2C17 /* qmqttmessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage.h; path = ../src/mqtt/qmqttmessage.h; sourceTree = SOURCE_ROOT; };
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttmessage.cpp; path = ../src/mqtt/qmqttmessage.cpp; sourceTree = SOURCE_ROOT; };
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage_p.h; path = ../src/mqtt/qmqttmessage_p.h; sourceTree = SOURCE_ROOT; };
872089002CE65451008C2C17 /* qmqttpublishproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties.h; path = ../src/mqtt/qmqttpublishproperties.h; sourceTree = SOURCE_ROOT; };
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttpublishproperties.cpp; path = ../src/mqtt/qmqttpublishproperties.cpp; sourceTree = SOURCE_ROOT; };
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties_p.h; path = ../src/mqtt/qmqttpublishproperties_p.h; sourceTree = SOURCE_ROOT; };
872089032CE65451008C2C17 /* qmqttsubscription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription.h; path = ../src/mqtt/qmqttsubscription.h; sourceTree = SOURCE_ROOT; };
872089042CE65451008C2C17 /* qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscription.cpp; path = ../src/mqtt/qmqttsubscription.cpp; sourceTree = SOURCE_ROOT; };
872089052CE65451008C2C17 /* qmqttsubscription_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription_p.h; path = ../src/mqtt/qmqttsubscription_p.h; sourceTree = SOURCE_ROOT; };
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscriptionproperties.h; path = ../src/mqtt/qmqttsubscriptionproperties.h; sourceTree = SOURCE_ROOT; };
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscriptionproperties.cpp; path = ../src/mqtt/qmqttsubscriptionproperties.cpp; sourceTree = SOURCE_ROOT; };
872089082CE65451008C2C17 /* qmqtttopicfilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicfilter.h; path = ../src/mqtt/qmqtttopicfilter.h; sourceTree = SOURCE_ROOT; };
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicfilter.cpp; path = ../src/mqtt/qmqtttopicfilter.cpp; sourceTree = SOURCE_ROOT; };
8720890A2CE65451008C2C17 /* qmqtttopicname.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicname.h; path = ../src/mqtt/qmqtttopicname.h; sourceTree = SOURCE_ROOT; };
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicname.cpp; path = ../src/mqtt/qmqtttopicname.cpp; sourceTree = SOURCE_ROOT; };
8720890C2CE65451008C2C17 /* qmqtttype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttype.h; path = ../src/mqtt/qmqtttype.h; sourceTree = SOURCE_ROOT; };
8720890D2CE65451008C2C17 /* qmqtttype.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttype.cpp; path = ../src/mqtt/qmqtttype.cpp; sourceTree = SOURCE_ROOT; };
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mqttpublisher.cpp; path = ../src/mqttpublisher.cpp; sourceTree = SOURCE_ROOT; };
872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackelliptical.cpp; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.cpp; sourceTree = "<group>"; };
872261ED289EA873006A6F75 /* nordictrackelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackelliptical.h; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.h; sourceTree = "<group>"; };
872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackelliptical.cpp; sourceTree = "<group>"; };
@@ -949,6 +1017,9 @@
8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_QTelnet.cpp; sourceTree = "<group>"; };
8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtelnetbike.cpp; sourceTree = "<group>"; };
8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "qdomyoszwift-Bridging-Header.h"; sourceTree = "<group>"; };
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbelliptical.cpp; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbelliptical.h; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h; sourceTree = "<group>"; };
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/devices/faketreadmill/faketreadmill.h; sourceTree = "<group>"; };
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/devices/faketreadmill/faketreadmill.cpp; sourceTree = "<group>"; };
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = "<group>"; };
@@ -1062,6 +1133,9 @@
873CD22A27EF8E4B000131BC /* iosinapppurchasetransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iosinapppurchasetransaction.h; path = ../src/purchasing/ios/iosinapppurchasetransaction.h; sourceTree = "<group>"; };
873CD22E27EF8EC1000131BC /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
873D388A29B0D744006A2611 /* ConnectIQ.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ConnectIQ.xcframework; path = ../src/ConnectIQ/iOS/ConnectIQ.xcframework; sourceTree = "<group>"; };
873D3C492C296B0100770CB9 /* moc_jumprope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_jumprope.cpp; sourceTree = "<group>"; };
873D3C4B2C296B3700770CB9 /* jumprope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = jumprope.cpp; path = ../src/devices/jumprope.cpp; sourceTree = "<group>"; };
873D3C4C2C296B3700770CB9 /* jumprope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = jumprope.h; path = ../src/devices/jumprope.h; sourceTree = "<group>"; };
873F022D274BE471002D0349 /* mcfbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mcfbike.cpp; path = ../src/devices/mcfbike/mcfbike.cpp; sourceTree = "<group>"; };
873F022E274BE471002D0349 /* mcfbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mcfbike.h; path = ../src/devices/mcfbike/mcfbike.h; sourceTree = "<group>"; };
873F0230274BE47D002D0349 /* moc_mcfbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mcfbike.cpp; sourceTree = "<group>"; };
@@ -1098,6 +1172,18 @@
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/devices/proformbike/proformbike.h; sourceTree = "<group>"; };
87586A4025B8340E00A243C4 /* proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformbike.cpp; path = ../src/devices/proformbike/proformbike.cpp; sourceTree = "<group>"; };
87586A4225B8341B00A243C4 /* moc_proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformbike.cpp; sourceTree = "<group>"; };
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kineticinroadbike.cpp; sourceTree = "<group>"; };
875CA9472D0C742500667EE6 /* kineticinroadbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = kineticinroadbike.h; path = ../src/devices/kineticinroadbike/kineticinroadbike.h; sourceTree = SOURCE_ROOT; };
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = kineticinroadbike.cpp; path = ../src/devices/kineticinroadbike/kineticinroadbike.cpp; sourceTree = SOURCE_ROOT; };
875CA94B2D130F8100667EE6 /* moc_osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_osc.cpp; sourceTree = "<group>"; };
875CA94D2D130FBC00667EE6 /* client.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = client.hpp; path = ../src/oscpp/client.hpp; sourceTree = SOURCE_ROOT; };
875CA94E2D130FBC00667EE6 /* error.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = error.hpp; path = ../src/oscpp/error.hpp; sourceTree = SOURCE_ROOT; };
875CA94F2D130FBC00667EE6 /* osc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osc.h; path = ../src/osc.h; sourceTree = SOURCE_ROOT; };
875CA9502D130FBC00667EE6 /* osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = osc.cpp; path = ../src/osc.cpp; sourceTree = SOURCE_ROOT; };
875CA9512D130FBC00667EE6 /* print.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = print.hpp; path = ../src/oscpp/print.hpp; sourceTree = SOURCE_ROOT; };
875CA9522D130FBC00667EE6 /* server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = server.hpp; path = ../src/oscpp/server.hpp; sourceTree = SOURCE_ROOT; };
875CA9532D130FBC00667EE6 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = types.hpp; path = ../src/oscpp/types.hpp; sourceTree = SOURCE_ROOT; };
875CA9542D130FBC00667EE6 /* util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = util.hpp; path = ../src/oscpp/util.hpp; sourceTree = SOURCE_ROOT; };
875F69B726342E8D0009FD78 /* spirittreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = spirittreadmill.h; path = ../src/devices/spirittreadmill/spirittreadmill.h; sourceTree = "<group>"; };
875F69B826342E8D0009FD78 /* spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = spirittreadmill.cpp; path = ../src/devices/spirittreadmill/spirittreadmill.cpp; sourceTree = "<group>"; };
875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_spirittreadmill.cpp; sourceTree = "<group>"; };
@@ -1217,9 +1303,17 @@
8772A0E425E43AD90080718C /* trxappgateusbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = trxappgateusbbike.h; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.h; sourceTree = "<group>"; };
8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbbike.cpp; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.cpp; sourceTree = "<group>"; };
8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbbike.cpp; sourceTree = "<group>"; };
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>"; };
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportstechelliptical.cpp; sourceTree = "<group>"; };
877758B42C98629B00BB1697 /* sportstechelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportstechelliptical.h; path = ../src/devices/sportstechelliptical/sportstechelliptical.h; sourceTree = "<group>"; };
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechelliptical.cpp; path = ../src/devices/sportstechelliptical/sportstechelliptical.cpp; sourceTree = "<group>"; };
877A7606269D8E0F0024DD2C /* libqtwebview_darwin_debug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtwebview_darwin_debug.a; path = ../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.a; sourceTree = "<group>"; };
877A7608269D8E9F0024DD2C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bowflextreadmill.cpp; path = ../src/devices/bowflextreadmill/bowflextreadmill.cpp; sourceTree = "<group>"; };
@@ -1247,6 +1341,9 @@
8785D5402B3DD0EC005A2EB7 /* PlayerStateWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlayerStateWrapper.h; path = "../src/zwift-api/PlayerStateWrapper.h"; sourceTree = "<group>"; };
8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_PlayerStateWrapper.cpp; sourceTree = "<group>"; };
8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_zwift_client_auth.cpp; sourceTree = "<group>"; };
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF22D09AADF005F86B4 /* sportsplusrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportsplusrower.h; path = ../src/devices/sportsplusrower/sportsplusrower.h; sourceTree = SOURCE_ROOT; };
8789DCDB6A4F681A76DF3F92 /* Qt5Widgets */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = Qt5Widgets; path = "/Users/cagnulein/Qt/5.15.2/ios/lib/libQt5Widgets$(QT_LIBRARY_SUFFIX).a"; sourceTree = "<absolute>"; };
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/devices/yesoulbike/yesoulbike.cpp; sourceTree = "<group>"; };
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/devices/yesoulbike/yesoulbike.h; sourceTree = "<group>"; };
@@ -1295,6 +1392,7 @@
87A0770E29B641D500A368BF /* wahookickrheadwind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wahookickrheadwind.h; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.h; sourceTree = "<group>"; };
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wahookickrheadwind.cpp; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier2ad9.h; path = ../src/characteristics/characteristicnotifier2ad9.h; sourceTree = "<group>"; };
87A0C4B7262329A600121A76 /* npecablebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = npecablebike.cpp; path = ../src/devices/npecablebike/npecablebike.cpp; sourceTree = "<group>"; };
87A0C4B8262329A600121A76 /* cscbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cscbike.h; path = ../src/devices/cscbike/cscbike.h; sourceTree = "<group>"; };
87A0C4B9262329A600121A76 /* cscbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cscbike.cpp; path = ../src/devices/cscbike/cscbike.cpp; sourceTree = "<group>"; };
@@ -1315,11 +1413,17 @@
87A3BC212656429400D302E3 /* rower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rower.h; path = ../src/devices/rower.h; sourceTree = "<group>"; };
87A3BC24265642A200D302E3 /* moc_rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_rower.cpp; sourceTree = "<group>"; };
87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonrower.cpp; sourceTree = "<group>"; };
87A3DD982D3413790060BAEB /* lifespantreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = lifespantreadmill.h; path = ../src/devices/lifespantreadmill/lifespantreadmill.h; sourceTree = SOURCE_ROOT; };
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lifespantreadmill.cpp; path = ../src/devices/lifespantreadmill/lifespantreadmill.cpp; sourceTree = SOURCE_ROOT; };
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_lifespantreadmill.cpp; sourceTree = "<group>"; };
87A3EBB925D2CFED0040EB4C /* sportstechbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportstechbike.h; path = ../src/devices/sportstechbike/sportstechbike.h; sourceTree = "<group>"; };
87A3EBBA25D2CFED0040EB4C /* sportstechbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechbike.cpp; path = ../src/devices/sportstechbike/sportstechbike.cpp; sourceTree = "<group>"; };
87A4B75F25AF27CB0027EF3C /* metric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metric.cpp; path = ../src/metric.cpp; sourceTree = "<group>"; };
87A4B76025AF27CB0027EF3C /* metric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metric.h; path = ../src/metric.h; sourceTree = "<group>"; };
87A659DB8BE7DBAA7B395EF4 /* fit_monitoring_info_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_monitoring_info_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_monitoring_info_mesg_listener.hpp"; sourceTree = "<absolute>"; };
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sramAXSController.cpp; sourceTree = "<group>"; };
87A6825B2CE3AB4000586A2A /* sramAXSController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sramAXSController.h; path = ../src/devices/sramAXSController/sramAXSController.h; sourceTree = SOURCE_ROOT; };
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sramAXSController.cpp; path = ../src/devices/sramAXSController/sramAXSController.cpp; sourceTree = SOURCE_ROOT; };
87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = technogymmyruntreadmill.cpp; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
87ADD2BA27634C1400B7A0AB /* technogymmyruntreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = technogymmyruntreadmill.h; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.h; sourceTree = "<group>"; };
87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
@@ -1336,6 +1440,8 @@
87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitshowtreadmill.cpp; sourceTree = "<group>"; };
87B617F025F260140094A1CB /* moc_snodebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_snodebike.cpp; sourceTree = "<group>"; };
87B617F125F260150094A1CB /* moc_screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_screencapture.cpp; sourceTree = "<group>"; };
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Zwift hub.pb.swift"; path = "../src/devices/zwifthubbike/Zwift hub.pb.swift"; sourceTree = SOURCE_ROOT; };
87B871922CE1E94D009B06CA /* zwifthubbike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwifthubbike.swift; path = ../src/devices/zwifthubbike/zwifthubbike.swift; sourceTree = SOURCE_ROOT; };
87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../src/ios/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_focustreadmill.cpp; sourceTree = "<group>"; };
@@ -1467,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>"; };
@@ -1502,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>"; };
@@ -2053,6 +2168,90 @@
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 */,
87873AF22D09AADF005F86B4 /* sportsplusrower.h */,
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */,
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */,
870A5DB42CEF8FD200839641 /* technogymbike.cpp */,
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */,
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */,
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */,
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */,
872088F22CE65451008C2C17 /* qmqttclient.h */,
872088F32CE65451008C2C17 /* qmqttclient.cpp */,
872088F42CE65451008C2C17 /* qmqttclient_p.h */,
872088F52CE65451008C2C17 /* qmqttconnection.cpp */,
872088F62CE65451008C2C17 /* qmqttconnection_p.h */,
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */,
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */,
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */,
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */,
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */,
872088FC2CE65451008C2C17 /* qmqttglobal.h */,
872088FD2CE65451008C2C17 /* qmqttmessage.h */,
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */,
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */,
872089002CE65451008C2C17 /* qmqttpublishproperties.h */,
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */,
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */,
872089032CE65451008C2C17 /* qmqttsubscription.h */,
872089042CE65451008C2C17 /* qmqttsubscription.cpp */,
872089052CE65451008C2C17 /* qmqttsubscription_p.h */,
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */,
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */,
872089082CE65451008C2C17 /* qmqtttopicfilter.h */,
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */,
8720890A2CE65451008C2C17 /* qmqtttopicname.h */,
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */,
8720890C2CE65451008C2C17 /* qmqtttype.h */,
8720890D2CE65451008C2C17 /* qmqtttype.cpp */,
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */,
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */,
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */,
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */,
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */,
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */,
87A6825B2CE3AB4000586A2A /* sramAXSController.h */,
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */,
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */,
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */,
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */,
877758B42C98629B00BB1697 /* sportstechelliptical.h */,
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */,
8715A3E82C75E6DB009BAC05 /* antbike.cpp */,
8715A3E92C75E6DB009BAC05 /* antbike.h */,
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */,
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */,
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */,
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */,
873D3C4B2C296B3700770CB9 /* jumprope.cpp */,
873D3C4C2C296B3700770CB9 /* jumprope.h */,
873D3C492C296B0100770CB9 /* moc_jumprope.cpp */,
87C4E5BA2C1C1D0900D0750E /* crossrope.cpp */,
87C4E5B92C1C1D0900D0750E /* crossrope.h */,
874823FF2B935AE6006F3CA1 /* ergtable.h */,
@@ -2452,6 +2651,10 @@
8710707229C4A5E70094D0F3 /* GarminConnect.swift */,
87A2E0202B2B024200E6168F /* swiftDebug.h */,
8730A3912B404159007E336D /* zwift_protobuf_layer.swift */,
87B871922CE1E94D009B06CA /* zwifthubbike.swift */,
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
);
name = Sources;
sourceTree = "<group>";
@@ -3053,7 +3256,6 @@
buildRules = (
);
dependencies = (
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */,
876E4E312594748100BD5714 /* PBXTargetDependency */,
);
name = qdomyoszwift;
@@ -3123,9 +3325,6 @@
DevelopmentTeam = 6335M7T29D;
ProvisioningStyle = Automatic;
};
E9F0AFC024A6F2D65CE84E08 = {
DevelopmentTeam = 6335M7T29D;
};
};
};
buildConfigurationList = DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */;
@@ -3145,7 +3344,6 @@
projectRoot = "";
targets = (
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */,
876E4E102594747F00BD5714 /* watchkit */,
876E4E192594748000BD5714 /* watchkit Extension */,
);
@@ -3196,31 +3394,6 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5E618435888B9D49F8540165 /* Qt Qmake */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "Qt Qmake";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_makeqmake.mak";
showEnvVarsInLog = 0;
};
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "Qt Preprocessors";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_preprocess.mak";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
876E4E162594748000BD5714 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -3242,6 +3415,7 @@
files = (
8738249627E646E3004F1B46 /* characteristicnotifier2acd.cpp in Compile Sources */,
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */,
87C5F0BB26285E5F0067A1B5 /* mimetext.cpp in Compile Sources */,
8732C17F27353464006DF424 /* iconceptbike.cpp in Compile Sources */,
87CF5169293C879800A7CABC /* characteristicwriteprocessore005.cpp in Compile Sources */,
@@ -3268,6 +3442,7 @@
873824BC27E64707004F1B46 /* moc_resolver.cpp in Compile Sources */,
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */,
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */,
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */,
20A50533946A39CBD2C89104 /* bluetoothdevice.cpp in Compile Sources */,
87C5F0D126285E7E0067A1B5 /* moc_stagesbike.cpp in Compile Sources */,
873824E927E647A8004F1B46 /* mdns.cpp in Compile Sources */,
@@ -3317,6 +3492,7 @@
8768C8C92BBC11C80099DBE1 /* adb_client.c in Compile Sources */,
873063C0259DF2C500DA0F44 /* moc_heartratebelt.cpp in Compile Sources */,
DD5ED224478CB82859C61B9F /* fit_buffered_record_mesg_broadcaster.cpp in Compile Sources */,
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */,
87368825259C602800C71C7E /* watchAppStart.swift in Compile Sources */,
87BCE6BF29F28F95001F70EB /* moc_ypooelliptical.cpp in Compile Sources */,
876ED21925C3E9000065F3DC /* moc_ftmsbike.cpp in Compile Sources */,
@@ -3329,6 +3505,7 @@
E7F190E59DC975BA4CA65F0C /* fit_crc.cpp in Compile Sources */,
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */,
DA1DC0B761BD7A3004BCF43D /* fit_date_time.cpp in Compile Sources */,
873D3C4D2C296B3800770CB9 /* jumprope.cpp in Compile Sources */,
87E1CA7D2B8DF04900E3C414 /* moc_trxappgateusbelliptical.cpp in Compile Sources */,
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */,
87DAE16526E9FF3A00B0527E /* solef80treadmill.cpp in Compile Sources */,
@@ -3338,6 +3515,7 @@
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */,
8730A3922B404159007E336D /* zwift_protobuf_layer.swift in Compile Sources */,
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */,
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */,
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
DF1FD9718B75FA591A7E3D80 /* fit_decode.cpp in Compile Sources */,
@@ -3370,11 +3548,13 @@
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */,
A4BD6DF51CFFF867B7B5AED4 /* fit_developer_field_definition.cpp in Compile Sources */,
87EB918B27EE5FE7002535E1 /* moc_inappproductqmltype.cpp in Compile Sources */,
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */,
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */,
3015F9B9FF4CA6D653D46CCA /* fit_developer_field_description.cpp in Compile Sources */,
87310B22266FBB78008BA0D6 /* moc_homefitnessbuddy.cpp in Compile Sources */,
87958F1B27628D5400124B24 /* moc_elitesterzosmart.cpp in Compile Sources */,
8768C8D82BBC12890099DBE1 /* centraldir.c in Compile Sources */,
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */,
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
@@ -3395,9 +3575,11 @@
87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */,
87F4FB5C29D550E00061BB4A /* moc_schwinn170bike.cpp in Compile Sources */,
87C5F0C226285E5F0067A1B5 /* emailaddress.cpp in Compile Sources */,
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */,
873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */,
25F2400F80DAFBD41FE5CC75 /* fit_field.cpp in Compile Sources */,
873824E227E647A8004F1B46 /* dns.cpp in Compile Sources */,
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */,
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */,
8768C9092BBC12B80099DBE1 /* socket_local_server.c in Compile Sources */,
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */,
@@ -3416,16 +3598,33 @@
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */,
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */,
87C424262BC1294000503687 /* moc_treadmillErgTable.cpp in Compile Sources */,
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */,
873824ED27E647A9004F1B46 /* resolver.cpp in Compile Sources */,
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */,
878531652711A3E1004B153D /* activiotreadmill.cpp in Compile Sources */,
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */,
48BA9CE9D6F256A15E8FB25D /* fit_mesg.cpp in Compile Sources */,
DD2E0091F3318F053D2995AA /* fit_mesg_broadcaster.cpp in Compile Sources */,
FE77C778768741F1A161682E /* fit_mesg_definition.cpp in Compile Sources */,
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */,
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */,
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */,
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */,
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */,
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */,
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */,
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */,
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */,
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */,
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */,
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */,
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */,
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */,
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */,
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */,
87BCE6BD29F28F72001F70EB /* ypooelliptical.cpp in Compile Sources */,
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */,
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */,
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */,
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
@@ -3433,6 +3632,7 @@
6DC5D7C695B8763F9E2E029F /* fit_profile.cpp in Compile Sources */,
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */,
87C5F0B726285E5F0067A1B5 /* mimecontentformatter.cpp in Compile Sources */,
873D3C4A2C296B0100770CB9 /* moc_jumprope.cpp in Compile Sources */,
23191C28CB29474279752FD3 /* fit_protocol_validator.cpp in Compile Sources */,
275D55B5D956B2E5F1B7E46E /* fit_unicode.cpp in Compile Sources */,
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */,
@@ -3451,6 +3651,7 @@
7CF08714869DA569C2EA551C /* keepawakehelper.cpp in Compile Sources */,
87EB918727EE5FE7002535E1 /* moc_nautilusbike.cpp in Compile Sources */,
873824B827E64707004F1B46 /* moc_cache.cpp in Compile Sources */,
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */,
873824E427E647A8004F1B46 /* cache.cpp in Compile Sources */,
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */,
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */,
@@ -3484,6 +3685,7 @@
87C5F0C026285E5F0067A1B5 /* mimepart.cpp in Compile Sources */,
47E45EE0BB22C1E4332F1D1D /* trxappgateusbtreadmill.cpp in Compile Sources */,
873824BB27E64707004F1B46 /* moc_prober_p.cpp in Compile Sources */,
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */,
8742C2B227C92C30007D3FA0 /* wahookickrsnapbike.cpp in Compile Sources */,
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */,
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */,
@@ -3502,6 +3704,7 @@
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */,
8742C2B427C92C48007D3FA0 /* moc_wahookickrsnapbike.cpp in Compile Sources */,
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */,
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */,
873824B027E64706004F1B46 /* moc_cache_p.cpp in Compile Sources */,
87EB918627EE5FE7002535E1 /* moc_inapppurchasebackend.cpp in Compile Sources */,
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */,
@@ -3512,6 +3715,12 @@
87A0C4BB262329A600121A76 /* npecablebike.cpp in Compile Sources */,
873CD22D27EF8E4B000131BC /* iosinapppurchaseproduct.mm in Compile Sources */,
873CD20727EF8D8A000131BC /* inappproduct.cpp in Compile Sources */,
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */,
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */,
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */,
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */,
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */,
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */,
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */,
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */,
872DCC392A18D4A800EC9F68 /* virtualdevice.cpp in Compile Sources */,
@@ -3536,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 */,
@@ -3548,6 +3758,7 @@
8783153C25E8DAFD0007817C /* sportstechbike.cpp in Compile Sources */,
873824E527E647A8004F1B46 /* message.cpp in Compile Sources */,
210F6A0A7E2FA7CDD3CA0084 /* qdomyoszwift_qml_plugin_import.cpp in Compile Sources */,
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */,
87062644259480A600D06586 /* APIFetcher.swift in Compile Sources */,
8768C8C72BBC11C80099DBE1 /* adb_auth_host.c in Compile Sources */,
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */,
@@ -3565,6 +3776,7 @@
8768C9062BBC12B80099DBE1 /* socket_local_client.c in Compile Sources */,
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */,
C3D1FD2587BF6F15B58BA675 /* moc_bluetooth.cpp in Compile Sources */,
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */,
87062648259480B700D06586 /* WorkoutTracking.swift in Compile Sources */,
8C3422A825EF7ECD78951307 /* moc_bluetoothdevice.cpp in Compile Sources */,
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */,
@@ -3593,9 +3805,11 @@
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
87C5F0D726285E7E0067A1B5 /* moc_mimefile.cpp in Compile Sources */,
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */,
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */,
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 */,
@@ -3608,6 +3822,7 @@
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */,
873824B227E64706004F1B46 /* moc_hostname.cpp in Compile Sources */,
873824EB27E647A8004F1B46 /* prober.cpp in Compile Sources */,
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */,
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */,
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */,
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */,
@@ -3620,10 +3835,13 @@
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */,
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */,
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */,
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */,
8738248127E646C1004F1B46 /* characteristicnotifier2ad2.cpp in Compile Sources */,
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */,
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */,
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
@@ -3662,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 */,
@@ -3669,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 */,
);
@@ -3688,11 +3910,6 @@
target = 876E4E102594747F00BD5714 /* watchkit */;
targetProxy = 876E4E302594748100BD5714 /* PBXContainerItemProxy */;
};
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */;
targetProxy = 876E4E0E2594739400BD5714 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -4016,7 +4233,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4049,6 +4266,9 @@
../../Qt/5.15.2/ios/include/QtMultimedia,
../src/devices,
../src/ios/adb/include,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4095,7 +4315,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
OTHER_CFLAGS = (
"-pipe",
"-g",
@@ -4207,7 +4427,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4242,6 +4462,9 @@
../../Qt/5.15.2/ios/include/QtMultimedia,
../src/devices,
../src/ios/adb/include,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4288,7 +4511,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-pipe",
@@ -4434,7 +4657,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4459,7 +4682,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4530,7 +4753,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4551,7 +4774,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4622,7 +4845,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4667,7 +4890,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4736,7 +4959,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 804;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4777,7 +5000,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.16;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";

View File

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

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

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

View File

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

View File

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

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

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

View File

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

View File

@@ -10,7 +10,7 @@ These instructions build the app itself, not the test project.
```buildoutcfg
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
$ cd qdomyos-zwift
$ git submodule update --init src/smtpclient/
@@ -106,7 +106,7 @@ This operation takes a moment to complete.
#### Install qdomyos-zwift from sources
```bash
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
git clone https://github.com/cagnulein/qdomyos-zwift.git
cd qdomyos-zwift
git submodule update --init src/smtpclient/
@@ -117,6 +117,11 @@ qmake qdomyos-zwift.pro
make
```
If you need GUI also do a
```
apt install qml-module*
```
Please note :
- Don't build the application with `-j4` option (this will fail)
- Build operation is circa 45 minutes (subsequent builds are faster)
@@ -172,6 +177,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

@@ -8,23 +8,21 @@ The testing project tst/qdomyos-zwift-tests.pro contains test code that uses the
New devices are added to the main QZ application by creating or modifying a subclass of the bluetoothdevice class.
At minimum, each device has a corresponding BluetoothDeviceTestData subclass in the test project, which is coded to provide information to the test framework to generate tests for device detection and potentially other things.
At minimum, each device has a corresponding BluetoothDeviceTestData object constructed in the DeviceTestDataIndex class in the test project, which is coded to provide information to the test framework to generate tests for device detection and potentially other things.
In the test project
* create a new folder for the device under tst/Devices. This is for anything you define for testing this device.
* add a new class with header file and optionally .cpp file to the project in that folder. Name the class DeviceNameTestData, substituting an appropriate name in place of "DeviceName".
* edit the header file to inherit the class from the BluetoothDeviceTestData abstract subclass appropriate to the device type, i.e. BikeTestData, RowerTestData, EllipticalTestData, TreadmillTestData.
* have this new subclass' constructor pass a unique test name to its superclass.
* add a new device name constant to the DeviceIndex class.
* locate the implementation of DeviceTestDataindex::Initialize and build the test data from a call to DeviceTestDataIndex::RegisterNewDeviceTestData(...)
* pass the device name constant defined in the DeviceIndex class to the call to DeviceTestDataIndex::RegisterNewDeviceTestData(...).
The tests are not organised around real devices that are handled, but the bluetoothdevice subclass that handles them - the "driver" of sorts.
You need to provide the following:
- patterns for valid names (e.g. equals a value, starts with a value, case sensitivity, specific length)
- invalid names to ensure the device is not identified when the name is invalid
- configuration settings that are required for the device to be detected
- configuration settings that are required for the device to be detected, including bluetooth device information configuration
- invalid configurations to test that the device is not detected, e.g. when it's disabled in the settings, but the name is correct
- exclusion devices: if a device with the same name but of a higher priority type is detected, this device should not be detected
- valid and invalid QBluetoothDeviceInfo configurations, e.g. to check the device is only detected when the manufacturer data is set correctly, or certain services are available or not.
- exclusion devices: for example if a device with the same name but of a higher priority type is detected, this device should not be detected
## Tools in the Test Framework
@@ -39,16 +37,18 @@ i.e. a test will
### DeviceDiscoveryInfo
This class contains a set of fields that store strongly typed QSettings values.
It also provides methods to read and write the values it knows about from and to a QSettings object.
This class:
* stores values for a specific subset of the QZSettings keys.
* provides methods to read and write the values it knows about from and to a QSettings object.
* provides a QBluetoothDeviceInfo object configured with the device name currently being tested.
It is used in conjunction with a TestSettings object to write a configuration during a test.
## Writing a device detection test
Because of the way the TestData classes currently work, it may be necessary to define multiple test data classes to cover the various cases.
For example, if any of a list of names is enough to identify a device, or another group of names but with a certain service in the bluetooth device info, that will require multiple classes.
Because of the way the BluetoothDeviceTestDataBuilder currently works, it may be necessary to define multiple test data objects to cover the various cases.
For example, if any of a list of names is enough to identify a device, or another group of names but with a certain service in the bluetooth device info, that will require multiple test data objects.
### Recognition by Name
@@ -68,133 +68,83 @@ Reading this, to identify this device:
In this case, we are not testing the last two, but can test the first two.
In deviceindex.h:
```
#pragma once
#include "Devices/Bike/biketestdata.h"
#include "devices/domyosbike/domyosbike.h"
class DomyosBikeTestData : public BikeTestData {
public:
DomyosBikeTestData() : BikeTestData("Domyos Bike") {
this->addDeviceName("Domyos-Bike", comparison::StartsWith);
this->addInvalidDeviceName("DomyosBridge", comparison::StartsWith);
}
// not used yet
deviceType get_expectedDeviceType() const override { return deviceType::DomyosBike; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<domyosbike*>(detectedDevice)!=nullptr;
}
};
static const QString DomyosBike;
```
The constructor adds a valid device name, and an invalid one. Various overloads of these methods and other members of the comparison enumeration provide other capabilities for specifying test data. If you add a valid device name that says the name should start with a value, additional names will be added automatically to the valid list with additional characters to test that it is in fact a "starts with" relationship. Also, valid and invalid names will be generated base on whether the comparison is case sensitive or not.
In deviceindex.cpp:
The get_expectedDeviceType() function is not actually used and is part of an unfinished refactoring of the device detection code, whereby the bluetoothdevice object doesn't actually get created intially. You could add a new value to the deviceType enum and return that, but it's not used yet. There's always deviceType::None.
```
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
```
The get_isExpectedDevice(bluetoothdevice *) function must be overridden to indicate if the specified object is of the type expected for this test data.
This pair adds the "friendly name" for the device as a constant, and also adds the key/value pair to an index.
In DeviceTestDataIndex::Initialize():
```
// Domyos bike
RegisterNewDeviceTestData(DeviceIndex::DomyosBike)
->expectDevice<domyosbike>()
->acceptDeviceName("Domyos-Bike", DeviceNameComparison::StartsWith)
->rejectDeviceName("DomyosBridge", DeviceNameComparison::StartsWith);
```
This set of instructions adds a valid device name, and an invalid one. Various overloads of these methods, other methods, and other members of the comparison enumeration provide other capabilities for specifying test data. If you add a valid device name that says the name should start with a value, additional names will be added automatically to the valid list with additional characters to test that it is in fact a "starts with" relationship. Also, valid and invalid names will be generated based on whether the comparison is case sensitive or not.
### Configuration Settings
Consider the CompuTrainerTestData. This device is not detected by name, but only by whether or not it is enabled in the settings.
To specify this in the test data, we override one of the configureSettings methods, the one for the simple case where there is a single valid and a single invalid configuration.
Consider the CompuTrainer bike. This device is not detected by name, but only by whether or not it is enabled in the settings.
To specify this in the test data, we use one of the BluetoothDeviceTestData::configureSettingsWith(...) methods, the one for the simple case where there is a single QZSetting with a specific enabling and disabling value.
Settings from QSettings that contribute to tests should be put into the DeviceDiscoveryInfo class.
For example, for the Computrainer Bike, the "computrainer_serial_port" value from the QSettings determines if the bike should be detected or not.
For example, for the Computrainer Bike, the "computrainer_serialport" value from the QSettings determines if the bike should be detected or not.
The computrainer_serialport QZSettings key should be registered in devicediscoveryinfo.cpp
In devicediscoveryinfo.cpp:
```
class DeviceDiscoveryInfo {
public :
...
QString computrainer_serial_port = nullptr;
...
}
```
void InitializeTrackedSettings() {
The getValues and setValues methods should be updated to include the addition(s):
```
void DeviceDiscoveryInfo::setValues(QSettings &settings, bool clear) const {
if(clear) settings.clear();
...
settings.setValue(QZSettings::computrainer_serialport, this->computrainer_serial_port);
...
}
void DeviceDiscoveryInfo::getValues(QSettings &settings){
...
this->computrainer_serial_port = settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
trackedSettings.insert(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport);
...
}
```
In the following example, the DeviceDiscoveryInfo class has been updated to contain the device's configuration setting (computrainer_serial_port).
- if an enabling configuration is requested (enable==true) a string that is known to be accepted is supplied
- if a disabling configuration is requested (enable==false) an empty string is supplied.
For this test data,
* if enabling configurations are requested, the computrainer_serialport setting will be populated with "COMX"
* if disabling configurations are requested, the computrainer_serialport setting will be populated with ""
This example uses the simpler of 2 configureSettings methods returns true/false to indicate if the configuration should be used for the test.
DeviceTestDataIndex::Initialize():
```
#pragma once
#include "Devices/Bike/biketestdata.h"
#include "devices/computrainerbike/computrainerbike.h"
class CompuTrainerTestData : public BikeTestData {
protected:
bool configureSettings(DeviceDiscoveryInfo& info, bool enable) const override {
info.computrainer_serial_port = enable ? "X":QString();
return true;
}
public:
CompuTrainerTestData() : BikeTestData("CompuTrainer Bike") {
// any name
this->addDeviceName("", comparison::StartsWithIgnoreCase);
}
deviceType get_expectedDeviceType() const override { return deviceType::CompuTrainerBike; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<computrainerbike*>(detectedDevice)!=nullptr;
}
};
// Computrainer Bike
RegisterNewDeviceTestData(DeviceIndex::ComputrainerBike)
->expectDevice<computrainerbike>()
->acceptDeviceName("", DeviceNameComparison::StartsWithIgnoreCase)
->configureSettingsWith(QZSettings::computrainer_serialport, "COMX", "");
```
Similarly, the Pafers Bike has a simple configuration setting:
```
#include "Devices/Bike/biketestdata.h"
#include "devices/pafersbike/pafersbike.h"
class PafersBikeTestData : public BikeTestData {
protected:
bool configureSettings(DeviceDiscoveryInfo& info, bool enable) const override {
// the treadmill is given priority
info.pafers_treadmill = !enable;
return true;
}
public:
PafersBikeTestData() : BikeTestData("Pafers Bike") {
this->addDeviceName("PAFERS_", comparison::StartsWithIgnoreCase);
}
deviceType get_expectedDeviceType() const override { return deviceType::PafersBike; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<pafersbike*>(detectedDevice)!=nullptr;
}
};
// Pafers Bike
RegisterNewDeviceTestData(DeviceIndex::PafersBike)
->expectDevice<pafersbike>()
->acceptDeviceName("PAFERS_", DeviceNameComparison::StartsWithIgnoreCase)
->configureSettingsWith(QZSettings::pafers_treadmill,false);
```
In that case, ```configureSettingsWith(QZSettings::pafers_treadmill,false)``` indicates that the pafers_treadmill setting will be false for enabling configurations and true for disabling ones.
A more complicated example is the Pafers Treadmill. It involves a name match, but also some configuration settings obtained earlier...
```
@@ -212,76 +162,60 @@ bool pafers_treadmill_bh_iboxster_plus =
```
Here the device could be activated due to a name match and various combinations of settings.
For this, the configureSettings function that takes a vector of DeviceDiscoveryInfo objects which is populated with configurations that lead to the specified result (enable = detected, !enable=not detected). Instead of returning a boolean to indicate if a configuration has been supplied, it populates a vector of DeviceDiscoveryInfo objects.
For this, the configureSettingsWith(...) function that takes a lambda function which consumes a vector of DeviceDiscoveryInfo objects which is populated with configurations that lead to the specified result (enable = detected, !enable=not detected).
```
#pragma once
// Pafers Treadmill
RegisterNewDeviceTestData(DeviceIndex::PafersTreadmill)
->expectDevice<paferstreadmill>()
->acceptDeviceName("PAFERS_", DeviceNameComparison::StartsWithIgnoreCase)
->configureSettingsWith( [](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void {
DeviceDiscoveryInfo config(info);
#include "Devices/Treadmill/treadmilltestdata.h"
#include "devices/paferstreadmill/paferstreadmill.h"
class PafersTreadmillTestData : public TreadmillTestData {
protected:
void configureSettings(const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations) const override {
DeviceDiscoveryInfo config(info);
if (enable) {
for(int x = 1; x<=3; x++) {
config.pafers_treadmill = x & 1;
config.pafers_treadmill_bh_iboxster_plus = x & 2;
configurations.push_back(config);
}
} else {
config.pafers_treadmill = false;
config.pafers_treadmill_bh_iboxster_plus = false;
configurations.push_back(config);
}
}
public:
PafersTreadmillTestData() : TreadmillTestData("Pafers Treadmill") {
this->addDeviceName("PAFERS_", comparison::StartsWithIgnoreCase);
}
deviceType get_expectedDeviceType() const override { return deviceType::PafersTreadmill; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<paferstreadmill*>(detectedDevice)!=nullptr;
}
};
if (enable) {
for(int x = 1; x<=3; x++) {
config.setValue(QZSettings::pafers_treadmill, x & 1);
config.setValue(QZSettings::pafers_treadmill_bh_iboxster_plus, x & 2);
configurations.push_back(config);
}
} else {
config.setValue(QZSettings::pafers_treadmill, false);
config.setValue(QZSettings::pafers_treadmill_bh_iboxster_plus, false);
configurations.push_back(config);
}
});
```
### Considering Extra QBluetoothDeviceInfo Content
Detection of some devices requires some specific bluetooth device information.
Supplying enabling and disabling QBluetoothDeviceInfo objects is done using a similar pattern to the multiple configurations scenario.
For example, the M3iBike requires specific manufacturer information.
Supplying enabling and disabling QBluetoothDeviceInfo objects is done by accessing the QBluetoothDeviceInfo member of the DeviceDiscoveryInfo object.
For example, the M3iBike requires specific manufacturer information, using the simpler of the lambda functions accepted by the configureSettingsWith function.
```
void M3IBikeTestData::configureBluetoothDeviceInfos(const QBluetoothDeviceInfo& info, bool enable, std::vector<QBluetoothDeviceInfo>& bluetoothDeviceInfos) const {
// The M3I bike detector looks into the manufacturer data.
// M3I Bike
RegisterNewDeviceTestData(DeviceIndex::M3IBike)
->expectDevice<m3ibike>()
->acceptDeviceName("M3", DeviceNameComparison::StartsWith)
->configureSettingsWith(
[](DeviceDiscoveryInfo& info, bool enable)->void
{
// The M3I bike detector looks into the manufacturer data.
if(!enable) {
info.DeviceInfo()->setManufacturerData(1, QByteArray("Invalid manufacturer data."));
return;
}
QBluetoothDeviceInfo result = info;
if(!enable) {
result.setManufacturerData(1, QByteArray("Invalid manufacturer data."));
bluetoothDeviceInfos.push_back(result);
return;
}
int key=0;
result.setManufacturerData(key++, hex2bytes("02010639009F00000000000000000014008001"));
bluetoothDeviceInfos.push_back(result);
}
int key=0;
info.DeviceInfo()->setManufacturerData(key++, hex2bytes("02010639009F00000000000000000014008001"));
});
```
The test framework populates the incoming QBluetoothDeviceInfo object with a name and a UUID. This is expected to have nothing else defined.
Another example is one of the test data classes for detecting a device that uses the statesbike class:
The test framework populates the incoming QBluetoothDeviceInfo object with a UUID and the name (generated from the acceptDeviceName and rejectDeviceName calls) currently being tested.
This is expected to have nothing else defined.
Another example is one of the test data definitions for detecting a device that uses the stagesbike class:
Detection code from bluetooth.cpp:
@@ -289,37 +223,49 @@ Detection code from bluetooth.cpp:
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
```
This condition is actually extracted from a more complicated example where the current test data classes can't cover all the detection criteria in one implementation. This is why this class inherits from StagesBikeTestData rather than BikeTestData directly.
This condition is actually extracted from a more complicated example where the BluetoothDeviceTestData class can't cover all the detection criteria with one instance.
```
class StagesBike3TestData : public StagesBikeTestData {
protected:
void configureBluetoothDeviceInfos(const QBluetoothDeviceInfo& info, bool enable, std::vector<QBluetoothDeviceInfo>& bluetoothDeviceInfos) const override {
// The condition, if the name is acceptable, is:
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
// Stages Bike General
auto stagesBikeExclusions = { GetTypeId<ftmsbike>() };
if(enable) {
QBluetoothDeviceInfo result = info;
result.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1818)}));
bluetoothDeviceInfos.push_back(result);
} else {
QBluetoothDeviceInfo hasInvalid = info;
hasInvalid.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1826)}));
QBluetoothDeviceInfo hasBoth = hasInvalid;
hasBoth.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1818),QBluetoothUuid((quint16)0x1826)}));
//
// ... other stages bike variants
//
bluetoothDeviceInfos.push_back(info); // has neither
bluetoothDeviceInfos.push_back(hasInvalid);
bluetoothDeviceInfos.push_back(hasBoth);
}
}
// Stages Bike (KICKR CORE)
RegisterNewDeviceTestData(DeviceIndex::StagesBike_KICKRCORE)
->expectDevice<stagesbike>()
->acceptDeviceName("KICKR CORE", DeviceNameComparison::StartsWithIgnoreCase)
->excluding(stagesBikeExclusions)
->configureSettingsWith(
[](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void
{
// The condition, if the name is acceptable, is:
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
public:
StagesBike3TestData() : StagesBikeTestData("Stages Bike (KICKR CORE)") {
if(enable) {
DeviceDiscoveryInfo result = info;
result.addBluetoothService(QBluetoothUuid((quint16)0x1818));
result.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
configurations.push_back(result);
} else {
DeviceDiscoveryInfo hasNeither = info;
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1818));
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
DeviceDiscoveryInfo hasInvalid = info;
hasInvalid.addBluetoothService(QBluetoothUuid((quint16)0x1826));
DeviceDiscoveryInfo hasBoth = hasInvalid;
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1818));
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1826));
configurations.push_back(info); // has neither
configurations.push_back(hasInvalid);
configurations.push_back(hasBoth);
}
});
this->addDeviceName("KICKR CORE", comparison::StartsWithIgnoreCase);
}
};
```
In this case, it populates the vector with the single enabling configuration if that's what's been requested, otherwise 3 disabling ones.
@@ -328,7 +274,7 @@ In this case, it populates the vector with the single enabling configuration if
Sometimes there might be ambiguity when multiple devices are available, and the detection code may specify that if the other conditions match, but certain specific kinds of devices (the exclusion devices) have already been detected, the newly matched device should be ignored.
The TestData class can be made to cover this by overriding the configureExclusions() method to add instances of the TestData classes for the exclusion devices to the object's internal list of exclusions.
The test data object can be made to cover this by calling the excluding(...) functions to add type identifiers for the bluetoothdevice classes for the exclusion devices to the object's internal list of exclusions.
Detection code:
@@ -336,39 +282,19 @@ Detection code:
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride &&
!echelonConnectSport && filter) {
```
The configureExclusions code is overridden to specify the exclusion test data objects. Note that the test for a previously detected device of the same type is not included.
The excluding<T>() template function is called to specify the exclusion device type. Note that the test for a previously detected device of the same type is not included.
```
#pragma once
#include "Devices/Bike/biketestdata.h"
#include "Devices/EchelonRower/echelonrowertestdata.h"
#include "Devices/EchelonStrideTreadmill/echelonstridetreadmilltestdata.h"
#include "devices/echelonconnectsport/echelonconnectsport.h"
class EchelonConnectSportBikeTestData : public BikeTestData {
public:
EchelonConnectSportBikeTestData() : BikeTestData("Echelon Connect Sport Bike") {
this->addDeviceName("ECH", comparison::StartsWith);
}
void configureExclusions() override {
this->exclude(new EchelonRowerTestData());
this->exclude(new EchelonStrideTreadmillTestData());
}
deviceType get_expectedDeviceType() const override { return deviceType::EchelonConnectSport; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<echelonconnectsport*>(detectedDevice)!=nullptr;
}
};
// Echelon Connect Sport Bike
RegisterNewDeviceTestData(DeviceIndex::EchelonConnectSportBike)
->expectDevice<echelonconnectsport>()
->acceptDeviceName("ECH", DeviceNameComparison::StartsWith)
->excluding<echelonrower>()
->excluding<echelonstride>();
```
### When a single TestData Class Can't Cover all the Conditions
### When a single test data object can't cover all the conditions
Detection code:
@@ -390,116 +316,81 @@ This presents 3 scenarios for the current test framework.
2. Match the name "KICKR CORE", presence and absence of specific service ids
3. Match the name "ASSIOMA" and the power sensor name setting starts with "Disabled"
The framework is not currently capable of specifying all these scenarios in a single class.
The generated test data is approximately the combinations of these lists: names * settings * bluetoothdeviceInfo * exclusions.
If a combination should not exist, a separate class should be used.
The framework is not currently capable of specifying all these scenarios in a single test data object, without checking the name of the supplied QBluetoothDeviceInfo object against name conditions specified and constructing extra configurations based on that.
The generated test data is approximately the combinations of these lists: names * settings * exclusions.
If a combination should not exist, separate test data objects should be used.
In the example of the StagesBikeTestData classes, the exclusions, which apply to all situations, are implemented in the superclass StagesBikeTestData,
In the example of the Stages Bike test data, the exclusions, which apply to all situations, are implemented in an array of type ids:
```
#pragma once
#include "Devices/Bike/biketestdata.h"
#include "devices/stagesbike/stagesbike.h"
#include "Devices/FTMSBike/ftmsbiketestdata.h"
class StagesBikeTestData : public BikeTestData {
protected:
StagesBikeTestData(std::string testName): BikeTestData(testName) {
}
void configureExclusions() override {
this->exclude(new FTMSBike1TestData());
this->exclude(new FTMSBike2TestData());
}
public:
deviceType get_expectedDeviceType() const override { return deviceType::StagesBike; }
bool get_isExpectedDevice(bluetoothdevice * detectedDevice) const override {
return dynamic_cast<stagesbike*>(detectedDevice)!=nullptr;
}
};
// Stages Bike General
auto stagesBikeExclusions = { GetTypeId<ftmsbike>() };
```
The name-match only in one subclass:
The name-match only in one test data instance:
```
class StagesBike1TestData : public StagesBikeTestData {
public:
StagesBike1TestData() : StagesBikeTestData("Stages Bike") {
this->addDeviceName("STAGES ", comparison::StartsWithIgnoreCase);
this->addDeviceName("TACX SATORI", comparison::StartsWithIgnoreCase);
}
};
// Stages Bike
RegisterNewDeviceTestData(DeviceIndex::StagesBike)
->expectDevice<stagesbike>()
->acceptDeviceNames({"STAGES ", "TACX SATORI"}, DeviceNameComparison::StartsWithIgnoreCase)
->acceptDeviceName("QD", DeviceNameComparison::IgnoreCase)
->excluding(stagesBikeExclusions);
```
The name and setting match in another subclass:
The name and setting match in another instance:
```
class StagesBike2TestData : public StagesBikeTestData {
protected:
bool configureSettings(DeviceDiscoveryInfo& info, bool enable) const override {
info.powerSensorName = enable ? "Disabled":"Roberto";
return true;
}
public:
StagesBike2TestData() : StagesBikeTestData("Stages Bike (Assioma / Power Sensor disabled") {
this->addDeviceName("ASSIOMA", comparison::StartsWithIgnoreCase);
}
};
// Stages Bike Stages Bike (Assioma / Power Sensor disabled
RegisterNewDeviceTestData(DeviceIndex::StagesBike_Assioma_PowerSensorDisabled)
->expectDevice<stagesbike>()
->acceptDeviceName("ASSIOMA", DeviceNameComparison::StartsWithIgnoreCase)
->configureSettingsWith(QZSettings::power_sensor_name, "DisabledX", "XDisabled")
->excluding( stagesBikeExclusions);
```
The name and bluetooth device info configurations in another:
```
// Stages Bike (KICKR CORE)
RegisterNewDeviceTestData(DeviceIndex::StagesBike_KICKRCORE)
->expectDevice<stagesbike>()
->acceptDeviceName("KICKR CORE", DeviceNameComparison::StartsWithIgnoreCase)
->excluding(stagesBikeExclusions)
->configureSettingsWith(
[](const DeviceDiscoveryInfo& info, bool enable, std::vector<DeviceDiscoveryInfo>& configurations)->void
{
// The condition, if the name is acceptable, is:
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
class StagesBike3TestData : public StagesBikeTestData {
protected:
void configureBluetoothDeviceInfos(const QBluetoothDeviceInfo& info, bool enable, std::vector<QBluetoothDeviceInfo>& bluetoothDeviceInfos) const override {
// The condition, if the name is acceptable, is:
// !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818)))
if(enable) {
DeviceDiscoveryInfo result = info;
result.addBluetoothService(QBluetoothUuid((quint16)0x1818));
result.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
configurations.push_back(result);
} else {
DeviceDiscoveryInfo hasNeither = info;
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1818));
hasNeither.removeBluetoothService(QBluetoothUuid((quint16)0x1826));
if(enable) {
QBluetoothDeviceInfo result = info;
result.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1818)}));
bluetoothDeviceInfos.push_back(result);
} else {
QBluetoothDeviceInfo hasInvalid = info;
hasInvalid.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1826)}));
QBluetoothDeviceInfo hasBoth = hasInvalid;
hasBoth.setServiceUuids(QVector<QBluetoothUuid>({QBluetoothUuid((quint16)0x1818),QBluetoothUuid((quint16)0x1826)}));
DeviceDiscoveryInfo hasInvalid = info;
hasInvalid.addBluetoothService(QBluetoothUuid((quint16)0x1826));
DeviceDiscoveryInfo hasBoth = hasInvalid;
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1818));
hasBoth.addBluetoothService(QBluetoothUuid((quint16)0x1826));
bluetoothDeviceInfos.push_back(info); // has neither
bluetoothDeviceInfos.push_back(hasInvalid);
bluetoothDeviceInfos.push_back(hasBoth);
}
}
public:
StagesBike3TestData() : StagesBikeTestData("Stages Bike (KICKR CORE)") {
this->addDeviceName("KICKR CORE", comparison::StartsWithIgnoreCase);
}
};
configurations.push_back(info); // has neither
configurations.push_back(hasInvalid);
configurations.push_back(hasBoth);
}
});
```
## Telling Google Test Where to Look
To register your test data class(es) with Google Test:
- open tst/Devices/devices.h
- add a #include for your new header file(s)
- add your new classes to the BluetoothDeviceTestDataTypes list.
This will add tests for your new device class to test runs of the tests in the BluetoothDeviceTestSuite class, which are about detecting, and not detecting devices in circumstances generated from the TestData classes.
The BluetoothDeviceTestSuite configuration specifies that the test data will be obtained from the DeviceTestDataIndex class, so there's nothing more to do.

143
helpers/winbt.py Normal file
View File

@@ -0,0 +1,143 @@
import sys
import logging
import asyncio
import threading
import random
import struct
import binascii
from typing import Any, Union
from bless import (
BlessServer,
BlessGATTCharacteristic,
GATTCharacteristicProperties,
GATTAttributePermissions,
)
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(name=__name__)
trigger: Union[asyncio.Event, threading.Event]
if sys.platform in ["darwin", "win32"]:
trigger = threading.Event()
else:
trigger = asyncio.Event()
def read_request(characteristic: BlessGATTCharacteristic, **kwargs) -> bytearray:
logger.debug(f"Reading {characteristic.value}")
return characteristic.value
def write_request(characteristic: BlessGATTCharacteristic, value: Any, **kwargs):
characteristic.value = value
logger.debug(f"Char value set to {characteristic.value}")
if characteristic.value == b"\x0f":
logger.debug("NICE")
trigger.set()
def generate_indoor_bike_data():
# Flags (16 bits)
flags = (1 << 2) | (1 << 6) # Instantaneous Cadence and Instantaneous Power present
speed = random.randint(0, 20000) # 0-20000
# Instantaneous Cadence (uint16, 0.5 rpm resolution)
cadence = random.randint(0, 400) # 0-200 rpm
# Instantaneous Power (sint16, watts)
power = random.randint(10, 50)
# Pack data into bytes
data = struct.pack("<HHHh", flags, speed, cadence, power)
return data
def generate_zwift_ride_data():
data_str = "2308ffbfffff0f1a04080010001a04080110001a04080210001a0408031000"
data = binascii.unhexlify(data_str)
return data
async def update_indoor_bike_data(server, service_uuid, char_uuid):
while True:
c = server.get_characteristic(char_uuid)
c.value = bytes(generate_indoor_bike_data())
server.update_value(service_uuid, char_uuid)
await asyncio.sleep(1)
async def update_zwift_ride_data(server, service_uuid, char_uuid):
while True:
c = server.get_characteristic(char_uuid)
c.value = bytes(generate_zwift_ride_data())
server.update_value(service_uuid, char_uuid)
await asyncio.sleep(1)
async def run(loop):
trigger.clear()
# Instantiate the server
server = BlessServer(name="FTMS Indoor Bike", loop=loop)
server.read_request_func = read_request
server.write_request_func = write_request
# Add Fitness Machine Service
ftms_uuid = "00001826-0000-1000-8000-00805f9b34fb"
await server.add_new_service(ftms_uuid)
# Add Indoor Bike Data Characteristic
indoor_bike_data_uuid = "00002ad2-0000-1000-8000-00805f9b34fb"
char_flags = (
GATTCharacteristicProperties.read
| GATTCharacteristicProperties.notify
)
permissions = GATTAttributePermissions.readable
await server.add_new_characteristic(
ftms_uuid, indoor_bike_data_uuid, char_flags, generate_indoor_bike_data(), permissions
)
zwift_ride_uuid = "00000001-19ca-4651-86e5-fa29dcdd09d1"
await server.add_new_service(zwift_ride_uuid)
syncRxChar = "00000003-19CA-4651-86E5-FA29DCDD09D1"
syncRx_flags = (
GATTCharacteristicProperties.write
)
syncRx_permissions = GATTAttributePermissions.writeable
syncTxChar = "00000004-19CA-4651-86E5-FA29DCDD09D1"
syncTx_flags = (
GATTCharacteristicProperties.read
| GATTCharacteristicProperties.indicate
)
syncTx_permissions = GATTAttributePermissions.readable
asyncChar = "00000002-19CA-4651-86E5-FA29DCDD09D1"
async_flags = (
GATTCharacteristicProperties.read
| GATTCharacteristicProperties.notify
)
async_permissions = GATTAttributePermissions.readable
await server.add_new_characteristic(
zwift_ride_uuid, syncRxChar, syncRx_flags, generate_indoor_bike_data(), syncRx_permissions
)
await server.add_new_characteristic(
zwift_ride_uuid, syncTxChar, syncTx_flags, generate_indoor_bike_data(), syncTx_permissions
)
await server.add_new_characteristic(
zwift_ride_uuid, asyncChar, async_flags, generate_zwift_ride_data(), async_permissions
)
logger.debug(server.get_characteristic(indoor_bike_data_uuid))
await server.start()
logger.debug("Advertising")
logger.info(f"FTMS Indoor Bike is now advertising")
# Start updating the indoor bike data
update_task = asyncio.create_task(update_indoor_bike_data(server, ftms_uuid, indoor_bike_data_uuid))
update_task_zwift_ride = asyncio.create_task(update_zwift_ride_data(server, zwift_ride_uuid, asyncChar))
await asyncio.sleep(99999999)
await server.stop()
loop = asyncio.get_event_loop()
loop.run_until_complete(run(loop))

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

115
src/EventHandler.h Normal file
View File

@@ -0,0 +1,115 @@
#ifndef EVENTHANDLER_H
#define EVENTHANDLER_H
#include <QDebug>
#include <QSocketNotifier>
#include <QFile>
#ifdef Q_OS_LINUX
#ifndef Q_OS_ANDROID
#include <linux/input.h>
#include "bluetooth.h"
class EventHandler : public QObject
{
Q_OBJECT
public:
EventHandler(const QString& devicePath, QObject* parent = nullptr)
: QObject(parent), m_devicePath(devicePath), m_notifier(nullptr), m_fd(-1) {}
~EventHandler() {
if (m_fd != -1) {
::close(m_fd);
}
}
bool initialize() {
m_fd = ::open(m_devicePath.toStdString().c_str(), O_RDONLY | O_NONBLOCK);
if (m_fd == -1) {
qDebug() << "Failed to open device:" << m_devicePath;
emit error(QString("Failed to open device: %1").arg(m_devicePath));
return false;
}
m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, &EventHandler::handleEvent);
qDebug() << "Device opened successfully:" << m_devicePath;
return true;
}
signals:
void keyPressed(int keyCode);
void error(const QString& errorMessage);
private slots:
void handleEvent() {
input_event ev;
ssize_t bytesRead = ::read(m_fd, &ev, sizeof(ev));
if (bytesRead == sizeof(ev)) {
if (ev.type == EV_KEY && ev.value == 1) { // Key press event
emit keyPressed(ev.code);
}
} else if (bytesRead == 0) {
qDebug() << "End of file reached.";
m_notifier->setEnabled(false);
} else if (bytesRead == -1) {
qDebug() << "Read error:" << strerror(errno);
emit error(QString("Failed to read from device: %1").arg(strerror(errno)));
}
}
private:
QString m_devicePath;
int m_fd;
QSocketNotifier* m_notifier;
};
class BluetoothHandler : public QObject
{
Q_OBJECT
public:
BluetoothHandler(bluetooth* bl, QString eventDevice, QObject* parent = nullptr)
: QObject(parent), m_bluetooth(bl)
{
m_handler = new EventHandler(eventDevice); // Adjust this path as needed
if (!m_handler->initialize()) {
qDebug() << "Failed to initialize EventHandler.";
return;
}
connect(m_handler, &EventHandler::keyPressed, this, &BluetoothHandler::onKeyPressed);
connect(m_handler, &EventHandler::error, this, &BluetoothHandler::onError);
}
~BluetoothHandler() {
delete m_handler;
}
private slots:
void onKeyPressed(int keyCode)
{
qDebug() << "Key pressed:" << keyCode;
if (m_bluetooth && m_bluetooth->device() && m_bluetooth->device()->deviceType() == bluetoothdevice::BIKE) {
if (keyCode == 115) // up
((bike*)m_bluetooth->device())->setGears(((bike*)m_bluetooth->device())->gears() + 1);
else if (keyCode == 114) // down
((bike*)m_bluetooth->device())->setGears(((bike*)m_bluetooth->device())->gears() - 1);
}
}
void onError(const QString& errorMessage)
{
qDebug() << "Error:" << errorMessage;
}
private:
EventHandler* m_handler;
bluetooth* m_bluetooth;
};
#endif // EVENTHANDLER_H
#endif // EVENTHANDLER_H
#endif // EVENTHANDLER_H

View File

@@ -7,7 +7,7 @@ import Qt.labs.settings 1.0
import Qt.labs.platform 1.1
import QtMultimedia 5.15
HomeForm{
HomeForm {
objectName: "home"
background: Rectangle {
anchors.fill: parent
@@ -33,6 +33,7 @@ HomeForm{
property bool theme_tile_shadow_enabled: true
property string theme_tile_shadow_color: "#9C27B0"
property int theme_tile_secondline_textsize: 12
property bool skipLocationServicesDialog: false
}
MessageDialog {
@@ -86,6 +87,49 @@ HomeForm{
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
}
property bool locationServiceRequsted: false
MessageDialog {
id: locationServicesDialog
text: "Permissions Required"
informativeText: "QZ requires both Bluetooth and Location Services to be enabled.\nLocation Services are necessary on Android to allow the app to find Bluetooth devices.\nThe GPS will not be used.\n\nWould you like to enable them?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {
locationServiceRequsted = true
rootItem.enableLocationServices()
}
onNoClicked: remindLocationServicesDialog.visible = true
visible: !rootItem.locationServices() && !locationServiceRequsted && !settings.skipLocationServicesDialog
}
MessageDialog {
id: remindLocationServicesDialog
text: "Reminder Preference"
informativeText: "Would you like to be reminded about enabling Location Services next time?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: settings.skipLocationServicesDialog = false
onNoClicked: settings.skipLocationServicesDialog = true
visible: false
}
MessageDialog {
text: "Restart the app"
informativeText: "To apply the changes, you need to restart the app.\nWould you like to do that now?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: Qt.callLater(Qt.quit)
onNoClicked: this.visible = false;
visible: locationServiceRequsted
}
Timer {
interval: 200; running: true; repeat: false
onTriggered: {
if(rootItem.firstRun()) {
stackView.push("Wizard.qml")
}
}
}
function inner_stop() {
stop_clicked();
rootItem.save_screenshot();
@@ -101,266 +145,298 @@ HomeForm{
}
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
Component.onCompleted: { console.log("completed"); }
Component.onCompleted: {
console.log("home.qml completed");
}
GridView {
GridView {
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: parent
cellWidth: 175 * settings.ui_zoom / 100
cellHeight: 130 * settings.ui_zoom / 100
focus: true
model: appModel
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
id: gridView
objectName: "gridview"
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
Screen.onPrimaryOrientationChanged:{
if(OS_VERSION === "Android")
gridView.leftMargin = (Screen.width % cellWidth) / 2;
else
gridView.leftMargin = (parent.width % cellWidth) / 2;
}
delegate: Item {
id: id1
width: 170 * settings.ui_zoom / 100
height: 125 * settings.ui_zoom / 100
visible: visibleItem
Component.onCompleted: console.log("completed " + objectName)
Behavior on x {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
Behavior on y {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
loops: Animation.Infinite; alwaysRunToEnd: true
}
states: State {
name: "active"; when: loc.currentId === gridId && window.lockTiles
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
}
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
Rectangle {
width: 168 * settings.ui_zoom / 100
height: 123 * settings.ui_zoom / 100
radius: 3
border.width: 1
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
color: settings.theme_tile_background_color
id: rect
}
DropShadow {
visible: settings.theme_tile_shadow_enabled
anchors.fill: rect
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: settings.theme_tile_shadow_color
source: rect
}
Timer {
id: toggleIconTimer
interval: 500; running: true; repeat: true
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
}
Image {
id: myIcon
x: 5
anchors {
bottom: id1.bottom
}
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
source: icon
visible: settings.theme_tile_icon_enabled && !largeButton
}
Text {
objectName: "value"
id: myValue
color: valueFontColor
y: 0
anchors {
horizontalCenter: parent.horizontalCenter
}
text: value
horizontalAlignment: Text.AlignHCenter
font.pointSize: valueFontSize * settings.ui_zoom / 100
font.bold: true
visible: !largeButton
}
Text {
objectName: "secondLine"
id: secondLineText
color: "white"
y: myValue.bottom
anchors {
top: myValue.bottom
horizontalCenter: parent.horizontalCenter
}
text: secondLine
horizontalAlignment: Text.AlignHCenter
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
font.bold: false
visible: !largeButton
}
Text {
id: myText
anchors {
top: myIcon.top
}
font.bold: true
font.pointSize: labelFontSize
color: "white"
text: name
anchors.left: parent.left
anchors.leftMargin: 55 * settings.ui_zoom / 100
anchors.topMargin: 20 * settings.ui_zoom / 100
visible: !largeButton
}
RoundButton {
objectName: minusName
autoRepeat: true
text: "-"
onClicked: minus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.left: parent.left
anchors.leftMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: plusName
text: "+"
onClicked: plus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.right: parent.right
anchors.rightMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: identificator
text: largeButtonLabel
onClicked: largeButton_clicked(objectName)
visible: largeButton
anchors.fill: rect
background: Rectangle {
color: largeButtonColor
radius: 20
}
font.pointSize: 20 * settings.ui_zoom / 100
}
}
}
footer: Item {
id: footerItem
width: parent.width
height: footerHeight
property real footerHeight: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
property real minHeight: parent.height / 4
property real maxHeight: parent.height * 3 / 4
anchors.bottom: parent.bottom
clip: true
visible: rootItem.chartFooterVisible || rootItem.videoVisible
Rectangle {
id: dragHandle
width: parent.width / 5
height: 10
color: "#9C27B0"
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.fill: parent
cellWidth: 175 * settings.ui_zoom / 100
cellHeight: 130 * settings.ui_zoom / 100
focus: true
model: appModel
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
id: gridView
objectName: "gridview"
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
Screen.onPrimaryOrientationChanged:{
if(OS_VERSION === "Android")
gridView.leftMargin = (Screen.width % cellWidth) / 2;
else
gridView.leftMargin = (parent.width % cellWidth) / 2;
visible: rootItem.chartFooterVisible || rootItem.videoVisible
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.strokeStyle = "#FFFFFF";
ctx.lineWidth = 2;
for (var i = 0; i < 3; i++) {
ctx.beginPath();
ctx.moveTo(0, (i + 1) * parent.height / 4);
ctx.lineTo(parent.width, (i + 1) * parent.height / 4);
ctx.stroke();
}
}
}
// highlight: Rectangle {
// width: 150
// height: 150
// color: "lightsteelblue"
// }
delegate: Item {
id: id1
width: 170 * settings.ui_zoom / 100
height: 125 * settings.ui_zoom / 100
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
visible: visibleItem
Component.onCompleted: console.log("completed " + objectName)
property real startY: 0
property real startHeight: 0
Behavior on x {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
onPressed: {
startY = mouseY
startHeight = footerItem.height
}
Behavior on y {
enabled: id1.state != "active"
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
}
SequentialAnimation on rotation {
NumberAnimation { to: 2; duration: 60 }
NumberAnimation { to: -2; duration: 120 }
NumberAnimation { to: 0; duration: 60 }
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
loops: Animation.Infinite; alwaysRunToEnd: true
}
states: State {
name: "active"; when: loc.currentId === gridId && window.lockTiles
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
}
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
Rectangle {
width: 168 * settings.ui_zoom / 100
height: 123 * settings.ui_zoom / 100
radius: 3
border.width: 1
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
color: settings.theme_tile_background_color
id: rect
}
DropShadow {
visible: settings.theme_tile_shadow_enabled
anchors.fill: rect
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: settings.theme_tile_shadow_color
source: rect
}
Timer {
id: toggleIconTimer
interval: 500; running: true; repeat: true
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
}
Image {
id: myIcon
x: 5
anchors {
bottom: id1.bottom
onMouseYChanged: {
if (pressed) {
var newHeight = Math.max(footerItem.minHeight, Math.min(footerItem.maxHeight, startHeight + startY - mouseY))
footerItem.footerHeight = newHeight
}
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
source: icon
visible: settings.theme_tile_icon_enabled && !largeButton
}
Text {
objectName: "value"
id: myValue
color: valueFontColor
y: 0
anchors {
horizontalCenter: parent.horizontalCenter
}
text: value
horizontalAlignment: Text.AlignHCenter
font.pointSize: valueFontSize * settings.ui_zoom / 100
font.bold: true
visible: !largeButton
}
Text {
objectName: "secondLine"
id: secondLineText
color: "white"
y: myValue.bottom
anchors {
top: myValue.bottom
horizontalCenter: parent.horizontalCenter
}
text: secondLine
horizontalAlignment: Text.AlignHCenter
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
font.bold: false
visible: !largeButton
}
Text {
id: myText
anchors {
top: myIcon.top
}
font.bold: true
font.pointSize: labelFontSize
color: "white"
text: name
anchors.left: parent.left
anchors.leftMargin: 55 * settings.ui_zoom / 100
anchors.topMargin: 20 * settings.ui_zoom / 100
visible: !largeButton
}
RoundButton {
objectName: minusName
autoRepeat: true
text: "-"
onClicked: minus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.left: parent.left
anchors.leftMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: plusName
text: "+"
onClicked: plus_clicked(objectName)
visible: writable && !largeButton
anchors.top: myValue.top
anchors.right: parent.right
anchors.rightMargin: 2
width: 48 * settings.ui_zoom / 100
height: 48 * settings.ui_zoom / 100
}
RoundButton {
autoRepeat: true
objectName: identificator
text: largeButtonLabel
onClicked: largeButton_clicked(objectName)
visible: largeButton
anchors.fill: rect
background: Rectangle {
color: largeButtonColor
radius: 20
}
font.pointSize: 20 * settings.ui_zoom / 100
//width: 48 * settings.ui_zoom / 100
//height: 48 * settings.ui_zoom / 100
}
/*MouseArea {
anchors.fill: parent
onClicked: parent.GridView.view.currentIndex = index
}*/
}
}
footer:
Item {
width: parent.width
height: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
anchors.top: gridView.bottom
visible: rootItem.chartFooterVisible || rootItem.videoVisible
Rectangle {
id: chartFooterRectangle
visible: rootItem.chartFooterVisible
anchors.fill: parent
ChartFooter {
anchors.fill: parent
visible: rootItem.chartFooterVisible
}
}
Rectangle {
objectName: "footerrectangle"
visible: rootItem.videoVisible
anchors.fill: parent
// Removed Timer, Play/Pause/Resume is now done via Homeform.cpp
/*
Timer {
id: pauseTimer
interval: 1000; running: true; repeat: true
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
videoPlaybackHalf.play() :
videoPlaybackHalf.pause()) } }
}
*/
onVisibleChanged: {
if(visible === true) {
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
console.log("videoRate: " + rootItem.videoRate)
videoPlaybackHalf.source = rootItem.videoPath
//videoPlaybackHalf.playbackRate = rootItem.videoRate
videoPlaybackHalf.seek(rootItem.videoPosition)
videoPlaybackHalf.play()
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
} else {
videoPlaybackHalf.stop()
}
}
MediaPlayer {
id: videoPlaybackHalf
objectName: "videoplaybackhalf"
autoPlay: false
playbackRate: rootItem.videoRate
onError: {
if (videoPlaybackHalf.NoError !== error) {
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
}
}
}
VideoOutput {
id:videoPlayer
anchors.fill: parent
source: videoPlaybackHalf
}
}
Rectangle {
id: chartFooterRectangle
visible: rootItem.chartFooterVisible
anchors.top: dragHandle.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
ChartFooter {
anchors.fill: parent
visible: rootItem.chartFooterVisible
}
}
Rectangle {
objectName: "footerrectangle"
visible: rootItem.videoVisible
anchors.top: dragHandle.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
onVisibleChanged: {
if(visible === true) {
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
console.log("videoRate: " + rootItem.videoRate)
videoPlaybackHalf.source = rootItem.videoPath
videoPlaybackHalf.seek(rootItem.videoPosition)
videoPlaybackHalf.play()
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
} else {
videoPlaybackHalf.stop()
}
}
MediaPlayer {
id: videoPlaybackHalf
objectName: "videoplaybackhalf"
autoPlay: false
playbackRate: rootItem.videoRate
onError: {
if (videoPlaybackHalf.NoError !== error) {
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
}
}
}
VideoOutput {
id: videoPlayer
anchors.fill: parent
source: videoPlaybackHalf
}
}
}
MouseArea {
property int currentId: -1 // Original position in model
property int newIndex // Current Position in model

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

1428
src/Wizard.qml Normal file

File diff suppressed because it is too large Load Diff

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.16.57" android:versionCode="804" 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 -->
@@ -78,7 +78,8 @@
<service
android:name=".ForegroundService"
android:enabled="true"
android:exported="true"></service>
android:foregroundServiceType="connectedDevice"
android:exported="false"></service>
<service
android:name=".WearableMessageListenerService"
android:enabled="true"
@@ -97,6 +98,7 @@
<service android:name="com.cgutman.androidremotedebugger.service.ShellService"
android:enabled="true"
android:foregroundServiceType="connectedDevice"
android:exported="false" >
</service>
@@ -109,13 +111,21 @@
android:value="ocr" />
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
<receiver android:name=".MediaButtonReceiver" android:exported="true" android:permission="android.permission.MODIFY_AUDIO_SETTINGS">
<intent-filter>
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
</intent-filter>
</receiver>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_MEDIA_BUTTON" />
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
@@ -126,6 +136,7 @@
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />

View File

@@ -18,7 +18,6 @@ repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
maven { url 'https://dl.bintray.com/rvalerio/maven' }
}
apply plugin: 'com.android.application'
@@ -28,11 +27,11 @@ def amazon = System.getenv('AMAZON')
println(amazon)
dependencies {
compile 'com.rvalerio:fgchecker:1.1.0'
implementation "androidx.core:core:1.12.0"
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
if(amazon == "1") {
// amazon app store
implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
@@ -45,13 +44,13 @@ dependencies {
def appcompat_version = "1.3.1"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:6.0.0"
implementation "com.android.billingclient:billing:6.0.1"
implementation 'com.android.support:appcompat-v7:28.0.0'
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:+'
@@ -130,7 +129,7 @@ android {
resConfig "en"
compileSdkVersion 33
minSdkVersion = 21
targetSdkVersion = 33
targetSdkVersion = 34
}
tasks.all { task ->

Binary file not shown.

Binary file not shown.

View File

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

View File

@@ -37,9 +37,10 @@ import java.util.UUID;
public class BleAdvertiser {
private static final UUID SERVICE_UUID = UUID.fromString("00001826-0000-1000-8000-00805f9b34fb");
private static final byte[] SERVICE_DATA = {0x01, 0x10, 0x00};
private static final byte[] SERVICE_DATA_ROWER = {0x01, 0x10, 0x00};
private static final byte[] SERVICE_DATA_TREADMILL = {0x01, 0x01, 0x00};
public static void startAdvertising(Context context) {
public static void startAdvertisingRower(Context context) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
android.bluetooth.le.BluetoothLeAdvertiser advertiser = bluetoothManager.getAdapter().getBluetoothLeAdvertiser();
@@ -53,7 +54,30 @@ public class BleAdvertiser {
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(new ParcelUuid(SERVICE_UUID))
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA)
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA_ROWER)
.build();
if (advertiser != null) {
advertiser.startAdvertising(settings, advertiseData, advertiseCallback);
}
}
}
public static void startAdvertisingTreadmill(Context context) {
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
android.bluetooth.le.BluetoothLeAdvertiser advertiser = bluetoothManager.getAdapter().getBluetoothLeAdvertiser();
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setConnectable(true)
.build();
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(new ParcelUuid(SERVICE_UUID))
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA_TREADMILL)
.build();
if (advertiser != null) {

View File

@@ -36,6 +36,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import android.os.Build;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
@@ -153,7 +155,7 @@ public class ChannelService extends Service {
public void openAllChannels() throws ChannelNotAvailableException {
if (Ant.heartRequest && heartChannelController == null)
heartChannelController = new HeartChannelController(acquireChannel());
heartChannelController = new HeartChannelController();
if (Ant.speedRequest) {
if(Ant.treadmill && sdmChannelController == null) {
@@ -258,8 +260,12 @@ public class ChannelService extends Service {
private void doBindAntRadioService() {
if (BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
// Start listing for channel available intents
registerReceiver(mChannelProviderStateChangedReceiver, new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED));
ContextCompat.registerReceiver(
this,
mChannelProviderStateChangedReceiver,
new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED),
ContextCompat.RECEIVER_EXPORTED
);
// Creating the intent and calling context.bindService() is handled by
// the static bindService() method in AntService

View File

@@ -9,9 +9,14 @@ import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import android.content.pm.ServiceInfo;
import android.util.Log;
public class ForegroundService extends Service {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
@Override
public void onCreate() {
super.onCreate();
@@ -29,7 +34,18 @@ public class ForegroundService extends Service {
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
try {
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, serviceType);
} else {
startForeground(1, notification);
}
} catch (Exception e) {
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
//do heavy work on a background thread
//stopSelf();
return START_NOT_STICKY;

View File

@@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
import android.content.ContextWrapper;
import android.content.IntentFilter;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import org.jetbrains.annotations.Nullable;
@@ -48,12 +49,24 @@ public class Garmin {
private static Integer HR = 0;
private static Integer FootCad = 0;
private static Double Speed = 0.0;
private static Integer Power = 0;
public static int getHR() {
Log.d(TAG, "getHR " + HR);
return HR;
}
public static int getPower() {
Log.d(TAG, "getPower " + Power);
return Power;
}
public static double getSpeed() {
Log.d(TAG, "getSpeed " + Speed);
return Speed;
}
public static int getFootCad() {
Log.d(TAG, "getFootCad " + FootCad);
return FootCad;
@@ -152,7 +165,12 @@ public class Garmin {
synchronized (receiverToWrapper) {
receiverToWrapper.put(receiver, wrappedRecv);
}
return super.registerReceiver(wrappedRecv, filter);
return ContextCompat.registerReceiver(
super.getBaseContext(),
wrappedRecv,
filter,
ContextCompat.RECEIVER_EXPORTED
);
}
@Override
@@ -221,13 +239,21 @@ public class Garmin {
if (status == ConnectIQ.IQMessageStatus.SUCCESS) {
//MessageHandler.getInstance().handleMessageFromWatchUsingCIQ(message, status, context);
Log.d(TAG, "onMessageReceived, status: " + status.toString() + message.get(0));
String var[] = message.toArray()[0].toString().split(",");
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
if(var.length > 1) {
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
try {
String var[] = message.toArray()[0].toString().split(",");
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
if(var.length > 1) {
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
if(var.length > 2) {
Power = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
Speed = Double.parseDouble(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
}
}
Log.d(TAG, "HR " + HR);
Log.d(TAG, "FootCad " + FootCad);
} catch (Exception e) {
Log.e(TAG, "Processing error", e);
}
Log.d(TAG, "HR " + HR);
Log.d(TAG, "FootCad " + FootCad);
} else {
Log.d(TAG, "onMessageReceived error, status: " + status.toString());
}

View File

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

View File

@@ -18,6 +18,7 @@ import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.util.Log;
import android.os.Build;
import androidx.core.content.ContextCompat;
/**
* This class is used for talking to hid of the dongle, connecting, disconnencting and enumerating the devices.
@@ -88,7 +89,12 @@ public class HidBridge {
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(_context, 0, new Intent(ACTION_USB_PERMISSION), flags);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
_context.registerReceiver(mUsbReceiver, filter);
ContextCompat.registerReceiver(
_context,
mUsbReceiver,
filter,
ContextCompat.RECEIVER_EXPORTED
);
_usbManager.requestPermission(_usbDevice, mPermissionIntent);
Log("Found the device");

View File

@@ -0,0 +1,65 @@
package org.cagnulen.qdomyoszwift;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.provider.Settings;
import android.util.Log;
public class LocationHelper {
private static final String TAG = "LocationHelper";
private static boolean isLocationEnabled = false;
private static boolean isBluetoothEnabled = false;
public static boolean start(Context context) {
Log.d(TAG, "Starting LocationHelper check...");
isLocationEnabled = isLocationEnabled(context);
isBluetoothEnabled = isBluetoothEnabled();
return isLocationEnabled && isBluetoothEnabled;
}
public static void requestPermissions(Context context) {
if (!isLocationEnabled || !isBluetoothEnabled) {
Log.d(TAG, "Some services are disabled. Prompting user...");
if (!isLocationEnabled) {
promptEnableLocation(context);
}
if (!isBluetoothEnabled) {
promptEnableBluetooth(context);
}
} else {
Log.d(TAG, "All services are already enabled.");
}
}
public static boolean check(Context context) {
return isLocationEnabled(context) && isBluetoothEnabled();
}
private static boolean isLocationEnabled(Context context) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
return locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
private static boolean isBluetoothEnabled() {
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
return bluetoothAdapter != null && bluetoothAdapter.isEnabled();
}
private static void promptEnableLocation(Context context) {
Log.d(TAG, "Prompting to enable Location...");
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private static void promptEnableBluetooth(Context context) {
Log.d(TAG, "Prompting to enable Bluetooth...");
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

View File

@@ -0,0 +1,71 @@
package org.cagnulen.qdomyoszwift;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.util.Log;
import android.os.Build;
public class MediaButtonReceiver extends BroadcastReceiver {
private static MediaButtonReceiver instance;
@Override
public void onReceive(Context context, Intent intent) {
Log.d("MediaButtonReceiver", "Received intent: " + intent.toString());
String intentAction = intent.getAction();
if ("android.media.VOLUME_CHANGED_ACTION".equals(intentAction)) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
Log.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
}
}
private native void nativeOnMediaButtonEvent(int prev, int current, int max);
public static void registerReceiver(Context context) {
try {
if (instance == null) {
instance = new MediaButtonReceiver();
}
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
if (context == null) {
Log.e("MediaButtonReceiver", "Context is null, cannot register receiver");
return;
}
if (Build.VERSION.SDK_INT >= 34) {
try {
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
} catch (SecurityException se) {
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
} else {
try {
context.registerReceiver(instance, filter);
} catch (SecurityException se) {
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
}
Log.d("MediaButtonReceiver", "Receiver registered successfully");
} catch (IllegalArgumentException e) {
Log.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
} catch (Exception e) {
Log.e("MediaButtonReceiver", "Unexpected error while registering receiver: " + e.getMessage());
}
}
public static void unregisterReceiver(Context context) {
if (instance != null) {
context.unregisterReceiver(instance);
instance = null;
}
}
}

View File

@@ -16,12 +16,18 @@ public class NotificationClient
private static Context _context;
private static Intent serviceIntent = null;
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
public NotificationClient() {}
public static void notify(Context context, String message) {
_context = context;
serviceIntent = new Intent(context, ForegroundService.class);
serviceIntent.putExtra("inputExtra", "QZ is Running");
serviceIntent.putExtra(EXTRA_FOREGROUND_SERVICE_TYPE,
FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {

View File

@@ -49,6 +49,9 @@ public class QZAdbRemote implements DeviceConnectionListener {
private static QZAdbRemote INSTANCE;
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
public static QZAdbRemote getInstance() {
if(INSTANCE == null) {
INSTANCE = new QZAdbRemote();
@@ -181,13 +184,14 @@ public class QZAdbRemote implements DeviceConnectionListener {
if (binder == null) {
service = new Intent(_context, ShellService.class);
service.putExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
/* Bind the service if we're not bound already. After binding, the callback will
* perform the initial connection. */
_context.bindService(service, QZAdbRemote.getInstance().serviceConn, Service.BIND_AUTO_CREATE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
_context.startForegroundService(service);
_context.startForegroundService(service);
}
else {
_context.startService(service);

View File

@@ -43,6 +43,8 @@ import android.graphics.Rect;
import android.graphics.Point;
import androidx.core.util.Pair;
import android.util.Log;
import android.os.Build;
public class ScreenCaptureService extends Service {
@@ -56,6 +58,9 @@ public class ScreenCaptureService extends Service {
private static int IMAGES_PRODUCED;
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
private MediaProjection mMediaProjection;
private String mStoreDir;
private ImageReader mImageReader;
@@ -296,7 +301,18 @@ public class ScreenCaptureService extends Service {
if (isStartCommand(intent)) {
// create notification
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
startForeground(notification.first, notification.second);
try {
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(notification.first, notification.second, serviceType);
} else {
startForeground(notification.first, notification.second);
}
} catch (Exception e) {
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
// start projection
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
Intent data = intent.getParcelableExtra(DATA);

View File

@@ -13,6 +13,7 @@ import android.app.Service;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import androidx.core.content.ContextCompat;
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
@@ -68,7 +69,12 @@ public class Usbserial {
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.cagnulen.qdomyoszwift.USB_PERMISSION"), flags);
IntentFilter filter = new IntentFilter("org.cagnulen.qdomyoszwift.USB_PERMISSION");
context.registerReceiver(usbReceiver, filter);
ContextCompat.registerReceiver(
context,
usbReceiver,
filter,
ContextCompat.RECEIVER_EXPORTED
);
manager.requestPermission(driver.getDevice(), permissionIntent);
for(int i=0; i<5000; i++) {
if(granted[0] != null) break;

View File

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

View File

@@ -23,8 +23,12 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import androidx.core.app.NotificationCompat;
import android.util.Log;
public class ShellService extends Service implements DeviceConnectionListener {
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
private ShellServiceBinder binder = new ShellServiceBinder();
private ShellListener listener = new ShellListener(this);
@@ -98,10 +102,20 @@ public class ShellService extends Service implements DeviceConnectionListener {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (foregroundId == 0) {
try {
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
// If we're not already running in the foreground, use a placeholder
// notification until a real connection is established. After connection
// establishment, the real notification will replace this one.
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification(), serviceType);
} else {
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
}
} catch (Exception e) {
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
}
// Don't restart if we've been killed. We will have already lost our connections
@@ -220,7 +234,7 @@ public class ShellService extends Service implements DeviceConnectionListener {
* and start it as foreground */
foregroundId = getConnectedNotificationId(newConn);
nm.cancel(foregroundId);
startForeground(foregroundId, createConnectionNotification(newConn, true));
startForeground(foregroundId, createConnectionNotification(newConn, true));
}
}
else {

View File

@@ -0,0 +1,126 @@
package com.rvalerio.fgchecker;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.rvalerio.fgchecker.detectors.Detector;
import com.rvalerio.fgchecker.detectors.LollipopDetector;
import com.rvalerio.fgchecker.detectors.PreLollipopDetector;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AppChecker {
static final int DEFAULT_TIMEOUT = 1000;
int timeout = DEFAULT_TIMEOUT;
ScheduledExecutorService service;
Runnable runnable;
Listener unregisteredPackageListener;
Listener anyPackageListener;
Map<String, Listener> listeners;
Detector detector;
Handler handler;
public interface Listener {
void onForeground(String process);
}
public AppChecker() {
listeners = new HashMap<>();
handler = new Handler(Looper.getMainLooper());
if(Utils.postLollipop())
detector = new LollipopDetector();
else
detector = new PreLollipopDetector();
}
public AppChecker timeout(int timeout) {
this.timeout = timeout;
return this;
}
public AppChecker when(String packageName, Listener listener) {
listeners.put(packageName, listener);
return this;
}
@Deprecated
public AppChecker other(Listener listener) {
return whenOther(listener);
}
public AppChecker whenOther(Listener listener) {
unregisteredPackageListener = listener;
return this;
}
public AppChecker whenAny(Listener listener) {
anyPackageListener = listener;
return this;
}
public void start(Context context) {
runnable = createRunnable(context.getApplicationContext());
service = new ScheduledThreadPoolExecutor(1);
service.schedule(runnable, timeout, TimeUnit.MILLISECONDS);
}
public void stop() {
if(service != null) {
service.shutdownNow();
service = null;
}
runnable = null;
}
private Runnable createRunnable(final Context context) {
return new Runnable() {
@Override
public void run() {
getForegroundAppAndNotify(context);
service.schedule(createRunnable(context), timeout, TimeUnit.MILLISECONDS);
}
};
}
private void getForegroundAppAndNotify(Context context) {
final String foregroundApp = getForegroundApp(context);
boolean foundRegisteredPackageListener = false;
if(foregroundApp != null) {
for (String packageName : listeners.keySet()) {
if (packageName.equalsIgnoreCase(foregroundApp)) {
foundRegisteredPackageListener = true;
callListener(listeners.get(foregroundApp), foregroundApp);
}
}
if(!foundRegisteredPackageListener && unregisteredPackageListener != null) {
callListener(unregisteredPackageListener, foregroundApp);
}
}
if(anyPackageListener != null) {
callListener(anyPackageListener, foregroundApp);
}
}
void callListener(final Listener listener, final String packageName) {
handler.post(new Runnable() {
@Override
public void run() {
listener.onForeground(packageName);
}
});
}
public String getForegroundApp(Context context) {
return detector.getForegroundApp(context);
}
}

View File

@@ -0,0 +1,26 @@
package com.rvalerio.fgchecker;
import android.annotation.TargetApi;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.Build;
public class Utils {
private Utils() {
}
public static boolean postLollipop() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean hasUsageStatsPermission(Context context) {
AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOps.checkOpNoThrow("android:get_usage_stats",
android.os.Process.myUid(), context.getPackageName());
boolean granted = mode == AppOpsManager.MODE_ALLOWED;
return granted;
}
}

View File

@@ -0,0 +1,8 @@
package com.rvalerio.fgchecker.detectors;
import android.content.Context;
public interface Detector {
String getForegroundApp(Context context);
}

View File

@@ -0,0 +1,34 @@
package com.rvalerio.fgchecker.detectors;
import android.annotation.TargetApi;
import android.app.Service;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.os.Build;
import com.rvalerio.fgchecker.Utils;
public class LollipopDetector implements Detector {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public String getForegroundApp(final Context context) {
if(!Utils.hasUsageStatsPermission(context))
return null;
String foregroundApp = null;
UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService(Service.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
UsageEvents usageEvents = mUsageStatsManager.queryEvents(time - 1000 * 3600, time);
UsageEvents.Event event = new UsageEvents.Event();
while (usageEvents.hasNextEvent()) {
usageEvents.getNextEvent(event);
if(event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
foregroundApp = event.getPackageName();
}
}
return foregroundApp ;
}
}

View File

@@ -0,0 +1,30 @@
package com.rvalerio.fgchecker.detectors;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
public class PreLollipopDetector implements Detector {
@Override
public String getForegroundApp(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Service.ACTIVITY_SERVICE);
ActivityManager.RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();
PackageManager pm = context.getPackageManager();
PackageInfo foregroundAppPackageInfo = null;
try {
foregroundAppPackageInfo = pm.getPackageInfo(foregroundTaskPackageName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
String foregroundApp = null;
if(foregroundAppPackageInfo != null)
foregroundApp = foregroundAppPackageInfo.applicationInfo.packageName;
return foregroundApp;
}
}

View File

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

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

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

View File

@@ -3,7 +3,7 @@
#include "characteristicwriteprocessor.h"
#include <QSettings>
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, int8_t bikeResistanceOffset,
bluetoothdevice *bike, QObject *parent)
: QObject(parent), bikeResistanceOffset(bikeResistanceOffset), bikeResistanceGain(bikeResistanceGain), Bike(bike) {}
@@ -25,6 +25,8 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
double CRRGain = settings.value(QZSettings::CRRGain, QZSettings::default_CRRGain).toDouble();
double CWGain = settings.value(QZSettings::CWGain, QZSettings::default_CWGain).toDouble();
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
QStringLiteral(" enabled ") + force_resistance;
@@ -38,6 +40,11 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
percentage = (((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * 2.0) * gain) + offset;
}
if(min_inclination > grade) {
grade = min_inclination;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
/*
Surface Road Crr MTB Crr Gravel Crr (Namebrand) Zwift Gravel Crr
Pavement .004 .01 .008 .008
@@ -60,9 +67,13 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
if (dt == bluetoothdevice::BIKE) {
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
// form Zwift
if (!((bike *)Bike)->inclinationAvailableByHardware())
Bike->setInclination(grade + CRR_offset + CW_offset);
// from Zwift
if (!((bike *)Bike)->inclinationAvailableByHardware()) {
if(zwift_play_emulator)
Bike->setInclination(grade);
else
Bike->setInclination(grade + CRR_offset + CW_offset);
}
emit changeInclination(grade, percentage);

View File

@@ -12,11 +12,11 @@
class CharacteristicWriteProcessor : public QObject {
Q_OBJECT
public:
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bluetoothdevice *Bike;
explicit CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
explicit CharacteristicWriteProcessor(double bikeResistanceGain, int8_t bikeResistanceOffset,
bluetoothdevice *bike, QObject *parent = nullptr);
virtual int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) = 0;
virtual void changePower(uint16_t power);

View File

@@ -6,7 +6,7 @@
#include <QtMath>
CharacteristicWriteProcessor2AD9::CharacteristicWriteProcessor2AD9(double bikeResistanceGain,
uint8_t bikeResistanceOffset, bluetoothdevice *bike,
int8_t bikeResistanceOffset, bluetoothdevice *bike,
CharacteristicNotifier2AD9 *notifier,
QObject *parent)
: CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier(notifier) {}
@@ -96,8 +96,6 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
int16_t sincline = a + (((int16_t)b) << 8);
double requestIncline = (double)sincline / 10.0;
if (requestIncline < 0)
requestIncline = 0;
if (dt == bluetoothdevice::TREADMILL)
((treadmill *)Bike)->changeInclination(requestIncline, requestIncline);

View File

@@ -9,7 +9,7 @@ class CharacteristicWriteProcessor2AD9 : public CharacteristicWriteProcessor {
CharacteristicNotifier2AD9 *notifier = nullptr;
public:
explicit CharacteristicWriteProcessor2AD9(double bikeResistanceGain, uint8_t bikeResistanceOffset,
explicit CharacteristicWriteProcessor2AD9(double bikeResistanceGain, int8_t bikeResistanceOffset,
bluetoothdevice *bike, CharacteristicNotifier2AD9 *notifier,
QObject *parent = nullptr);
int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) override;

View File

@@ -6,7 +6,7 @@
#include <QtMath>
CharacteristicWriteProcessorE005::CharacteristicWriteProcessorE005(double bikeResistanceGain,
uint8_t bikeResistanceOffset, bluetoothdevice *bike,
int8_t bikeResistanceOffset, bluetoothdevice *bike,
// CharacteristicNotifier2AD9 *notifier,
QObject *parent)
: CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent) {}

View File

@@ -8,7 +8,7 @@ class CharacteristicWriteProcessorE005 : public CharacteristicWriteProcessor {
Q_OBJECT
public:
explicit CharacteristicWriteProcessorE005(double bikeResistanceGain, uint8_t bikeResistanceOffset,
explicit CharacteristicWriteProcessorE005(double bikeResistanceGain, int8_t bikeResistanceOffset,
bluetoothdevice *bike, // CharacteristicNotifier2AD9 *notifier,
QObject *parent = nullptr);
int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) override;

View File

@@ -0,0 +1,197 @@
#include "antbike.h"
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <math.h>
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
#endif
#include <chrono>
using namespace std::chrono_literals;
antbike::antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->noVirtualDevice = noVirtualDevice;
initDone = false;
connect(refresh, &QTimer::timeout, this, &antbike::update);
refresh->start(200ms);
}
void antbike::update() {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen hh;
Cadence = hh.getFootCad();
m_watt = hh.getPower();
qDebug() << QStringLiteral("Current Garmin Cadence: ") << QString::number(Cadence.value());
if (settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = metric::calculateSpeedFromPower(
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
speedLimit());
} else {
Speed = hh.getSpeed();
}
#endif
#endif
#ifdef Q_OS_ANDROID
Cadence = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Garmin", "getFootCad", "()I");
m_watt = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Garmin", "getPower", "()I");
if (settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = metric::calculateSpeedFromPower(
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
speedLimit());
} else {
Speed = QAndroidJniObject::callStaticMethod<jdouble>("org/cagnulen/qdomyoszwift/Garmin", "getSpeed", "()D");
}
qDebug() << QStringLiteral("Current Garmin Cadence: ") << QString::number(Cadence.value());
#endif
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
if (requestInclination != -100) {
Inclination = requestInclination;
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
requestInclination = -100;
}
update_metrics(false, watts());
Distance += ((Speed.value() / (double)3600.0) /
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice() && !noVirtualDevice
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual bike interface..."));
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
connect(virtualBike, &virtualbike::changeInclination, this, &antbike::changeInclinationRequested);
connect(virtualBike, &virtualbike::ftmsCharacteristicChanged, this, &antbike::ftmsCharacteristicChanged);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
if (!firstStateChanged)
emit connectedAndDiscovered();
firstStateChanged = 1;
// ********************************************************************************************************
if (!noVirtualDevice) {
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
debug("Current Heart: " + QString::number(Heart.value()));
}
#endif
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
update_hr_from_external();
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
}
if (Heart.value()) {
static double lastKcal = 0;
if (KCal.value() < 0) // if the user pressed stop, the KCAL resets the accumulator
lastKcal = abs(KCal.value());
KCal = metric::calculateKCalfromHR(Heart.average(), elapsed.value()) + lastKcal;
}
if (requestResistance != -1 && requestResistance != currentResistance().value()) {
Resistance = requestResistance;
m_pelotonResistance = requestResistance;
}
}
void antbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QByteArray b = newValue;
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
}
void antbike::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
uint16_t antbike::wattsFromResistance(double resistance) {
return _ergTable.estimateWattage(Cadence.value(), resistance);
}
resistance_t antbike::resistanceFromPowerRequest(uint16_t power) {
//QSettings settings;
//bool toorx_srx_3500 = settings.value(QZSettings::toorx_srx_3500, QZSettings::default_toorx_srx_3500).toBool();
/*if(toorx_srx_3500)*/ {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
if (Cadence.value() == 0)
return 1;
for (resistance_t i = 1; i < maxResistance(); i++) {
if (wattsFromResistance(i) <= power && wattsFromResistance(i + 1) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << wattsFromResistance(i)
<< wattsFromResistance(i + 1) << power;
return i;
}
}
if (power < wattsFromResistance(1))
return 1;
else
return maxResistance();
} /*else {
return power / 10;
}*/
}
uint16_t antbike::watts() { return m_watt.value(); }
bool antbike::connected() { return true; }

View File

@@ -0,0 +1,81 @@
#ifndef ANTBIKE_H
#define ANTBIKE_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 "ergtable.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class antbike : public bike {
Q_OBJECT
public:
antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice);
bool connected() override;
uint16_t watts() override;
resistance_t maxResistance() override { return 100; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
private:
QTimer *refresh;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
QDateTime lastGoodCadence = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool noVirtualDevice = false;
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
uint16_t wattsFromResistance(double resistance);
signals:
void disconnected();
void debug(QString string);
private slots:
void changeInclinationRequested(double grade, double percentage);
void update();
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // ANTBIKE_H

View File

@@ -12,7 +12,7 @@
using namespace std::chrono_literals;
apexbike::apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
apexbike::apexbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
@@ -140,28 +140,45 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
lastPacket = newValue;
if (newValue.length() != 10 && newValue.at(2) != 0x31) {
if (newValue.length() == 10 && newValue.at(2) == 0x31) {
Resistance = newValue.at(5);
emit resistanceRead(Resistance.value());
m_pelotonResistance = Resistance.value();
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
}
if (newValue.length() != 10 || newValue.at(2) != 0x30) {
return;
}
Resistance = newValue.at(5);
emit resistanceRead(Resistance.value());
m_pelotonResistance = Resistance.value();
uint16_t distanceData = (newValue.at(3) << 8) | ((uint8_t)newValue.at(4));
double distance = ((double)distanceData);
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
if(distance != lastDistance) {
if(lastDistance != 0) {
double deltaDistance = distance - lastDistance;
double deltaTime = fabs(now.msecsTo(lastTS));
double timeHours = deltaTime / (1000.0 * 60.0 * 60.0);
double k = 0.005333;
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = (deltaDistance *k) / timeHours; // Speed in distance units per hour
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = Speed.value() / 0.37497622;
}
}
lastDistance = distance;
lastTS = now;
qDebug() << "lastDistance" << lastDistance << "lastTS" << lastTS;
}
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((uint8_t)newValue.at(4));
}
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(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
@@ -221,10 +238,17 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
void apexbike::btinit() {
sendPoll();
QThread::msleep(2000);
sendPoll();
QThread::msleep(2000);
uint8_t initData1[] = {0xeb, 0x50, 0x51, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa2};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
QThread::msleep(500);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
QThread::msleep(500);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
QThread::msleep(500);
initDone = true;

View File

@@ -35,7 +35,7 @@
class apexbike : public bike {
Q_OBJECT
public:
apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
apexbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected() override;
private:
@@ -54,7 +54,7 @@ class apexbike : public bike {
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;
@@ -69,6 +69,9 @@ class apexbike : public bike {
bool noWriteResistance = false;
bool noHeartService = false;
double lastDistance = 0;
QDateTime lastTS = QDateTime::currentDateTime();
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
bhfitnesselliptical::bhfitnesselliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
bhfitnesselliptical::bhfitnesselliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);

View File

@@ -35,7 +35,7 @@
class bhfitnesselliptical : public elliptical {
Q_OBJECT
public:
bhfitnesselliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
bhfitnesselliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
bool connected() override;
@@ -55,7 +55,7 @@ class bhfitnesselliptical : public elliptical {
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
const uint8_t max_resistance = 72; // 24;
const uint8_t default_resistance = 6;

View File

@@ -1,6 +1,7 @@
#include "devices/bike.h"
#include "qdebugfixup.h"
#include "homeform.h"
#include <QSettings>
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
@@ -14,6 +15,8 @@ void bike::changeResistance(resistance_t resistance) {
double zwift_erg_resistance_down =
settings.value(QZSettings::zwift_erg_resistance_down, QZSettings::default_zwift_erg_resistance_down).toDouble();
qDebug() << QStringLiteral("bike::changeResistance") << autoResistanceEnable << resistance;
lastRawRequestedResistanceValue = resistance;
if (autoResistanceEnable) {
double v = (resistance * m_difficult) + gears();
@@ -81,12 +84,63 @@ void bike::changePower(int32_t power) {
}
}
double bike::gears() { return m_gears; }
double bike::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();
if(gears_zwift_ratio) {
if(m_gears < 1)
return 1.0;
else if(m_gears > 24)
return 24.0;
}
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;
settings.setValue(QZSettings::gears_current_value, m_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);
}
@@ -306,3 +360,61 @@ uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
}
return watt;
}
double bike::gearsZwiftRatio() {
if(m_gears <= 0)
return 0.65;
else if(m_gears > 24)
return 6;
switch((int)m_gears) {
case 1:
return 0.75;
case 2:
return 0.87;
case 3:
return 0.99;
case 4:
return 1.11;
case 5:
return 1.23;
case 6:
return 1.38;
case 7:
return 1.53;
case 8:
return 1.68;
case 9:
return 1.86;
case 10:
return 2.04;
case 11:
return 2.22;
case 12:
return 2.40;
case 13:
return 2.61;
case 14:
return 2.82;
case 15:
return 3.03;
case 16:
return 3.24;
case 17:
return 3.49;
case 18:
return 3.74;
case 19:
return 3.99;
case 20:
return 4.24;
case 21:
return 4.54;
case 22:
return 4.84;
case 23:
return 5.14;
case 24:
return 5.49;
}
return 1;
}

View File

@@ -23,6 +23,9 @@ class bike : public bluetoothdevice {
double currentCrankRevolutions() override;
uint16_t lastCrankEventTime() override;
bool connected() override;
double defaultMaxGears() { return 9999.0; }
virtual double maxGears() { return defaultMaxGears(); }
virtual double minGears() { return -9999.0; }
virtual uint16_t watts();
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
@@ -36,8 +39,10 @@ class bike : public bluetoothdevice {
uint8_t metrics_override_heartrate() override;
void setGears(double d);
double gears();
double gearsZwiftRatio();
void setSpeedLimit(double speed) { m_speedLimit = speed; }
double speedLimit() { return m_speedLimit; }
virtual bool ifitCompatible() {return false;}
/**
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
@@ -58,26 +63,36 @@ class bike : public bluetoothdevice {
void changeInclination(double grade, double percentage) override;
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
void gearUp() {QSettings settings; setGears(gears() +
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
void gearDown() {QSettings settings; setGears(gears() -
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
void gearUp() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() + (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}
void gearDown() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() - (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}
Q_SIGNALS:
void bikeStarted();
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;
metric RequestedPelotonResistance;
metric RequestedCadence;
metric RequestedPower;
resistance_t requestResistance = -1;
double requestInclination = -100;
int16_t requestPower = -1;
bool ergModeSupported = false; // if a bike has this mode supported, when from the virtual bike there is a power
// request there is no need to translate in resistance levels

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
#include "qzsettings.h"
#include "devices/activiotreadmill/activiotreadmill.h"
#include "devices/antbike/antbike.h"
#include "devices/apexbike/apexbike.h"
#include "devices/bhfitnesselliptical/bhfitnesselliptical.h"
#include "devices/bkoolbike/bkoolbike.h"
@@ -31,10 +32,13 @@
#ifndef Q_OS_IOS
#include "devices/computrainerbike/computrainerbike.h"
#include "devices/csaferower/csaferower.h"
#include "devices/csafeelliptical/csafeelliptical.h"
#endif
#include "devices/concept2skierg/concept2skierg.h"
#include "devices/crossrope/crossrope.h"
#include "devices/cscbike/cscbike.h"
#include "devices/cycleopsphantombike/cycleopsphantombike.h"
#include "devices/deeruntreadmill/deerruntreadmill.h"
#include "devices/domyosbike/domyosbike.h"
#include "devices/domyoselliptical/domyoselliptical.h"
#include "devices/domyosrower/domyosrower.h"
@@ -65,9 +69,11 @@
#include "devices/iconceptelliptical/iconceptelliptical.h"
#include "devices/inspirebike/inspirebike.h"
#include "devices/keepbike/keepbike.h"
#include "devices/kineticinroadbike/kineticinroadbike.h"
#include "devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h"
#include "devices/kingsmithr2treadmill/kingsmithr2treadmill.h"
#include "devices/lifefitnesstreadmill/lifefitnesstreadmill.h"
#include "devices/lifespantreadmill/lifespantreadmill.h"
#include "devices/m3ibike/m3ibike.h"
#include "devices/mcfbike/mcfbike.h"
#include "devices/mepanelbike/mepanelbike.h"
@@ -76,6 +82,7 @@
#include "devices/nautilustreadmill/nautilustreadmill.h"
#include "devices/nordictrackelliptical/nordictrackelliptical.h"
#include "devices/nordictrackifitadbbike/nordictrackifitadbbike.h"
#include "devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h"
#include "devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h"
#include "devices/npecablebike/npecablebike.h"
#include "devices/octaneelliptical/octaneelliptical.h"
@@ -83,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"
@@ -107,7 +115,10 @@
#include "devices/spirittreadmill/spirittreadmill.h"
#include "devices/sportsplusbike/sportsplusbike.h"
#include "devices/sportsplusrower/sportsplusrower.h"
#include "devices/sportstechbike/sportstechbike.h"
#include "devices/sportstechelliptical/sportstechelliptical.h"
#include "devices/sramAXSController/sramAXSController.h"
#include "devices/stagesbike/stagesbike.h"
#include "devices/renphobike/renphobike.h"
@@ -118,11 +129,13 @@
#include "devices/echelonstride/echelonstride.h"
#include "templateinfosenderbuilder.h"
#include "technogymbike/technogymbike.h"
#include "devices/toorxtreadmill/toorxtreadmill.h"
#include "devices/treadmill.h"
#include "devices/truetreadmill/truetreadmill.h"
#include "devices/trxappgateusbbike/trxappgateusbbike.h"
#include "devices/trxappgateusbelliptical/trxappgateusbelliptical.h"
#include "devices/trxappgateusbrower/trxappgateusbrower.h"
#include "devices/trxappgateusbtreadmill/trxappgateusbtreadmill.h"
#include "devices/ultrasportbike/ultrasportbike.h"
#include "devices/wahookickrheadwind/wahookickrheadwind.h"
@@ -145,7 +158,7 @@ class bluetooth : public QObject, public SignalHandler {
bluetooth(const discoveryoptions &options);
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false,
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
bool testResistance = false, int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
bool startDiscovery = true);
~bluetooth();
bluetoothdevice *device();
@@ -159,6 +172,7 @@ class bluetooth : public QObject, public SignalHandler {
bool useDiscovery = false;
QFile *debugCommsLog = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
antbike *antBike = nullptr;
apexbike *apexBike = nullptr;
bkoolbike *bkoolBike = nullptr;
bhfitnesselliptical *bhFitnessElliptical = nullptr;
@@ -170,8 +184,11 @@ class bluetooth : public QObject, public SignalHandler {
#ifndef Q_OS_IOS
computrainerbike *computrainerBike = nullptr;
csaferower *csafeRower = nullptr;
csafeelliptical *csafeElliptical = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
cycleopsphantombike *cycleopsphantomBike = nullptr;
deerruntreadmill *deerrunTreadmill = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
domyosrower *domyosRower = nullptr;
@@ -186,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;
@@ -193,6 +211,7 @@ class bluetooth : public QObject, public SignalHandler {
nordictrackelliptical *nordictrackElliptical = nullptr;
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
nordictrackifitadbelliptical *nordictrackifitadbElliptical = nullptr;
octaneelliptical *octaneElliptical = nullptr;
octanetreadmill *octaneTreadmill = nullptr;
pelotonbike *pelotonBike = nullptr;
@@ -212,8 +231,11 @@ class bluetooth : public QObject, public SignalHandler {
truetreadmill *trueTreadmill = nullptr;
horizongr7bike *horizonGr7Bike = nullptr;
schwinnic4bike *schwinnIC4Bike = nullptr;
technogymbike* technogymBike = nullptr;
sportstechbike *sportsTechBike = nullptr;
sportstechelliptical *sportsTechElliptical = nullptr;
sportsplusbike *sportsPlusBike = nullptr;
sportsplusrower *sportsPlusRower = nullptr;
inspirebike *inspireBike = nullptr;
snodebike *snodeBike = nullptr;
eslinkertreadmill *eslinkerTreadmill = nullptr;
@@ -235,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;
@@ -242,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;
@@ -254,6 +278,7 @@ class bluetooth : public QObject, public SignalHandler {
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
ypooelliptical *ypooElliptical = nullptr;
ziprotreadmill *ziproTreadmill = nullptr;
kineticinroadbike *kineticInroadBike = nullptr;
strydrunpowersensor *powerTreadmill = nullptr;
eliterizer *eliteRizer = nullptr;
elitesterzosmart *eliteSterzoSmart = nullptr;
@@ -266,6 +291,7 @@ class bluetooth : public QObject, public SignalHandler {
QList<eliteariafan *> eliteAriaFan;
QList<zwiftclickremote* > zwiftPlayDevice;
zwiftclickremote* zwiftClickRemote = nullptr;
sramaxscontroller* sramAXSController = nullptr;
QString filterDevice = QLatin1String("");
bool testResistance = false;
@@ -274,7 +300,7 @@ class bluetooth : public QObject, public SignalHandler {
bool noConsole = false;
bool logs = true;
uint32_t pollDeviceTime = 200;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool forceHeartBeltOffForTimeout = false;
@@ -299,6 +325,8 @@ class bluetooth : public QObject, public SignalHandler {
bool eliteRizerAvaiable();
bool eliteSterzoSmartAvaiable();
bool fitmetriaFanfitAvaiable();
bool zwiftDeviceAvaiable();
bool sramDeviceAvaiable();
bool fitmetria_fanfit_isconnected(QString name);
#ifdef Q_OS_WIN
@@ -337,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

@@ -21,7 +21,7 @@ bluetoothdevice::~bluetoothdevice() {
}
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
void bluetoothdevice::start() { requestStart = 1; }
void bluetoothdevice::start() { requestStart = 1; lastStart = QDateTime::currentMSecsSinceEpoch(); }
void bluetoothdevice::stop(bool pause) {
requestStop = 1;
if (pause)
@@ -169,7 +169,7 @@ void bluetoothdevice::setVirtualDevice(virtualdevice *virtualDevice, VIRTUAL_DEV
}
// keiser m3i has a separate management of this, so please check it
void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
void bluetoothdevice::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));
@@ -181,15 +181,15 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
bool power_as_treadmill =
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
if(deviceType() == bluetoothdevice::BIKE)
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")) == false &&
!power_as_bike && !power_as_treadmill)
watt_calc = false;
if(deviceType() == bluetoothdevice::BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
if (!_firstUpdate && !paused) {
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {

View File

@@ -216,6 +216,18 @@ class bluetoothdevice : public QObject {
*/
metric wattsMetric();
/**
* @brief wattsMetricforUi Show the wattage applying averaging in case the user requested this. Units: watts
*/
double wattsMetricforUI() {
QSettings settings;
bool power5s = settings.value(QZSettings::power_avg_5s, QZSettings::default_power_avg_5s).toBool();
if (power5s)
return wattsMetric().average5s();
else
return wattsMetric().value();
}
/**
* @brief changeFanSpeed Tries to change the fan speed.
* @param speed The requested fan speed. Units: depends on device
@@ -401,7 +413,7 @@ class bluetoothdevice : public QObject {
*/
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL };
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE };
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
/**
@@ -531,6 +543,12 @@ class bluetoothdevice : public QObject {
int8_t requestDecreaseFan = -1;
double requestFanSpeed = -1;
int64_t lastStart = 0;
int64_t lastStop = 0;
metric RequestedPower;
int16_t requestPower = -1;
/**
* @brief m_difficult The current difficulty gain. Units: device dependent
*/
@@ -692,7 +710,7 @@ class bluetoothdevice : public QObject {
* @param watt_calc ??
* @param watts ?. Unit: watts
*/
void update_metrics(bool watt_calc, const double watts);
void update_metrics(bool watt_calc, const double watts, const bool from_accessory = false);
/**
* @brief update_hr_from_external Updates heart rate from Garmin Companion App or Apple Watch

View File

@@ -59,9 +59,6 @@ class bowflext216treadmill : public treadmill {
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
int64_t lastStart = 0;
int64_t lastStop = 0;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;

View File

@@ -61,9 +61,6 @@ class bowflextreadmill : public treadmill {
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
int64_t lastStart = 0;
int64_t lastStop = 0;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;

View File

@@ -13,7 +13,7 @@
using namespace std::chrono_literals;
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);

View File

@@ -32,7 +32,7 @@
class computrainerbike : public bike {
Q_OBJECT
public:
computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
computrainerbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
@@ -56,7 +56,7 @@ class computrainerbike : public bike {
QTimer *refresh;
virtualbike *virtualBike = nullptr;
uint8_t counterPoll = 0;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;

View File

@@ -28,12 +28,6 @@ crossrope::crossrope(uint32_t pollDeviceTime, bool noConsole, bool noHeartServic
this->noConsole = noConsole;
this->noHeartService = noHeartService;
if (forceInitSpeed > 0)
lastSpeed = forceInitSpeed;
if (forceInitInclination > 0)
lastInclination = forceInitInclination;
refresh = new QTimer(this);
initDone = false;
connect(refresh, &QTimer::timeout, this, &crossrope::update);
@@ -82,17 +76,17 @@ void crossrope::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
void crossrope::updateDisplay(uint16_t elapsed) {}
void crossrope::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
if (m_control == nullptr || m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
qDebug() << m_control->state() << bluetoothDevice.isValid() << gattCommunicationChannelService
<< gattWriteCharacteristic.isValid() << initDone << requestSpeed << requestInclination;
<< gattWriteCharacteristic.isValid() << initDone;
if (initRequest) {
initRequest = false;
btinit((lastSpeed > 0 ? true : false));
btinit((false));
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() && initDone) {
QSettings settings;
@@ -107,11 +101,7 @@ void crossrope::update() {
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
@@ -133,6 +123,8 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
QDateTime now = QDateTime::currentDateTime();
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
emit debug(QStringLiteral(" << ") + QString::number(value.length()) + QStringLiteral(" ") + value.toHex(' '));
@@ -142,11 +134,29 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
return;
double steps = (double)(uint16_t)((newValue.at(1) << 8) | ((uint8_t)newValue.at(2)));
if(steps != StepCount.value()) {
Cadence = (steps - StepCount.value()) / fabs(StepCount.valueChanged().msecsTo(QDateTime::currentDateTime())) * 60000.0;
StepCount = steps;
if(steps != JumpsCount.value()) {
CadenceRaw = (steps - JumpsCount.value()) / fabs(JumpsCount.valueChanged().msecsTo(now)) * 60000.0;
JumpsSequence = JumpsSequence.value() + 1;
Cadence = CadenceRaw.average20s();
JumpsCount = steps;
Speed = Cadence.value() * 0.15; // (speed emulated)
Distance += ((Speed.value() / 3600000.0) *
((double)lastTimeCharacteristicChanged.msecsTo(now)));
} else if(abs(Cadence.lastChanged().secsTo(now)) > 2) {
CadenceRaw = 0;
Cadence = 0;
JumpsSequence = 0;
Speed = 0;
}
Speed = (newValue.at(5) << 8) | ((uint8_t)newValue.at(6));
if (watts(weight))
KCal +=
((((0.048 * ((double)watts(weight)) + 1.19) *
weight * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
#ifdef Q_OS_ANDROID
@@ -160,7 +170,7 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
}
}
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Current Step Count: ") + QString::number(StepCount.value()));
emit debug(QStringLiteral("Current Jumps Count: ") + QString::number(JumpsCount.value()));
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
// debug("Current Distance: " + QString::number(distance));
@@ -306,3 +316,12 @@ bool crossrope::connected() {
return m_control->state() == QLowEnergyController::DiscoveredState;
}
uint16_t crossrope::watts(double weight) {
QSettings settings;
double power_per_kg = 1.5;
double cadence_efficiency = 0.1;
// Power calculation
double power = (power_per_kg * weight) + (cadence_efficiency * Cadence.value());
return power;
}

View File

@@ -26,18 +26,17 @@
#include <QDateTime>
#include <QObject>
#include "treadmill.h"
#include "jumprope.h"
class crossrope : public treadmill {
class crossrope : public jumprope {
Q_OBJECT
public:
crossrope(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected() override;
bool canHandleSpeedChange() override { return false; }
bool canHandleInclineChange() override { return false; }
private:
uint16_t watts(double weight) override;
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,
@@ -52,9 +51,6 @@ class crossrope : public treadmill {
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
int64_t lastStart = 0;
int64_t lastStop = 0;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
@@ -64,6 +60,8 @@ class crossrope : public treadmill {
bool initDone = false;
bool initRequest = false;
metric CadenceRaw;
Q_SIGNALS:
void disconnected();
void debug(QString string);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
#ifndef CYCLEOPSPHANTOMBIKE_H
#define CYCLEOPSPHANTOMBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "devices/bike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class cycleopsphantombike : public bike {
Q_OBJECT
public:
cycleopsphantombike(bool noWriteResistance, bool noHeartService);
void changePower(int32_t power) override;
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
private:
enum class ControlMode : uint8_t {
Headless = 0x00,
ManualPower = 0x01,
ManualSlope = 0x02,
PowerRange = 0x03,
WarmUp = 0x04,
RollDown = 0x05
};
enum class ControlStatus : uint8_t {
SpeedOkay = 0x00,
SpeedUp = 0x01,
SpeedDown = 0x02,
RollDownInitializing = 0x03,
RollDownInProcess = 0x04,
RollDownPassed = 0x05,
RollDownFailed = 0x06
};
void setControlMode(ControlMode mode, int16_t parameter1 = 0, int16_t parameter2 = 0);
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceInclination(double inclination);
uint16_t watts() override;
double bikeResistanceToPeloton(double resistance);
void setUserConfiguration(double wheelDiameter, double gearRatio);
QTimer *refresh;
const int max_resistance = 100;
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyCharacteristic gattWriteCharCustomId;
QLowEnergyService *gattPowerService = nullptr;
QLowEnergyService *gattCustomService;
// 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

@@ -0,0 +1,495 @@
#include "deerruntreadmill.h"
#include "virtualdevices/virtualbike.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
using namespace std::chrono_literals;
deerruntreadmill::deerruntreadmill(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;
if (forceInitSpeed > 0) {
lastSpeed = forceInitSpeed;
}
if (forceInitInclination > 0) {
lastInclination = forceInitInclination;
}
refresh = new QTimer(this);
initDone = false;
connect(refresh, &QTimer::timeout, this, &deerruntreadmill::update);
refresh->start(pollDeviceTime);
}
void deerruntreadmill::writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data,
uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
if (wait_for_response) {
connect(this, &deerruntreadmill::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 (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
emit debug(QStringLiteral("writeCharacteristic error because the connection is closed"));
return;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(characteristic, *writeBuffer);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
QStringLiteral(" // ") + info);
}
loop.exec();
if (timeout.isActive() == false) {
emit debug(QStringLiteral(" exit for timeout"));
}
}
uint8_t deerruntreadmill::calculateXOR(uint8_t arr[], size_t size) {
uint8_t result = 0;
if (size < 7) {
qDebug() << QStringLiteral("array too small");
return 0;
}
for (size_t i = 5; i <= size - 3; i++) {
result ^= arr[i];
}
return result;
}
void deerruntreadmill::forceSpeed(double requestSpeed) {
QSettings settings;
uint8_t writeSpeed[] = {0x4d, 0x00, 0xc9, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd8, 0x43};
writeSpeed[2] = pollCounter;
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
}
void deerruntreadmill::forceIncline(double requestIncline) {
}
void deerruntreadmill::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
void deerruntreadmill::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit((lastSpeed > 0 ? true : false));
} else if (/*bluetoothDevice.isValid() &&*/
m_control->state() == QLowEnergyController::DiscoveredState && gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() && gattNotifyCharacteristic.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();
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, &deerruntreadmill::debug);
connect(virtualTreadMill, &virtualtreadmill::changeInclination, this,
&deerruntreadmill::changeInclinationRequested);
this->setVirtualDevice(virtualTreadMill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
debug("creating virtual bike interface...");
auto virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&deerruntreadmill::changeInclinationRequested);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
firstInit = 1;
}
}
// ********************************************************************************************************
// debug("Domyos Treadmill RSSI " + QString::number(bluetoothDevice.rssi()));
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
{
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
} else 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;
} else if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
// should be:
// 0x49 = inited
// 0x8a = tape stopped after a pause
/*if (lastState == 0x49)*/ {
uint8_t initData2[] = {0x4d, 0x00, 0x0c, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0x2a, 0x43};
initData2[2] = pollCounter;
writeCharacteristic(gattWriteCharacteristic, initData2, sizeof(initData2), QStringLiteral("start"),
false, true);
} /*else {
uint8_t pause[] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x07};
writeCharacteristic(gattWriteCharacteristic, pause, sizeof(pause), QStringLiteral("pause"), false,
true);
}*/
requestStart = -1;
emit tapeStarted();
} else if (requestStop != -1) {
emit debug(QStringLiteral("stopping... ") + paused);
/*if (lastState == PAUSED) {
uint8_t pause[] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x07};
writeCharacteristic(gattWriteCharacteristic, pause, sizeof(pause), QStringLiteral("pause"), false,
true);
} else*/ {
uint8_t stop[] = {0x4d, 0x00, 0x48, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x50, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd6, 0x43};
stop[2] = pollCounter;
writeCharacteristic(gattWriteCharacteristic, stop, sizeof(stop), QStringLiteral("stop"), false,
true);
}
requestStop = -1;
} else {
uint8_t poll[] = {0x4d, 0x00, 0x00, 0x05, 0x6a, 0x05, 0xfd, 0xf8, 0x43};
poll[2] = pollCounter;
writeCharacteristic(gattWriteCharacteristic, poll, sizeof(poll), QStringLiteral("poll"), false,
true);
}
pollCounter++;
/*if (requestFanSpeed != -1) {
emit debug(QStringLiteral("changing fan speed..."));
sendChangeFanSpeed(requestFanSpeed);
requestFanSpeed = -1;
}
if (requestIncreaseFan != -1) {
emit debug(QStringLiteral("increasing fan speed..."));
sendChangeFanSpeed(FanSpeed + 1);
requestIncreaseFan = -1;
} else if (requestDecreaseFan != -1) {
emit debug(QStringLiteral("decreasing fan speed..."));
sendChangeFanSpeed(FanSpeed - 1);
requestDecreaseFan = -1;
}*/
}
}
}
void deerruntreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void deerruntreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
Q_UNUSED(characteristic);
QByteArray value = newValue;
QDateTime now = QDateTime::currentDateTime();
emit debug(QStringLiteral(" << ") + QString::number(value.length()) + QStringLiteral(" ") + value.toHex(' '));
emit packetReceived();
if (newValue.length() < 51)
return;
lastPacket = value;
// lastState = value.at(0);
double speed = ((double)(((value[9] << 8) & 0xff) + value[10]) / 100.0);
double incline = 0.0;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
uint8_t heart = 0;
if (heart == 0) {
update_hr_from_external();
} else
Heart = heart;
}
}
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(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((speed / (double)3600.0) /
((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(now))));
lastTimeCharacteristicChanged = now;
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
emit debug(QStringLiteral("Current incline: ") + QString::number(incline));
emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
// emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
// emit debug(QStringLiteral("Current Distance: ") + QString::number(distance));
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
if (Speed.value() != speed) {
emit speedChanged(speed);
}
Speed = speed;
if (Inclination.value() != incline) {
emit inclinationChanged(0, incline);
}
Inclination = incline;
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (speed > 0) {
lastSpeed = speed;
lastInclination = incline;
}
firstCharacteristicChanged = false;
}
void deerruntreadmill::btinit(bool startTape) {
initDone = true;
}
double deerruntreadmill::minStepInclination() { return 1.0; }
void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff1);
QBluetoothUuid _gattNotifyCharacteristicId((quint16)0xfff2);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
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);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotifyCharacteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&deerruntreadmill::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&deerruntreadmill::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &deerruntreadmill::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&deerruntreadmill::descriptorWritten);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void deerruntreadmill::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void deerruntreadmill::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void deerruntreadmill::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
emit debug(QStringLiteral("serviceScanDone"));
auto services_list = m_control->services();
emit debug("Services found:");
for (const QBluetoothUuid &s : qAsConst(services_list)) {
emit debug(s.toString());
}
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService) {
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&deerruntreadmill::stateChanged);
gattCommunicationChannelService->discoverDetails();
} else {
emit debug(QStringLiteral("error on find Service"));
}
}
void deerruntreadmill::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("deerruntreadmill::errorService ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void deerruntreadmill::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("deerruntreadmill::error ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void deerruntreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &deerruntreadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &deerruntreadmill::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &deerruntreadmill::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &deerruntreadmill::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."));
searchStopped = false;
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"));
searchStopped = false;
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
void deerruntreadmill::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();
}
}
bool deerruntreadmill::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void deerruntreadmill::searchingStop() { searchStopped = true; }

View File

@@ -0,0 +1,103 @@
#ifndef DEERRUNTREADMILL_H
#define DEERRUNTREADMILL_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 "devices/treadmill.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class deerruntreadmill : public treadmill {
Q_OBJECT
public:
deerruntreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected() override;
double minStepInclination() override;
private:
void forceSpeed(double requestSpeed);
void forceIncline(double requestIncline);
void btinit(bool startTape);
void writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len,
const QString &info, bool disable_log = false, bool wait_for_response = false);
void startDiscover();
uint8_t calculateXOR(uint8_t arr[], size_t size);
bool noConsole = false;
bool noHeartService = false;
uint32_t pollDeviceTime = 200;
uint8_t pollCounter = 0;
bool searchStopped = false;
uint8_t sec1Update = 0;
uint8_t firstInit = 0;
QByteArray lastPacket;
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
bool initDone = false;
bool initRequest = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
void debug(QString string);
void speedChanged(double speed);
void packetReceived();
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 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 // DEERRUNTREADMILL_H

View File

@@ -106,11 +106,12 @@ enum { DM_SERV_OP(DM_SERV_ENUMI_OP, 0, 0, 0) DM_SERV_I_NUM };
} \
} \
if (P2.size()) { \
QString dircon_id = QString("%1").arg(settings.value(QZSettings::dircon_id, \
QZSettings::default_dircon_id).toInt(), 4, 10, QChar('0')); \
DirconProcessor *processor = new DirconProcessor( \
P2, \
QString(QStringLiteral(NAME)) \
.replace(QStringLiteral("$uuid_hex$"), \
QString(QStringLiteral("%1")).arg(DM_MACHINE_##DESC, 4, 10, QLatin1Char('0'))), \
.replace(QStringLiteral("$uuid_hex$"), dircon_id), \
server_base_port + DM_MACHINE_##DESC, QString(QStringLiteral("%1")).arg(DM_MACHINE_##DESC), mac, \
this); \
QString servdesc; \
@@ -143,13 +144,13 @@ QString DirconManager::getMacAddress() {
#define DM_CHAR_NOTIF_BUILD_OP(UUID, P1, P2, P3) notif##UUID = new CharacteristicNotifier##UUID(P1, this);
DirconManager::DirconManager(bluetoothdevice *Bike, uint8_t bikeResistanceOffset, double bikeResistanceGain,
DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset, double bikeResistanceGain,
QObject *parent)
: QObject(parent) {
QSettings settings;
DirconProcessorService *service;
QList<DirconProcessorService *> services, proc_services;
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
uint8_t type = dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
: DM_MACHINE_TYPE_BIKE;
qDebug() << "Building Dircom Manager";

View File

@@ -34,7 +34,7 @@ class DirconManager : public QObject {
static QString getMacAddress();
public:
explicit DirconManager(bluetoothdevice *t, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
explicit DirconManager(bluetoothdevice *t, int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
QObject *parent = nullptr);
private slots:
void bikeProvider();

View File

@@ -38,7 +38,7 @@ public:
/**
* @brief Specifies a value that will be added to the resistance requests going to the bike, after the gain has been applied.
*/
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
/**
* @brief The resistance requests going to the bike should be multiplied by this, before adding the resistance offset.

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