Compare commits

...

386 Commits

Author SHA1 Message Date
Roberto Viola
379cf8d7de Update project.pbxproj 2024-10-29 08:02:08 +01:00
Roberto Viola
2f2989f90d completed? 2024-10-28 15:40:00 +01:00
Roberto Viola
6fac9770be qml finally saves the settings correctly 2024-10-28 15:09:13 +01:00
Roberto Viola
8d07d7c3f7 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-28 13:44:31 +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
f85f743499 i need to save the first 3 static objects and use it in the wahoo module 2024-10-28 11:11:05 +01:00
Roberto Viola
74c37f5624 Update gears.qml 2024-10-28 09:46:58 +01:00
Roberto Viola
d4dbaf5c57 Adidas Treadmill #1705 (#2705) 2024-10-28 09:45:49 +01:00
Roberto Viola
527396eafc Revert "Update gears.qml"
This reverts commit 0f149448b3.
2024-10-28 09:18:54 +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
0f149448b3 Update gears.qml 2024-10-25 16:28:33 +02: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
ed1599ca8e need to center the values in the table 2024-10-24 15:08:25 +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
89808ae65b fixing casting to double 2024-10-24 11:15:51 +02:00
Roberto Viola
2da194f073 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-24 11:00:46 +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
d712621b7b fixing formula 2024-10-23 11:47:05 +02:00
Roberto Viola
1c06260036 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-23 11:44:26 +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
2d1364497e Update virtualbike_zwift.swift 2024-10-22 15:40:04 +02:00
Roberto Viola
cc7757bfcd Update project.pbxproj 2024-10-22 15:38:44 +02:00
Roberto Viola
49c7a96c81 Merge branch 'master' into Custom-gearing-ranges/ratios-(Discussion-#2671) 2024-10-22 15:32:50 +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
05d598ffcf first raw version 2024-10-21 14:06:48 +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
326 changed files with 15241 additions and 5412 deletions

View File

@@ -1,3 +1,4 @@
# This is a basic workflow to help you get started with Actions
name: CI
@@ -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
@@ -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/
@@ -824,8 +825,24 @@ 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: Install dependencies
run: |
.\vcpkg\vcpkg install protobuf protobuf-c abseil
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
@@ -839,6 +856,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,7 +867,10 @@ 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
@@ -860,10 +881,11 @@ jobs:
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 +905,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 +952,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 +998,24 @@ 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: Install dependencies
run: |
.\vcpkg\vcpkg install protobuf protobuf-c abseil
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 ..
@@ -993,6 +1034,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,11 +1048,179 @@ 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
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
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: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-binary
path: src/qdomyos-zwift
raspberry-pi-build-and-image-64bit:
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 64-bit
uses: docker://arm64v8/debian:bullseye
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
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: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-binary
path: src/qdomyos-zwift
- name: Download and expand Raspberry Pi OS image
run: |
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-05-03/2023-05-03-raspios-bullseye-arm64-lite.img.xz
xz -d 2023-05-03-raspios-bullseye-arm64-lite.img.xz
ORIGINAL_SIZE=$(stat -c %s 2023-05-03-raspios-bullseye-arm64-lite.img)
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
truncate -s $NEW_SIZE 2023-05-03-raspios-bullseye-arm64-lite.img
sudo apt-get update
sudo apt-get install -y parted
sudo parted 2023-05-03-raspios-bullseye-arm64-lite.img resizepart 2 100%
- name: Mount Raspberry Pi image
run: |
sudo apt-get install -y kpartx qemu-user-static
LOOP_DEVICE=$(sudo losetup -f --show 2023-05-03-raspios-bullseye-arm64-lite.img)
echo "Loop device is $LOOP_DEVICE"
sudo kpartx -av $LOOP_DEVICE
sudo mkdir -p /mnt/raspbian
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
- name: Install Qt and dependencies on Raspberry Pi image
run: |
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
apt-get update
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
EOF
- name: Copy binary to Raspberry Pi image
run: |
sudo cp src/qdomyos-zwift /mnt/raspbian/home/pi/
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
- name: Setup auto-start for qdomyos-zwift
run: |
echo '[Unit]
Description=QDomyos-Zwift
After=multi-user.target
[Service]
ExecStart=/home/pi/qdomyos-zwift
User=pi
Environment=DISPLAY=:0
[Install]
WantedBy=multi-user.target' | sudo tee /mnt/raspbian/etc/systemd/system/qdomyos-zwift.service
sudo chroot /mnt/raspbian systemctl enable qdomyos-zwift.service
- name: Unmount Raspberry Pi image
run: |
sudo umount /mnt/raspbian
sudo kpartx -d ${{ env.LOOP_DEVICE }}
sudo losetup -d ${{ env.LOOP_DEVICE }}
- name: Compress modified Raspberry Pi image
run: |
xz -z 2023-05-03-raspios-bullseye-arm64-lite.img
- name: Upload Raspberry Pi image as artifact
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-image
path: 2023-05-03-raspios-bullseye-arm64-lite.img.xz
upload_to_release:
permissions: write-all
runs-on: ubuntu-20.04
@@ -1018,7 +1228,7 @@ jobs:
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-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 +1254,9 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/*
raspberry-pi-64bit-binary/*
2023-05-03-raspios-bullseye-arm64-lite.img.xz

View File

@@ -152,6 +152,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 */; };
@@ -174,6 +176,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 */; };
@@ -352,8 +356,12 @@
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 */; };
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 */; };
@@ -916,6 +924,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>"; };
@@ -951,6 +962,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>"; };
@@ -1222,9 +1236,15 @@
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; };
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>"; };
@@ -1300,6 +1320,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>"; };
@@ -2058,6 +2079,19 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
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 */,
@@ -3276,6 +3310,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 */,
@@ -3325,6 +3360,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 */,
@@ -3347,6 +3383,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 */,
@@ -3384,6 +3421,7 @@
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 */,
@@ -3407,6 +3445,7 @@
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 */,
@@ -3494,6 +3533,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 */,
@@ -3558,6 +3598,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 */,
@@ -3603,6 +3644,7 @@
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 */,
@@ -4026,7 +4068,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4105,7 +4147,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",
@@ -4217,7 +4259,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4298,7 +4340,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",
@@ -4444,7 +4486,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4469,7 +4511,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";
@@ -4540,7 +4582,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4561,7 +4603,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";
@@ -4632,7 +4674,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4677,7 +4719,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";
@@ -4746,7 +4788,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 810;
CURRENT_PROJECT_VERSION = 920;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4787,7 +4829,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

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

61
src/CRC16IBM.h Normal file
View File

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

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
@@ -86,6 +86,32 @@ HomeForm{
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
}
property var locationServiceRequsted: false
MessageDialog {
text: "Permissions Required"
informativeText: "QZ requires both Bluetooth and Location Services to be enabled.\nLocation Services are necessary on Android to allow the app to find Bluetooth devices.\nThe GPS will not be used.\n\nWould you like to enable them?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {locationServiceRequsted = true; rootItem.enableLocationServices()}
visible: !rootItem.locationServices() && !locationServiceRequsted
}
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 +127,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

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.61" android:versionCode="814" 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.1" android:versionCode="908" 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 -->
@@ -79,7 +79,7 @@
android:name=".ForegroundService"
android:enabled="true"
android:foregroundServiceType="connectedDevice"
android:exported="true"></service>
android:exported="false"></service>
<service
android:name=".WearableMessageListenerService"
android:enabled="true"
@@ -98,6 +98,7 @@
<service android:name="com.cgutman.androidremotedebugger.service.ShellService"
android:enabled="true"
android:foregroundServiceType="connectedDevice"
android:exported="false" >
</service>
@@ -110,6 +111,12 @@
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>
@@ -118,6 +125,7 @@
<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"/>
@@ -128,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,7 +27,7 @@ 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'

Binary file not shown.

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;
@@ -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), Context.RECEIVER_NOT_EXPORTED);
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,6 +9,8 @@ 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";
@@ -32,8 +34,18 @@ public class ForegroundService extends Service {
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.build();
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
startForeground(1, notification, serviceType);
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, Context.RECEIVER_NOT_EXPORTED);
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

@@ -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, Context.RECEIVER_NOT_EXPORTED);
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,45 @@
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;
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) {
if (instance == null) {
instance = new MediaButtonReceiver();
}
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
Log.d("MediaButtonReceiver", "registerReceiver");
}
public static void unregisterReceiver(Context context) {
if (instance != null) {
context.unregisterReceiver(instance);
instance = null;
}
}
}

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 {
@@ -299,8 +301,18 @@ public class ScreenCaptureService extends Service {
if (isStartCommand(intent)) {
// create notification
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
startForeground(notification.first, notification.second, serviceType);
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, Context.RECEIVER_NOT_EXPORTED);
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

@@ -23,6 +23,7 @@ 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 {
@@ -101,11 +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(), serviceType);
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

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

@@ -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,7 @@ 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();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
QStringLiteral(" enabled ") + force_resistance;
@@ -60,9 +61,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,11 +84,32 @@ 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;
if(gears_zwift_ratio && (gears > 24 || gears < 1)) {
qDebug() << "new gear value ignored because of gears_zwift_ratio setting!";
return;
}
m_gears = gears;
if(homeform::singleton()) {
homeform::singleton()->updateGearsValue();
}
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);
@@ -306,3 +330,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

@@ -36,8 +36,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,10 +60,18 @@ 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();
@@ -73,11 +83,9 @@ class bike : public bluetoothdevice {
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

View File

@@ -18,7 +18,7 @@ bluetooth::bluetooth(const discoveryoptions &options)
options.bikeResistanceGain, options.startDiscovery) {}
bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistance, bool noHeartService,
uint32_t pollDeviceTime, bool noConsole, bool testResistance, uint8_t bikeResistanceOffset,
uint32_t pollDeviceTime, bool noConsole, bool testResistance, int8_t bikeResistanceOffset,
double bikeResistanceGain, bool startDiscovery) {
QSettings settings;
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
@@ -107,9 +107,12 @@ void bluetooth::finished() {
debug(QStringLiteral("BTLE scanning finished"));
QSettings settings;
bool antbike =
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
QString nordictrack_2950_ip =
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
QString proform_elliptical_ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
bool fake_bike =
settings.value(QZSettings::applewatch_fakedevice, QZSettings::default_applewatch_fakedevice).toBool();
bool fakedevice_elliptical =
@@ -118,7 +121,7 @@ void bluetooth::finished() {
bool fakedevice_treadmill =
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
// wifi devices on windows
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill) {
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill || !proform_elliptical_ip.isEmpty() || antbike) {
// faking a bluetooth device
qDebug() << "faking a bluetooth device for nordictrack_2950_ip";
deviceDiscovered(QBluetoothDeviceInfo());
@@ -164,10 +167,14 @@ void bluetooth::finished() {
bool fitmetriaFanfitFound =
!settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
bool zwiftDeviceFound =
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
if ((!heartRateBeltFound && !heartRateBeltAvaiable()) || (!ftmsAccessoryFound && !ftmsAccessoryAvaiable()) ||
(!cscFound && !cscSensorAvaiable()) || (!powerSensorFound && !powerSensorAvaiable()) ||
(!eliteRizerFound && !eliteRizerAvaiable()) || (!eliteSterzoSmartFound && !eliteSterzoSmartAvaiable()) ||
(!fitmetriaFanfitFound && !fitmetriaFanfitAvaiable())) {
(!fitmetriaFanfitFound && !fitmetriaFanfitAvaiable()) ||
(!zwiftDeviceFound && !zwiftDeviceAvaiable())) {
// force heartRateBelt off
forceHeartBeltOffForTimeout = true;
@@ -274,6 +281,20 @@ bool bluetooth::fitmetriaFanfitAvaiable() {
return false;
}
bool bluetooth::zwiftDeviceAvaiable() {
uint8_t count = 0;
Q_FOREACH (QBluetoothDeviceInfo b, devices) {
if (b.name().toUpper().startsWith("ZWIFT ")) {
count++;
if(count >= 2)
return true;
}
}
return false;
}
bool bluetooth::powerSensorAvaiable() {
QSettings settings;
@@ -371,6 +392,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::ftms_accessory_name, QZSettings::default_ftms_accessory_name).toString();
bool heartRateBeltFound = heartRateBeltName.startsWith(QStringLiteral("Disabled"));
bool ftmsAccessoryFound = ftmsAccessoryName.startsWith(QStringLiteral("Disabled"));
bool zwiftDeviceFound =
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
bool fitmetriaFanfitFound =
!settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
bool toorx_ftms = settings.value(QZSettings::toorx_ftms, QZSettings::default_toorx_ftms).toBool();
@@ -383,6 +406,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::enerfit_SPX_9500, QZSettings::default_enerfit_SPX_9500).toBool() ||
settings.value(QZSettings::toorx_srx_3500, QZSettings::default_toorx_srx_3500).toBool() ||
settings.value(QZSettings::hop_sport_hs_090h_bike, QZSettings::default_hop_sport_hs_090h_bike).toBool() ||
settings.value(QZSettings::toorx_bike_srx_500, QZSettings::default_toorx_bike_srx_500).toBool() ||
settings.value(QZSettings::hertz_xr_770, QZSettings::default_hertz_xr_770).toBool()) &&
!toorx_ftms;
bool snode_bike = settings.value(QZSettings::snode_bike, QZSettings::default_snode_bike).toBool();
@@ -422,9 +446,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QString proformtdf1ip = settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString();
QString proformtreadmillip =
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
bool antbike_setting =
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
QString nordictrack_2950_ip =
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
QString proform_elliptical_ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
QString computrainerSerialPort =
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
QString csaferowerSerialPort = settings.value(QZSettings::csafe_rower, QZSettings::default_csafe_rower).toString();
@@ -457,6 +484,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
fitmetriaFanfitFound = fitmetriaFanfitAvaiable();
}
if (!zwiftDeviceFound) {
zwiftDeviceFound = zwiftDeviceAvaiable();
}
if (!ftmsAccessoryFound) {
ftmsAccessoryFound = ftmsAccessoryAvaiable();
@@ -588,7 +619,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
#endif
bool searchDevices = (heartRateBeltFound && ftmsAccessoryFound && cscFound && powerSensorFound && eliteRizerFound &&
eliteSterzoSmartFound && fitmetriaFanfitFound) ||
eliteSterzoSmartFound && fitmetriaFanfitFound && zwiftDeviceFound) ||
forceHeartBeltOffForTimeout;
if (searchDevices) {
@@ -622,6 +653,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit deviceConnected(b);
connect(fakeBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
connect(fakeBike, &fakebike::inclinationChanged, this, &bluetooth::inclinationChanged);
connect(fakeBike, &fakebike::debug, this, &bluetooth::debug);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
// #358
@@ -636,6 +668,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(fakeElliptical, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
connect(fakeElliptical, &fakeelliptical::inclinationChanged, this, &bluetooth::inclinationChanged);
connect(fakeElliptical, &fakeelliptical::debug, this, &bluetooth::debug);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
// #358
@@ -649,6 +682,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit deviceConnected(b);
connect(fakeRower, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
connect(fakeRower, &fakerower::inclinationChanged, this, &bluetooth::inclinationChanged);
connect(fakeRower, &fakerower::debug, this, &bluetooth::debug);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
// #358
@@ -663,6 +697,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(fakeTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
connect(fakeTreadmill, &faketreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
connect(fakeTreadmill, &faketreadmill::debug, this, &bluetooth::debug);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
// #358
@@ -731,6 +766,19 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
}
this->signalBluetoothDeviceConnected(csafeRower);
#endif
} else if (antbike_setting && !antBike) {
this->stopDiscovery();
antBike = new antbike(noWriteResistance, noHeartService, false);
emit deviceConnected(b);
connect(antBike, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(antBike, &antbike::debug, this, &bluetooth::debug);
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(antBike);
} else if (!proformtreadmillip.isEmpty() && !proformWifiTreadmill) {
this->stopDiscovery();
proformWifiTreadmill = new proformwifitreadmill(noWriteResistance, noHeartService, bikeResistanceOffset,
@@ -773,6 +821,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(nordictrackifitadbBike);
} else if (!proform_elliptical_ip.isEmpty() && !nordictrackifitadbElliptical) {
this->stopDiscovery();
nordictrackifitadbElliptical = new nordictrackifitadbelliptical(noWriteResistance, noHeartService,
bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(nordictrackifitadbElliptical, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
connect(nordictrackifitadbElliptical, &nordictrackifitadbelliptical::debug, this, &bluetooth::debug);
// nordictrackifitadbTreadmill->deviceDiscovered(b);
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(nordictrackifitadbElliptical);
} else if (((csc_as_bike && b.name().startsWith(cscName)) ||
b.name().toUpper().startsWith(QStringLiteral("JOROTO-BK-"))) &&
!cscBike && filter) {
@@ -839,7 +901,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(domyosRower);
} else if (b.name().startsWith(QStringLiteral("Domyos-Bike")) &&
} else if ((b.name().startsWith(QStringLiteral("Domyos-Bike")) && (!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool())) &&
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -892,6 +954,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(domyosElliptical);
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
this->setLastBluetoothDevice(b);
@@ -1044,7 +1107,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (b.name().startsWith(QStringLiteral("Domyos")) &&
!b.name().startsWith(QStringLiteral("DomyosBr")) &&
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKING-")) && !domyos && !domyosElliptical && b.name().compare(ftms_treadmill, Qt::CaseInsensitive) &&
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && filter) {
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill &&
(!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) &&
filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
@@ -1110,9 +1175,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("KS-SC-BLR2C")) ||
!b.name().toUpper().compare(QStringLiteral("RE")) || // just "RE"
b.name().toUpper().startsWith(QStringLiteral("KS-H")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-F0")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-BLC")) || // Walkingpad C2 #1672
b.name().toUpper().startsWith(
QStringLiteral("KS-BLR"))) && // Treadmill KingSmith WalkingPad R2 Pro KS-HCR1AA
!(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) && // it's an FTMS one
!kingsmithR1ProTreadmill &&
!kingsmithR2Treadmill && filter) {
this->setLastBluetoothDevice(b);
@@ -1157,7 +1224,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("TRUE")) ||
b.name().toUpper().startsWith(QStringLiteral("ASSAULT TREADMILL ")) ||
(b.name().toUpper().startsWith(QStringLiteral("WDWAY")) && b.name().length() == 8) || // WdWay179
(b.name().toUpper().startsWith(QStringLiteral("TREADMILL")) && !gem_module_inclination && !deviceHasService(b, QBluetoothUuid((quint16)0x1814)))) &&
(b.name().toUpper().startsWith(QStringLiteral("TREADMILL")) && !gem_module_inclination && !deviceHasService(b, QBluetoothUuid((quint16)0x1814)) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)))) &&
!trueTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1250,22 +1317,28 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("TRX4500")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("MATRIXTF50")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("T01_")) || // FTMS
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
!b.name().contains('(') && !b.name().contains(')') && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) &&
horizon_treadmill_force_ftms) || // FTMS, TF-769DF2
((b.name().toUpper().startsWith(QStringLiteral("TOORX")) ||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")))) &&
!toorx_ftms && toorx_ftms_treadmill) ||
!b.name().compare(ftms_treadmill, Qt::CaseInsensitive) ||
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) ||
b.name().toUpper().startsWith(QStringLiteral("XT685")) ||
b.name().toUpper().startsWith(QStringLiteral("XT285")) ||
b.name().toUpper().startsWith(QStringLiteral("WELLFIT TM")) ||
b.name().toUpper().startsWith(QStringLiteral("XTERRA TR")) ||
b.name().toUpper().startsWith(QStringLiteral("T118_")) ||
b.name().toUpper().startsWith(QStringLiteral("EW-EP-")) || // Miweba MC700 ellipital Trainer #2419
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
b.name().toUpper().startsWith(QStringLiteral("FIT-")) || // FIT-1596
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
(b.name().toUpper().startsWith("SCHWINN 810")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // sports tech f37s treadmill #2412
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("TT8")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
@@ -1281,8 +1354,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) &&
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
) &&
!horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1419,6 +1492,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("MKSM")) || // MKSM3600036
(b.name().toUpper().startsWith("YS_C1_")) || // Yesoul C1H
(b.name().toUpper().startsWith("YS_G1_")) || // Yesoul S3
(b.name().toUpper().startsWith("YS_G1MPLUS")) || // Yesoul G1M Plus
(b.name().toUpper().startsWith("DS25-")) || // Bodytone DS25
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
(b.name().toUpper().startsWith("3G CARDIO ")) ||
@@ -1443,16 +1517,31 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("BIKE-")) ||
(b.name().toUpper().startsWith("SPAX-BK-")) ||
(b.name().toUpper().startsWith("YSV1")) ||
(b.name().toUpper().startsWith("VOLT") && b.name().length() == 4) ||
(b.name().toUpper().startsWith("VICTORY")) ||
(b.name().toUpper().startsWith("CECOTEC")) || // Cecotec DrumFit Indoor 10000 MagnoMotor Connected #2420
(b.name().toUpper().startsWith("WATTBIKE")) ||
(b.name().toUpper().startsWith("ZYCLEZBIKE")) ||
(b.name().toUpper().startsWith("WAVEFIT-")) ||
(b.name().toUpper().startsWith("WAVEFIT-")) ||
(b.name().toUpper().startsWith("KETTLERBLE")) ||
(b.name().toUpper().startsWith("JAS_C3")) ||
(b.name().toUpper().startsWith("SCH_190U")) ||
(b.name().toUpper().startsWith("RAVE WHITE")) ||
(b.name().toUpper().startsWith("DOMYOS-BIKING-")) ||
(b.name().startsWith(QStringLiteral("Domyos-Bike")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool()) ||
(b.name().toUpper().startsWith("F") && b.name().toUpper().endsWith("ARROW")) || // FI9110 Arrow, https://www.fitnessdigital.it/bicicletta-smart-bike-ion-fitness-arrow-connect/p/10022863/ IO Fitness Arrow
(b.name().toUpper().startsWith("ICSE") && b.name().length() == 4) ||
(b.name().toUpper().startsWith("FLX") && b.name().length() == 10) ||
(b.name().toUpper().startsWith("CSRB") && b.name().length() == 11) ||
(b.name().toUpper().startsWith("DU30-")) || // BodyTone du30
(b.name().toUpper().startsWith("BIKZU_")) ||
(b.name().toUpper().startsWith("WLT8828")) ||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
(b.name().toUpper().startsWith("HARISON-X15")) ||
(b.name().toUpper().startsWith("FEIVON V2")) ||
(b.name().toUpper().startsWith("FELVON V2")) ||
(b.name().toUpper().startsWith("JUSTO")) ||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
@@ -1475,7 +1564,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith("KICKR ROLLR") ||
(b.name().toUpper().startsWith("HAMMER ") && saris_trainer) ||
(b.name().toUpper().startsWith("WAHOO KICKR"))) &&
!wahooKickrSnapBike && filter) {
!wahooKickrSnapBike && !ftmsBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
wahooKickrSnapBike =
@@ -1503,8 +1592,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(horizonGr7Bike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
(b.name().toUpper().startsWith("TACX SATORI")) ||
(b.name().toUpper().startsWith("RACER S")) ||
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818))) ||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
!stagesBike && !ftmsBike && filter) {
@@ -1554,6 +1647,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("CR 00")) ||
b.name().toUpper().startsWith(QStringLiteral("KAYAKPRO")) ||
b.name().toUpper().startsWith(QStringLiteral("WHIPR")) ||
b.name().toUpper().startsWith(QStringLiteral("H-181-")) ||
b.name().toUpper().startsWith(QStringLiteral("S4 COMMS")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-WLT")) || // KS-WLT-W1
b.name().toUpper().startsWith(QStringLiteral("I-ROWER")) ||
@@ -1578,7 +1672,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith(QLatin1String("ECH-STRIDE")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-UK-")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-FR-")) ||
b.name().toUpper().startsWith(QLatin1String("STRIDE-")) ||
b.name().toUpper().startsWith(QLatin1String("STRIDE")) ||
b.name().toUpper().startsWith(QLatin1String("STRIDE6S-")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-SD-SPT"))) &&
!echelonStride && filter) {
this->setLastBluetoothDevice(b);
@@ -1754,6 +1849,22 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
sportsTechBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(sportsTechBike);
} else if (b.name().toUpper().startsWith(QStringLiteral("EW-EP-")) && !sportsTechElliptical && !horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
sportsTechElliptical = new sportstechelliptical(noWriteResistance, noHeartService, bikeResistanceOffset,
bikeResistanceGain);
// stateFileRead();
emit deviceConnected(b);
connect(sportsTechElliptical, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(echelonConnectSport, SIGNAL(disconnected()), this, SLOT(restart()));
connect(sportsTechElliptical, &sportstechelliptical::debug, this, &bluetooth::debug);
// connect(echelonConnectSport, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
// connect(echelonConnectSport, SIGNAL(inclinationChanged(double)), this,
// SLOT(inclinationChanged(double)));
sportsTechElliptical->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(sportsTechElliptical);
} else if ((b.name().toUpper().startsWith(QStringLiteral("CARDIOFIT")) ||
(b.name().toUpper().contains(QStringLiteral("CARE")) &&
b.name().length() == 11)) // CARE9040177 - Carefitness CV-351
@@ -1821,7 +1932,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
proformTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(proformTreadmill);
} else if (b.name().toUpper().startsWith(QStringLiteral("ESLINKER")) && !eslinkerTreadmill && filter) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER")) ||
b.name().toUpper().startsWith(QStringLiteral("ESLINKER"))) && !eslinkerTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
eslinkerTreadmill = new eslinkertreadmill(this->pollDeviceTime, noConsole, noHeartService);
@@ -1836,6 +1948,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
eslinkerTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(eslinkerTreadmill);
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT")) && !deerrunTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
deerrunTreadmill = new deerruntreadmill(this->pollDeviceTime, noConsole, noHeartService);
// stateFileRead();
emit deviceConnected(b);
connect(deerrunTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(proformtreadmill, SIGNAL(disconnected()), this, SLOT(restart()));
connect(deerrunTreadmill, &deerruntreadmill::debug, this, &bluetooth::debug);
// connect(proformtreadmill, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
// connect(proformtreadmill, SIGNAL(inclinationChanged(double)), this,
// SLOT(inclinationChanged(double)));
deerrunTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(deerrunTreadmill);
} else if (b.name().toUpper().startsWith(QStringLiteral("PAFERS_")) && !pafersTreadmill &&
(pafers_treadmill || pafers_treadmill_bh_iboxster_plus) && filter) {
this->setLastBluetoothDevice(b);
@@ -1930,7 +2057,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
mcfBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(mcfBike);
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
b.name().toUpper().startsWith(QStringLiteral("MASTERT40-")) ||
b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD")) ||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1941,7 +2070,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(toorx, &toorxtreadmill::debug, this, &bluetooth::debug);
toorx->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(toorx);
} else if ((b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT"))) && !iConceptBike &&
} else if (((b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT")) && !b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD"))) ||
b.name().toUpper().startsWith(QStringLiteral("BH-"))) && !iConceptBike &&
!iconcept_elliptical && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2019,6 +2149,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("FIT HI WAY")) ||
b.name().toUpper().startsWith(QStringLiteral("BIKZU_")) ||
b.name().toUpper().startsWith(QStringLiteral("PASYOU-")) ||
b.name().toUpper().startsWith(QStringLiteral("VIRTUFIT")) ||
((b.name().startsWith(QStringLiteral("TOORX")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
@@ -2053,7 +2184,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(ultraSportBike, &solebike::debug, this, &bluetooth::debug);
ultraSportBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(ultraSportBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("KEEP_BIKE_"))) && !keepBike && filter) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("KEEP_BIKE_")) ||
b.name().toUpper().startsWith(QStringLiteral("KEEP_CC_"))) && !keepBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
keepBike = new keepbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
@@ -2076,7 +2208,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
soleBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(soleBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("BFCP")) ||
(b.name().toUpper().startsWith(QStringLiteral("HT")) && b.name().length() == 11)) &&
(b.name().toUpper().startsWith(QStringLiteral("HT")) && (b.name().length() == 11 || b.name().length() == 12))) &&
!skandikaWiriBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2161,10 +2293,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !horizonTreadmill && !snode_bike && !fitplus_bike && !ftmsBike && !iconsole_elliptical) ||
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
!b.name().contains('(') && !b.name().contains(')')) ||
!b.name().contains('(') && !b.name().contains(')') && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("WINFITA"))) || // also FTMS
(b.name().startsWith(QStringLiteral("BF70")))) &&
!fitshowTreadmill && !iconsole_elliptical && filter) {
!fitshowTreadmill && !iconsole_elliptical && !horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
fitshowTreadmill = new fitshowtreadmill(this->pollDeviceTime, noConsole, noHeartService);
@@ -2319,7 +2451,8 @@ void bluetooth::connectedAndDiscovered() {
connect(heartRateBelt, &heartratebelt::debug, this, &bluetooth::debug);
connect(heartRateBelt, &heartratebelt::heartRate, this->device(), &bluetoothdevice::heartRate);
heartRateBelt->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested(b.name() + " (HR sensor) connected!");
break;
}
}
@@ -2406,6 +2539,8 @@ void bluetooth::connectedAndDiscovered() {
connect(cadenceSensor, &bluetoothdevice::cadenceChanged, this->device(),
&bluetoothdevice::cadenceSensor);
cadenceSensor->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested(b.name() + " (cadence sensor) connected!");
break;
}
}
@@ -2451,6 +2586,10 @@ void bluetooth::connectedAndDiscovered() {
&bluetoothdevice::verticalOscillationSensor);
powerSensorRun->deviceDiscovered(b);
}
if(homeform::singleton())
homeform::singleton()->setToastRequested(b.name() + " (power sensor) connected!");
break;
}
}
@@ -2518,6 +2657,8 @@ void bluetooth::connectedAndDiscovered() {
connect(zwiftClickRemote->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
connect(zwiftClickRemote->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
zwiftClickRemote->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Zwift Click Connected!");
break;
}
}
@@ -2525,22 +2666,27 @@ void bluetooth::connectedAndDiscovered() {
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("ZWIFT PLAY"))) && zwiftPlayDevice.size() < 2 && this->device() &&
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
} else {
qDebug() << "manufacturer not found for ZWIFT CLICK";
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
zwiftPlayDevice.length() == 0 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
}
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug);
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
zwiftPlayDevice.last()->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Zwift Play/Ride Connected!");
}
}
}
@@ -2759,6 +2905,11 @@ void bluetooth::restart() {
delete proformWifiBike;
proformWifiBike = nullptr;
}
if (antBike) {
delete antBike;
antBike = nullptr;
}
if (proformTelnetBike) {
delete proformTelnetBike;
@@ -2779,6 +2930,11 @@ void bluetooth::restart() {
delete nordictrackifitadbBike;
nordictrackifitadbBike = nullptr;
}
if (nordictrackifitadbElliptical) {
delete nordictrackifitadbElliptical;
nordictrackifitadbElliptical = nullptr;
}
if (powerBike) {
delete powerBike;
@@ -2970,6 +3126,11 @@ void bluetooth::restart() {
delete eslinkerTreadmill;
eslinkerTreadmill = nullptr;
}
if (deerrunTreadmill) {
delete deerrunTreadmill;
deerrunTreadmill = nullptr;
}
if (crossRope) {
delete crossRope;
@@ -3020,6 +3181,11 @@ void bluetooth::restart() {
delete sportsTechBike;
sportsTechBike = nullptr;
}
if (sportsTechElliptical) {
delete sportsTechElliptical;
sportsTechElliptical = nullptr;
}
if (sportsPlusBike) {
delete sportsPlusBike;
@@ -3193,10 +3359,14 @@ bluetoothdevice *bluetooth::device() {
return proformTelnetBike;
} else if (proformWifiTreadmill) {
return proformWifiTreadmill;
} else if (antBike) {
return antBike;
} else if (nordictrackifitadbTreadmill) {
return nordictrackifitadbTreadmill;
} else if (nordictrackifitadbBike) {
return nordictrackifitadbBike;
} else if (nordictrackifitadbElliptical) {
return nordictrackifitadbElliptical;
} else if (powerBike) {
return powerBike;
} else if (powerTreadmill) {
@@ -3297,6 +3467,8 @@ bluetoothdevice *bluetooth::device() {
return proformRower;
} else if (eslinkerTreadmill) {
return eslinkerTreadmill;
} else if (deerrunTreadmill) {
return deerrunTreadmill;
} else if (crossRope) {
return crossRope;
} else if (bowflexTreadmill) {
@@ -3317,6 +3489,8 @@ bluetoothdevice *bluetooth::device() {
return schwinn170Bike;
} else if (sportsTechBike) {
return sportsTechBike;
} else if (sportsTechElliptical) {
return sportsTechElliptical;
} else if (sportsPlusBike) {
return sportsPlusBike;
} else if (inspireBike) {

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"
@@ -35,6 +36,7 @@
#include "devices/concept2skierg/concept2skierg.h"
#include "devices/crossrope/crossrope.h"
#include "devices/cscbike/cscbike.h"
#include "devices/deeruntreadmill/deerruntreadmill.h"
#include "devices/domyosbike/domyosbike.h"
#include "devices/domyoselliptical/domyoselliptical.h"
#include "devices/domyosrower/domyosrower.h"
@@ -76,6 +78,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"
@@ -108,6 +111,7 @@
#include "devices/spirittreadmill/spirittreadmill.h"
#include "devices/sportsplusbike/sportsplusbike.h"
#include "devices/sportstechbike/sportstechbike.h"
#include "devices/sportstechelliptical/sportstechelliptical.h"
#include "devices/stagesbike/stagesbike.h"
#include "devices/renphobike/renphobike.h"
@@ -145,7 +149,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 +163,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;
@@ -172,6 +177,7 @@ class bluetooth : public QObject, public SignalHandler {
csaferower *csafeRower = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
deerruntreadmill *deerrunTreadmill = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
domyosrower *domyosRower = nullptr;
@@ -193,6 +199,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;
@@ -213,6 +220,7 @@ class bluetooth : public QObject, public SignalHandler {
horizongr7bike *horizonGr7Bike = nullptr;
schwinnic4bike *schwinnIC4Bike = nullptr;
sportstechbike *sportsTechBike = nullptr;
sportstechelliptical *sportsTechElliptical = nullptr;
sportsplusbike *sportsPlusBike = nullptr;
inspirebike *inspireBike = nullptr;
snodebike *snodeBike = nullptr;
@@ -274,7 +282,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 +307,7 @@ class bluetooth : public QObject, public SignalHandler {
bool eliteRizerAvaiable();
bool eliteSterzoSmartAvaiable();
bool fitmetriaFanfitAvaiable();
bool zwiftDeviceAvaiable();
bool fitmetria_fanfit_isconnected(QString name);
#ifdef Q_OS_WIN

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)

View File

@@ -531,6 +531,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
*/

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

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

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.

View File

@@ -13,7 +13,7 @@
using namespace std::chrono_literals;
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, uint8_t bikeResistanceOffset,
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
@@ -685,7 +685,41 @@ resistance_t domyosbike::resistanceFromPowerRequest(uint16_t power) {
uint16_t domyosbike::wattsFromResistance(double resistance) {
QSettings settings;
if (!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1)
if (settings.value(QZSettings::domyos_bike_500_profile_v2, QZSettings::default_domyos_bike_500_profile_v2).toBool()) {
switch ((int)resistance) {
case 1:
return (5.0 * Cadence.value()) / 9.5488;
case 2:
return (5.7 * Cadence.value()) / 9.5488;
case 3:
return (6.5 * Cadence.value()) / 9.5488;
case 4:
return (7.5 * Cadence.value()) / 9.5488;
case 5:
return (8.6 * Cadence.value()) / 9.5488;
case 6:
return (9.9 * Cadence.value()) / 9.5488;
case 7:
return (11.4 * Cadence.value()) / 9.5488;
case 8:
return (13.6 * Cadence.value()) / 9.5488;
case 9:
return (15.3 * Cadence.value()) / 9.5488;
case 10:
return (17.3 * Cadence.value()) / 9.5488;
case 11:
return (19.8 * Cadence.value()) / 9.5488;
case 12:
return (22.5 * Cadence.value()) / 9.5488;
case 13:
return (25.6 * Cadence.value()) / 9.5488;
case 14:
return (28.4 * Cadence.value()) / 9.5488;
case 15:
return (35.9 * Cadence.value()) / 9.5488;
}
return 0;
} else if (!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1)
.toBool() ||
resistance < 8)
return ((10.39 + 1.45 * (resistance - 1.0)) * (exp(0.028 * (currentCadence().value()))));

View File

@@ -37,7 +37,7 @@ class domyosbike : public bike {
Q_OBJECT
public:
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
resistance_t resistanceFromPowerRequest(uint16_t power) override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
@@ -74,7 +74,7 @@ class domyosbike : public bike {
bool noWriteResistance = false;
bool noHeartService = false;
bool testResistance = false;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool searchStopped = false;
uint8_t sec1Update = 0;

View File

@@ -15,7 +15,7 @@
using namespace std::chrono_literals;
domyoselliptical::domyoselliptical(bool noWriteResistance, bool noHeartService, bool testResistance,
uint8_t bikeResistanceOffset, double bikeResistanceGain) {
int8_t bikeResistanceOffset, double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);

View File

@@ -32,7 +32,7 @@ class domyoselliptical : public elliptical {
Q_OBJECT
public:
domyoselliptical(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
~domyoselliptical();
bool connected() override;
bool inclinationAvailableByHardware() override;
@@ -64,7 +64,7 @@ class domyoselliptical : public elliptical {
bool noWriteResistance = false;
bool noHeartService = false;
bool testResistance = false;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool searchStopped = false;
uint8_t sec1Update = 0;

View File

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

View File

@@ -36,7 +36,7 @@ class domyosrower : public rower {
Q_OBJECT
public:
domyosrower(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
~domyosrower();
bool connected() override;
@@ -72,7 +72,7 @@ class domyosrower : public rower {
bool noHeartService = false;
bool testResistance = false;
bool ftmsRower = false;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool searchStopped = false;
uint8_t sec1Update = 0;

View File

@@ -1,4 +1,5 @@
#include "echelonconnectsport.h"
#include "homeform.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
@@ -17,7 +18,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
@@ -469,7 +470,13 @@ void echelonconnectsport::serviceScanDone(void) {
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&echelonconnectsport::stateChanged);
gattCommunicationChannelService->discoverDetails();
if(gattCommunicationChannelService != nullptr) {
gattCommunicationChannelService->discoverDetails();
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
m_control->disconnectFromDevice();
}
}
void echelonconnectsport::errorService(QLowEnergyService::ServiceError err) {

View File

@@ -37,7 +37,7 @@
class echelonconnectsport : public bike {
Q_OBJECT
public:
echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
echelonconnectsport(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
@@ -65,7 +65,7 @@ class echelonconnectsport : public bike {
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;

View File

@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;

View File

@@ -35,7 +35,7 @@
class echelonrower : public rower {
Q_OBJECT
public:
echelonrower(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
echelonrower(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
resistance_t maxResistance() override{ return max_resistance; }
@@ -62,7 +62,7 @@ class echelonrower : public rower {
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;

View File

@@ -196,12 +196,22 @@ void echelonstride::update() {
uint8_t initData3[] = {0xf0, 0xb0, 0x01, 0x01, 0xa2};
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("start"), false, true);
if(stride4) {
uint8_t initData0[] = {0xf0, 0xa5, 0x00, 0x95};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
}
uint8_t initData4[] = {0xf0, 0xd0, 0x01, 0x00, 0xc1};
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("start"), false, false);
uint8_t initData5[] = {0xf0, 0xd0, 0x01, 0x11, 0xd2};
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("start"), false, false);
if(stride4) {
uint8_t initData0[] = {0xf0, 0xd3, 0x02, 0x01, 0xf4, 0xba};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
}
lastStart = QDateTime::currentMSecsSinceEpoch();
requestStart = -1;
emit tapeStarted();
@@ -285,6 +295,32 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
} else if (((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd0) {
writeCharacteristic((uint8_t *)newValue.constData(), newValue.length(), "reply to d0", false, false);
return;
} else if (((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd1 && stride4) {
double miles = 1;
if (settings.value(QZSettings::sole_treadmill_miles, QZSettings::default_sole_treadmill_miles).toBool())
miles = 1.60934;
// this line on iOS sometimes gives strange overflow values
// uint16_t convertedData = (((uint16_t)newValue.at(3)) << 8) | (uint16_t)newValue.at(4);
qDebug() << "speed1" << newValue.at(7);
uint16_t convertedData = (uint8_t)newValue.at(7);
qDebug() << "speed2" << convertedData;
convertedData = convertedData << 8;
qDebug() << "speed3" << convertedData;
convertedData = convertedData & 0xFF00;
qDebug() << "speed4" << convertedData;
convertedData = convertedData + (uint8_t)newValue.at(8);
qDebug() << "speed5" << convertedData;
Speed = (((double)convertedData) / 100.0) * miles;
if (Speed.value() > 0)
lastStart = 0;
else
lastStop = 0;
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
return;
}
/*if (newValue.length() != 21)
@@ -344,7 +380,17 @@ void echelonstride::btinit() {
uint8_t initData1[] = {0xf0, 0xa1, 0x00, 0x91};
uint8_t initData2[] = {0xf0, 0xa3, 0x00, 0x93};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
// stride4
uint8_t initDataStride4_0[] = {0xf0, 0xe0, 0xfd, 0x3e, 0x65, 0x48, 0xd5, 0x8d};
if(stride4) {
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true); // send a frame to wait the Value: f0e0728518586198
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, false);
writeCharacteristic(initDataStride4_0, sizeof(initDataStride4_0), QStringLiteral("init"), false, false);
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, false);
} else {
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
}
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
@@ -352,7 +398,9 @@ void echelonstride::btinit() {
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
if(!stride4)
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
initDone = true;
}
@@ -432,6 +480,12 @@ void echelonstride::error(QLowEnergyController::Error err) {
void echelonstride::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
if(bluetoothDevice.name().toUpper().startsWith("STRIDE4")) {
stride4 = true;
qDebug() << "STRIDE4 workaround enabled!";
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &echelonstride::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &echelonstride::serviceScanDone);

View File

@@ -68,9 +68,6 @@ class echelonstride : public treadmill {
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
int64_t lastStart = 0;
int64_t lastStop = 0;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
@@ -81,6 +78,8 @@ class echelonstride : public treadmill {
bool initDone = false;
bool initRequest = false;
bool stride4 = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -38,6 +38,40 @@ void elliptical::update_metrics(bool watt_calc, const double watts) {
_firstUpdate = false;
}
resistance_t elliptical::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
void elliptical::changePower(int32_t power) {
RequestedPower = power; // in order to paint in any case the request power on the charts
if (!autoResistanceEnable) {
qDebug() << QStringLiteral("changePower ignored because auto resistance is disabled");
return;
}
requestPower = power; // used by some bikes that have ERG mode builtin
QSettings settings;
bool force_resistance =
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
.toBool();
// bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool(); //Not used
// anywhere in code
double erg_filter_upper =
settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
double erg_filter_lower =
settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
double deltaDown = wattsMetric().value() - ((double)power);
double deltaUp = ((double)power) - wattsMetric().value();
qDebug() << QStringLiteral("filter ") + QString::number(deltaUp) + " " + QString::number(deltaDown) + " " +
QString::number(erg_filter_upper) + " " + QString::number(erg_filter_lower);
if (/*!ergModeSupported &&*/ force_resistance /*&& erg_mode*/ &&
(deltaUp > erg_filter_upper || deltaDown > erg_filter_lower)) {
resistance_t r = (resistance_t)resistanceFromPowerRequest(power);
changeResistance(r); // resistance start from 1
}
}
uint16_t elliptical::watts() {
QSettings settings;

View File

@@ -33,12 +33,14 @@ class elliptical : public bluetoothdevice {
void setGears(double d);
double gears();
virtual double minStepInclination() { return 0.5; }
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
public Q_SLOTS:
virtual void changeSpeed(double speed);
void changeResistance(resistance_t res) override;
void changeInclination(double grade, double inclination) override;
virtual void changeCadence(int16_t cad);
void changePower(int32_t power) override;
virtual void changeRequestedPelotonResistance(int8_t resistance);
signals:

View File

@@ -1,4 +1,5 @@
#include "eslinkertreadmill.h"
#include "homeform.h"
#include "keepawakehelper.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
@@ -7,9 +8,67 @@
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <QRandomGenerator>
using namespace std::chrono_literals;
class CRC8
{
public:
CRC8(quint8 polynomial = 0x07, quint8 init = 0x00, bool refIn = false, bool refOut = false, quint8 xorOut = 0x00)
: m_polynomial(polynomial), m_init(init), m_refIn(refIn), m_refOut(refOut), m_xorOut(xorOut)
{
generateTable();
}
quint8 calculate(const QByteArray &data)
{
quint8 crc = m_init;
for (char c : data) {
if (m_refIn)
c = reflect8(c);
crc = m_table[crc ^ static_cast<quint8>(c)];
}
if (m_refOut)
crc = reflect8(crc);
return crc ^ m_xorOut;
}
private:
quint8 m_polynomial;
quint8 m_init;
bool m_refIn;
bool m_refOut;
quint8 m_xorOut;
quint8 m_table[256];
void generateTable()
{
for (int i = 0; i < 256; ++i) {
quint8 crc = static_cast<quint8>(i);
for (int j = 0; j < 8; ++j) {
if (crc & 0x80)
crc = (crc << 1) ^ m_polynomial;
else
crc <<= 1;
}
m_table[i] = crc;
}
}
quint8 reflect8(quint8 value)
{
quint8 reflected = 0;
for (int i = 0; i < 8; ++i) {
if (value & 0x01)
reflected |= (1 << (7 - i));
value >>= 1;
}
return reflected;
}
};
eslinkertreadmill::eslinkertreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
@@ -48,7 +107,7 @@ void eslinkertreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len, con
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
QLowEnergyService::WriteWithoutResponse);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
@@ -71,7 +130,8 @@ void eslinkertreadmill::updateDisplay(uint16_t elapsed) {
writeCharacteristic(display, sizeof(display),
QStringLiteral("updateDisplay elapsed=") + QString::number(elapsed), false, false);
} else {
} else if (treadmill_type == ESANGLINKER){
}
}
@@ -116,6 +176,16 @@ void eslinkertreadmill::forceSpeed(double requestSpeed) {
writeCharacteristic(display, sizeof(display),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
} else if(treadmill_type == ESANGLINKER) {
uint8_t display[] = {0xa9, 0x01, 0x01, 0x0b, 0x00};
display[3] = (int)qRound(requestSpeed * 10 * 0.621371);
for (int i = 0; i < 4; i++) {
display[4] = display[4] ^ display[i];
}
writeCharacteristic(display, sizeof(display),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
}
}
@@ -159,7 +229,7 @@ void eslinkertreadmill::update() {
}
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE ||
treadmill_type == TYPE::COSTAWAY) {
treadmill_type == TYPE::COSTAWAY || treadmill_type == TYPE::ESANGLINKER) {
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
@@ -236,10 +306,8 @@ void eslinkertreadmill::update() {
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE) {
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("startTape"), false, true);
}
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("startTape"), false, true);
requestSpeed = 1.0;
requestStart = -1;
emit tapeStarted();
@@ -247,7 +315,8 @@ void eslinkertreadmill::update() {
if (requestStop != -1) {
requestSpeed = 0;
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape", false, true);
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x00, 0x0b};
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("stopTape"), false, true);
requestStop = -1;
}
}
@@ -255,6 +324,14 @@ void eslinkertreadmill::update() {
void eslinkertreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
if(gatt == QBluetoothUuid((quint16)0x1826)) {
QSettings settings;
settings.setValue(QZSettings::ftms_treadmill, bluetoothDevice.name());
qDebug() << "forcing FTMS treadmill since it has FTMS";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS treadmill found, restart the app to apply the change");
}
}
void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
@@ -270,6 +347,43 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
emit packetReceived();
if(treadmill_type == TYPE::ESANGLINKER) {
if((uint8_t)newValue.at(0) == 0xa9 && (uint8_t)newValue.at(1) == 0x08 && (uint8_t)newValue.at(2) == 0x04) { // pair request
lastPairFrame = newValue;
qDebug() << "lastPairFrame" << lastPairFrame;
uint8_t initData6[] = {0xa9, 0x08, 0x04, 0x0c, 0x06, 0x48, 0x12, 0xf5};
if(lastPairFrame.length() < 3) {
qDebug() << "ERROR! Pair code!";
return;
}
QByteArray crypto = cryptographicArray(lastPairFrame.at(3));
initData6[3] = crypto.at(0);
initData6[4] = crypto.at(1);
initData6[5] = crypto.at(2);
initData6[6] = crypto.at(3);
CRC8 crc8;
initData6[7] = crc8.calculate(QByteArray((const char*)&initData6[3], 4));
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
emit pairPacketReceived();
} else if((uint8_t)newValue.at(0) == 0xa9 && (uint8_t)newValue.at(1) == 0x08 &&
(uint8_t)newValue.at(2) == 0x01 && (uint8_t)newValue.at(3) == 0xff && (uint8_t)newValue.at(4) == 0x5f) { // handshake request
qDebug() << "handshake";
uint8_t initData5[] = {0xa9, 0x08, 0x01, 0xad, 0x0d};
initData5[3] = QRandomGenerator::global()->bounded(256);
CRC8 crc8;
initData5[4] = crc8.calculate(QByteArray((const char*)&initData5[3], 1));
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
emit handshakePacketReceived();
}
}
if (treadmill_type == CADENZA_FITNESS_T45) {
if (newValue.length() == 6 && newValue.at(0) == 8 && newValue.at(1) == 4 && newValue.at(2) == 1 &&
newValue.at(3) == 0 && newValue.at(4) == 0 && newValue.at(5) == 1) {
@@ -347,12 +461,11 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
}
}
if ((newValue.length() != 17 && (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE)))
return;
else if (newValue.length() != 5 && treadmill_type == COSTAWAY)
return;
if ((newValue.length() != 17 && (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE))) {
if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
} else if (newValue.length() != 5 && (treadmill_type == COSTAWAY || treadmill_type == TYPE::ESANGLINKER)) {
} else if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
double speed = GetSpeedFromPacket(value);
double incline = GetInclinationFromPacket(value);
double kcal = GetKcalFromPacket(value);
@@ -388,9 +501,9 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
lastSpeed = speed;
lastInclination = incline;
}
} else if (treadmill_type == COSTAWAY) {
} else if (treadmill_type == COSTAWAY || (treadmill_type == TYPE::ESANGLINKER && (uint8_t)newValue.at(1) == 0xe0)) {
const double miles = 1.60934;
if(((uint8_t)newValue.at(3)) == 0xFF)
if(((uint8_t)newValue.at(3)) == 0xFF && treadmill_type == COSTAWAY)
Speed = 0;
else
Speed = (double)((uint8_t)newValue.at(3)) / 10.0 * miles;
@@ -465,7 +578,56 @@ double eslinkertreadmill::GetInclinationFromPacket(const QByteArray &packet) {
void eslinkertreadmill::btinit(bool startTape) {
Q_UNUSED(startTape)
if (treadmill_type == COSTAWAY) {
if (treadmill_type == ESANGLINKER) {
uint8_t initData1[] = {0xa9, 0xf2, 0x01, 0x2f, 0x75};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
uint8_t initData2[] = {0xa9, 0x0a, 0x01, 0xc6, 0x64};
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
uint8_t initData3[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
uint8_t initData4[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
waitForHandshakePacket();
QThread::sleep(2);
waitForPairPacket();
QThread::sleep(1);
uint8_t initData7[] = {0xa9, 0x1e, 0x01, 0xfe, 0x48};
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, true);
uint8_t initData8[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, true);
QThread::sleep(2);
uint8_t initData9[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, true);
uint8_t initData10[] = {0xa9, 0x8e, 0x01, 0x09, 0x2f};
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
uint8_t initData11[] = {0xa9, 0xb2, 0x01, 0xfe, 0xe4};
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, true);
QThread::sleep(3);
uint8_t initData12[] = {0xa9, 0x8e, 0x01, 0x09, 0x2f};
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
uint8_t initData13[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, true);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Init completed, you can use the treadmill now!");
} else if (treadmill_type == COSTAWAY) {
uint8_t initData1[] = {0xa9, 0xf2, 0x01, 0x2f, 0x75};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
@@ -633,7 +795,10 @@ void eslinkertreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bool eslinker_ypoo = settings.value(QZSettings::eslinker_ypoo, QZSettings::default_eslinker_ypoo).toBool();
bool eslinker_costaway =
settings.value(QZSettings::eslinker_costaway, QZSettings::default_eslinker_costaway).toBool();
if (eslinker_cadenza) {
if(device.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) {
treadmill_type = ESANGLINKER;
qDebug() << "ESANGLINKER workaround ENABLED!";
} else if (eslinker_cadenza) {
treadmill_type = CADENZA_FITNESS_T45;
} else if (eslinker_ypoo) {
treadmill_type = YPOO_MINI_CHANGE;
@@ -677,3 +842,76 @@ bool eslinkertreadmill::autoStartWhenSpeedIsGreaterThenZero() {
else
return false;
}
QByteArray eslinkertreadmill::cryptographicArray(quint8 b2) {
int i = b2 % 6;
QRandomGenerator *random = QRandomGenerator::global();
int nextInt = random->bounded(1, 16); // 1 to 15
int nextInt2 = random->bounded(1, 16); // 1 to 15
QByteArray bArr(4, 0);
switch (i) {
case 0:
bArr[0] = static_cast<char>(nextInt);
bArr[1] = static_cast<char>(nextInt2);
bArr[2] = static_cast<char>(nextInt + nextInt2);
bArr[3] = static_cast<char>(nextInt * nextInt2);
break;
case 1:
bArr[0] = static_cast<char>(nextInt);
bArr[1] = static_cast<char>(nextInt2);
bArr[2] = static_cast<char>(nextInt * nextInt2);
bArr[3] = static_cast<char>(nextInt + nextInt2);
break;
case 2:
bArr[0] = static_cast<char>(nextInt + nextInt2);
bArr[1] = static_cast<char>(nextInt);
bArr[2] = static_cast<char>(nextInt2);
bArr[3] = static_cast<char>(nextInt * nextInt2);
break;
case 3:
bArr[0] = static_cast<char>(nextInt * nextInt2);
bArr[1] = static_cast<char>(nextInt);
bArr[2] = static_cast<char>(nextInt2);
bArr[3] = static_cast<char>(nextInt + nextInt2);
break;
case 4:
bArr[0] = static_cast<char>(nextInt + nextInt2);
bArr[1] = static_cast<char>(nextInt * nextInt2);
bArr[2] = static_cast<char>(nextInt);
bArr[3] = static_cast<char>(nextInt2);
break;
case 5:
bArr[0] = static_cast<char>(nextInt * nextInt2);
bArr[1] = static_cast<char>(nextInt + nextInt2);
bArr[2] = static_cast<char>(nextInt);
bArr[3] = static_cast<char>(nextInt2);
break;
}
return bArr;
}
void eslinkertreadmill::waitForPairPacket() {
QEventLoop loop;
QTimer timeout;
connect(this, &eslinkertreadmill::pairPacketReceived, &loop, &QEventLoop::quit);
timeout.singleShot(3000, &loop, SLOT(quit()));
loop.exec();
}
void eslinkertreadmill::waitForHandshakePacket() {
QEventLoop loop;
QTimer timeout;
connect(this, &eslinkertreadmill::handshakePacketReceived, &loop, &QEventLoop::quit);
timeout.singleShot(3000, &loop, SLOT(quit()));
loop.exec();
}
double eslinkertreadmill::minStepSpeed() {
if(treadmill_type == ESANGLINKER)
return 0.160934; // 0.1 mi
else
return 0.5;
}

View File

@@ -36,6 +36,7 @@ class eslinkertreadmill : public treadmill {
double minStepInclination() override;
bool autoPauseWhenSpeedIsZero() override;
bool autoStartWhenSpeedIsGreaterThenZero() override;
double minStepSpeed() override;
private:
double GetSpeedFromPacket(const QByteArray &packet);
@@ -46,6 +47,9 @@ class eslinkertreadmill : public treadmill {
void forceIncline(double requestIncline);
void updateDisplay(uint16_t elapsed);
void btinit(bool startTape);
void waitForPairPacket();
void waitForHandshakePacket();
QByteArray cryptographicArray(quint8 b2);
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
@@ -60,18 +64,17 @@ class eslinkertreadmill : public treadmill {
uint8_t requestHandshake = 0;
bool requestVar2 = false;
bool toggleRequestSpeed = false;
QByteArray lastPairFrame;
typedef enum TYPE {
RHYTHM_FUN = 0,
CADENZA_FITNESS_T45 = 1, // it has the same protocol of RHYTHM_FUN but without the header and the footer
YPOO_MINI_CHANGE = 2, // Similar to RHYTHM_FUN but has no ascension
COSTAWAY = 3,
ESANGLINKER = 4,
} TYPE;
volatile TYPE treadmill_type = RHYTHM_FUN;
int64_t lastStart = 0;
int64_t lastStop = 0;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
@@ -85,6 +88,8 @@ class eslinkertreadmill : public treadmill {
void debug(QString string);
void speedChanged(double speed);
void packetReceived();
void pairPacketReceived();
void handshakePacketReceived();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);

View File

@@ -44,7 +44,11 @@ void faketreadmill::update() {
}
if (requestInclination != -100) {
Inclination = requestInclination;
double step =
settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline)
.toDouble();
double r = qRound(requestInclination / step) * step;
Inclination = r;
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
requestInclination = -100;
}

View File

@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QSettings settings;
@@ -89,6 +89,7 @@ void fitplusbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
bool virtufit_etappe = settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
requestResistanceCompleted = false;
if (virtufit_etappe || merach_MRK || H9110_OSAKA) {
if (requestResistance == 1) {
uint8_t res[] = {0x02, 0x44, 0x05, 0x01, 0xf9, 0xb9, 0x03};
@@ -590,14 +591,14 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
resistance_t res = newValue.at(5);
if (settings.value(QZSettings::gears_from_bike, QZSettings::default_gears_from_bike).toBool()) {
qDebug() << QStringLiteral("gears_from_bike") << res << Resistance.value() << gears()
<< lastRawRequestedResistanceValue << lastRequestedResistance().value() << requestResistance;
<< lastRawRequestedResistanceValue << lastRequestedResistance().value() << requestResistance << requestResistanceCompleted;
if (
// if the resistance is different from the previous one
res != qRound(Resistance.value()) &&
// and the last target resistance is different from the current one or there is no any pending last
// requested resistance
((lastRequestedResistance().value() != res && lastRequestedResistance().value() != 0 && requestResistance == -1) ||
(lastRawRequestedResistanceValue == -1 && requestResistance == -1)) &&
((lastRequestedResistance().value() != res && lastRequestedResistance().value() != 0 && requestResistance == -1 && requestResistanceCompleted) ||
(lastRawRequestedResistanceValue == -1 && requestResistance == -1 && requestResistanceCompleted)) &&
// and the difference between the 2 resistances are less than 6
qRound(Resistance.value()) > 1 && qAbs(res - qRound(Resistance.value())) < 6) {
@@ -608,6 +609,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
setGears(g);
}
}
requestResistanceCompleted = true;
Resistance = res;
emit resistanceRead(Resistance.value());
if (merach_MRK || sportstech_sx600) {

View File

@@ -36,7 +36,7 @@
class fitplusbike : public bike {
Q_OBJECT
public:
fitplusbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
resistance_t maxResistance() override { return max_resistance; }
bool connected() override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
@@ -60,7 +60,7 @@ class fitplusbike : public bike {
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotifyFTMSCharacteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;
@@ -68,6 +68,7 @@ class fitplusbike : public bike {
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
resistance_t lastResistanceBeforeDisconnection = -1;
bool requestResistanceCompleted = true;
bool initDone = false;
bool initRequest = false;

View File

@@ -3,6 +3,7 @@
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualtreadmill.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
@@ -159,8 +160,9 @@ void fitshowtreadmill::update() {
}
if (initRequest) {
initRequest = false;
btinit(true);
QSettings settings;
initRequest = false;
btinit(settings.value(QZSettings::atletica_lightspeed_treadmill, QZSettings::default_atletica_lightspeed_treadmill).toBool());
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotifyCharacteristic.isValid() && initDone) {
@@ -297,6 +299,13 @@ void fitshowtreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << "adding" << gatt.toString() << "as the default service";
serviceId = gatt; // NOTE: clazy-rule-of-tow
}
if(gatt == QBluetoothUuid((quint16)0x1826) && !fs_connected) {
QSettings settings;
settings.setValue(QZSettings::ftms_treadmill, bluetoothDevice.name());
qDebug() << "forcing FTMS treadmill since it has FTMS";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS treadmill found, restart the app to apply the change");
}
}
void fitshowtreadmill::sendSportData() {
@@ -829,10 +838,10 @@ void fitshowtreadmill::error(QLowEnergyController::Error err) {
void fitshowtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
/*if (device.name().startsWith(QStringLiteral("FS-")) ||
(device.name().startsWith(QStringLiteral("SW")) && device.name().length() == 14))*/
if (device.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT"))) {
if (device.name().toUpper().startsWith(QStringLiteral("FS-"))) {
qDebug() << "FS FIX!";
fs_connected = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT"))) {
qDebug() << "NOBLEPRO FIX!";
minStepInclinationValue = 0.5;
noblepro_connected = true;

View File

@@ -98,8 +98,6 @@ class fitshowtreadmill : public treadmill {
void sendSportData();
void removeFromBuffer();
QBluetoothUuid serviceId;
int64_t lastStart = 0;
int64_t lastStop = 0;
int retrySend = 0;
bool noHeartService = false;
bool anyrun = false;
@@ -154,6 +152,7 @@ class fitshowtreadmill : public treadmill {
double minStepInclinationValue = 1.0;
bool noblepro_connected = false;
bool fs_connected = false;
metric rawInclination;

View File

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

View File

@@ -1,4 +1,5 @@
#include "ftmsbike.h"
#include "homeform.h"
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
@@ -21,8 +22,9 @@ extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
using namespace std::chrono_literals;
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
@@ -32,17 +34,54 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResi
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &ftmsbike::update);
refresh->start(200ms);
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
}
void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
void ftmsbike::writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
if(!zwiftPlayService) {
qDebug() << QStringLiteral("zwiftPlayService is null!");
return;
}
if (wait_for_response) {
connect(zwiftPlayService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(zwiftPlayService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
if (zwiftPlayWriteChar.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
zwiftPlayService->writeCharacteristic(zwiftPlayWriteChar, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
zwiftPlayService->writeCharacteristic(zwiftPlayWriteChar, *writeBuffer);
}
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
}
loop.exec();
}
bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
if(!gattFTMSService) {
qDebug() << QStringLiteral("gattFTMSService is null!");
return;
return false;
}
if (wait_for_response) {
@@ -70,6 +109,8 @@ void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
}
loop.exec();
return true;
}
void ftmsbike::init() {
@@ -77,23 +118,71 @@ void ftmsbike::init() {
return;
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(write, sizeof(write), "requestControl", false, true);
bool ret = writeCharacteristic(write, sizeof(write), "requestControl", false, true);
write[0] = {FTMS_START_RESUME};
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
initDone = true;
initRequest = false;
if(ret) {
initDone = true;
initRequest = false;
}
}
void ftmsbike::zwiftPlayInit() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(zwiftPlayService && gears_zwift_ratio) {
uint8_t rideOn[] = {0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e, 0x02, 0x01};
writeCharacteristicZwiftPlay(rideOn, sizeof(rideOn), "rideOn", false, true);
uint8_t init1[] = {0x41, 0x08, 0x05};
writeCharacteristicZwiftPlay(init1, sizeof(init1), "init1", false, true);
uint8_t init2[] = {0x04, 0x2a, 0x04, 0x10, 0xc0, 0xbb, 0x01};
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
uint8_t init3[] = {0x00, 0x08, 0x00};
writeCharacteristicZwiftPlay(init3, sizeof(init3), "init3", false, true);
writeCharacteristicZwiftPlay(init1, sizeof(init1), "init1", false, true);
uint8_t init4[] = {0x00, 0x08, 0x88, 0x04};
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
uint8_t init5[] = {0x04, 0x2a, 0x0a, 0x10, 0xc0, 0xbb, 0x01, 0x20, 0xbf, 0x06, 0x28, 0xb4, 0x42};
writeCharacteristicZwiftPlay(init5, sizeof(init5), "init5", false, true);
uint8_t init6[] = {0x04, 0x22, 0x0b, 0x08, 0x00, 0x10, 0xda, 0x02, 0x18, 0xec, 0x27, 0x20, 0x90, 0x03};
writeCharacteristicZwiftPlay(init6, sizeof(init6), "init6", false, true);
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
uint8_t init7[] = {0x04, 0x22, 0x03, 0x10, 0xa9, 0x01};
writeCharacteristicZwiftPlay(init7, sizeof(init7), "init7", false, true);
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
uint8_t init8[] = {0x04, 0x22, 0x02, 0x10, 0x00};
writeCharacteristicZwiftPlay(init8, sizeof(init8), "init8", false, true);
}
}
void ftmsbike::forcePower(int16_t requestPower) {
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
if(resistance_lvl_mode) {
forceResistance(resistanceFromPowerRequest(requestPower));
} else {
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
write[1] = ((uint16_t)requestPower) & 0xFF;
write[2] = ((uint16_t)requestPower) >> 8;
write[1] = ((uint16_t)requestPower) & 0xFF;
write[2] = ((uint16_t)requestPower) >> 8;
writeCharacteristic(write, sizeof(write), QStringLiteral("forcePower ") + QString::number(requestPower));
writeCharacteristic(write, sizeof(write), QStringLiteral("forcePower ") + QString::number(requestPower));
powerForced = true;
powerForced = true;
}
}
uint16_t ftmsbike::wattsFromResistance(double resistance) {
@@ -127,7 +216,7 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
resistance_lvl_mode == false) {
resistance_lvl_mode == false && _3G_Cardio_RB == false) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
@@ -140,6 +229,8 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
if(_3G_Cardio_RB)
requestResistance = requestResistance * 10;
write[1] = ((uint8_t)(requestResistance));
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
@@ -153,6 +244,7 @@ void ftmsbike::update() {
}
if (initRequest) {
zwiftPlayInit();
initRequest = false;
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
@@ -176,7 +268,9 @@ void ftmsbike::update() {
forceResistance(currentResistance().value());
}
if (requestResistance != -1) {
auto virtualBike = this->VirtualBike();
if (requestResistance != -1 || lastGearValue != gears()) {
if (requestResistance > 100) {
requestResistance = 100;
} // TODO, use the bluetooth value
@@ -184,19 +278,127 @@ void ftmsbike::update() {
requestResistance = 1;
}
if (requestResistance != currentResistance().value()) {
if (requestResistance != currentResistance().value() || lastGearValue != gears()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
// if the FTMS is connected, the ftmsCharacteristicChanged event will do all the stuff because it's a
// FTMS bike. This condition handles the peloton requests
auto virtualBike = this->VirtualBike();
// FTMS bike. This condition handles the peloton requests
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
(requestPower == 0 || requestPower == -1)) {
init();
forceResistance(requestResistance);
forceResistance(requestResistance + (gears() * 5));
}
}
requestResistance = -1;
}
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
}
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) {
uint8_t gear1[] = {0x04, 0x2a, 0x03, 0x10, 0xdc, 0xec};
uint8_t gear2[] = {0x04, 0x2a, 0x04, 0x10, 0xdc, 0xec, 0x01};
uint32_t gear_value = 0;
switch((int)gears()) {
case 1:
gear_value = 0x3acc;
break;
case 2:
gear_value = 0x43fc;
break;
case 3:
gear_value = 0x4dac;
break;
case 4:
gear_value = 0x56d5;
break;
case 5:
gear_value = 0x608c;
break;
case 6:
gear_value = 0x6be8;
break;
case 7:
gear_value = 0x77c4;
break;
case 8:
gear_value = 0x183a0;
break;
case 9:
gear_value = 0x191a8;
break;
case 10:
gear_value = 0x19fb0;
break;
case 11:
gear_value = 0x1adb8;
break;
case 12:
gear_value = 0x1bbc0;
break;
case 13:
gear_value = 0x1cbf3;
break;
case 14:
gear_value = 0x1dca8;
break;
case 15:
gear_value = 0x1ecdc;
break;
case 16:
gear_value = 0x1fd90;
break;
case 17:
gear_value = 0x290d4;
break;
case 18:
gear_value = 0x2a498;
break;
case 19:
gear_value = 0x2b7dc;
break;
case 20:
gear_value = 0x2cb9f;
break;
case 21:
gear_value = 0x2e2d8;
break;
case 22:
gear_value = 0x2fa90;
break;
case 23:
gear_value = 0x391c8;
break;
case 24:
gear_value = 0x3acf3;
break;
default:
// Gestione del caso di default
break;
}
gear_value = gear_value * settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble();
if(gear_value < 0x10000) {
gear1[4] = gear_value & 0xFF;
gear1[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
writeCharacteristicZwiftPlay(gear1, sizeof(gear1), "gear", false, true);
} else {
gear2[4] = gear_value & 0xFF;
gear2[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
gear2[6] = ((gear_value & 0xFF0000) >> 16) & 0xFF;
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gear", false, true);
}
uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04};
writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true);
}
lastGearValue = gears();
if (requestPower != -1) {
qDebug() << QStringLiteral("writing power") << requestPower;
init();
@@ -215,6 +417,13 @@ void ftmsbike::update() {
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
QSettings settings;
if (settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(write, sizeof(write), QStringLiteral("init SS2K"));
}
}
}
}
@@ -246,6 +455,17 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
return;
}
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) { // Battery Service
if(newValue.length() > 0) {
uint8_t b = (uint8_t)newValue.at(0);
if(b != battery_level)
if(homeform::singleton())
homeform::singleton()->setToastRequested(QStringLiteral("Battery Level ") + QString::number(b) + " %");
battery_level = b;
}
return;
}
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
union flags {
@@ -331,7 +551,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
((double)lastRefreshCharacteristicChanged2AD2.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -359,7 +579,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
if (!resistance_received && !DU30_bike) {
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
@@ -379,6 +599,10 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
} else if(DOMYOS) {
// doesn't send power at all and the resistance either
m_watt = wattFromHR(true);
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (Flags.avgPower && newValue.length() > index + 1) {
@@ -390,8 +614,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
/*KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));*/
index += 2;
// energy per hour
@@ -399,17 +623,17 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
// energy per minute
index += 1;
} else {
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged2AD2.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
@@ -439,6 +663,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
if (Flags.remainingTime) {
// todo
}
lastRefreshCharacteristicChanged2AD2 = now;
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE)) {
union flags {
struct {
@@ -500,7 +726,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(now)));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -595,7 +821,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
@@ -629,6 +855,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
if (Flags.remainingTime) {
// todo
}
lastRefreshCharacteristicChanged2ACE = now;
} else {
return;
}
@@ -638,8 +866,6 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = now;
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!heart || Heart.value() == 0 || disable_hr_frommachinery)) {
update_hr_from_external();
@@ -699,7 +925,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool()) {
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U) {
QBluetoothUuid ftmsService((quint16)0x1826);
if (s->serviceUuid() != ftmsService) {
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
@@ -711,7 +937,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
auto descriptors_list = c.descriptors();
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
@@ -757,10 +983,23 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
gattWriteCharControlPointId = c;
gattFTMSService = s;
}
QBluetoothUuid _zwiftPlayWriteCharControlPointId(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1"));
if (c.uuid() == _zwiftPlayWriteCharControlPointId) {
qDebug() << QStringLiteral("Zwift Play service and Control Point found");
zwiftPlayWriteChar = c;
zwiftPlayService = s;
}
}
}
}
if(gattFTMSService == nullptr && DOMYOS) {
settings.setValue(QZSettings::domyosbike_notfmts, true);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Domyos bike presents itself like a FTMS but it's not. Restart QZ to apply the fix, thanks.");
}
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool()) {
init();
@@ -813,26 +1052,50 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
}
QByteArray b = newValue;
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if (gattWriteCharControlPointId.isValid()) {
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
// handling gears
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS) {
qDebug() << "applying gears mod" << m_gears;
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && ((zwiftPlayService == nullptr && gears_zwift_ratio) || !gears_zwift_ratio)) {
lastPacketFromFTMS.clear();
for(int i=0; i<b.length(); i++)
lastPacketFromFTMS.append(b.at(i));
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
if (m_gears != 0) {
slope += (m_gears * 50);
b[3] = slope & 0xFF;
b[4] = slope >> 8;
if (gears() != 0) {
slope += (gears() * 50);
}
b[3] = slope & 0xFF;
b[4] = slope >> 8;
qDebug() << "applying gears mod" << gears() << slope;
/*} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) {
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
uint8_t gear2[] = {0x04, 0x22, 0x02, 0x10, 0x00};
int g = (int)(((double)slope / 100.0) + settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble());
if(g < 0) {
g = 0;
}
gear2[4] = g;
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gearInclination", false, false);*/
} else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
lastPacketFromFTMS.clear();
for(int i=0; i<b.length(); i++)
lastPacketFromFTMS.append(b.at(i));
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
int16_t power = (((uint8_t)b.at(1)) + (b.at(2) << 8));
if (gears() != 0) {
power += (gears() * 10);
}
b[1] = power & 0xFF;
b[2] = power >> 8;
qDebug() << "applying gears mod" << gears() << gearsZwiftRatio() << power;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray(b);
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
writeCharacteristic((uint8_t*)b.data(), b.length(), "injectWrite ", false, true);
}
}
@@ -898,6 +1161,7 @@ resistance_t ftmsbike::pelotonToBikeResistance(int pelotonResistance) {
}
void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
@@ -912,6 +1176,22 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("DU30 found");
max_resistance = 32;
DU30_bike = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("ICSE") && bluetoothDevice.name().length() == 4)) {
qDebug() << QStringLiteral("ICSE found");
ICSE = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
DOMYOS = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("3G Cardio RB"))) {
qDebug() << QStringLiteral("_3G_Cardio_RB found");
_3G_Cardio_RB = true;
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_190U"))) {
qDebug() << QStringLiteral("SCH_190U found");
SCH_190U = true;
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
resistance_lvl_mode = true;
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);

View File

@@ -68,15 +68,18 @@ enum FtmsResultCode {
class ftmsbike : public bike {
Q_OBJECT
public:
ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
private:
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void zwiftPlayInit();
void startDiscover();
uint16_t watts() override;
void init();
@@ -90,12 +93,18 @@ class ftmsbike : public bike {
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyService *gattFTMSService = nullptr;
QLowEnergyCharacteristic zwiftPlayWriteChar;
QLowEnergyService *zwiftPlayService = nullptr;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
QByteArray lastPacketFromFTMS;
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
QDateTime lastRefreshCharacteristicChanged2ACE = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
double lastGearValue = -1;
int max_resistance = 100;
bool initDone = false;
@@ -110,6 +119,12 @@ class ftmsbike : public bike {
bool resistance_received = false;
bool DU30_bike = false;
bool ICSE = false;
bool DOMYOS = false;
bool _3G_Cardio_RB = false;
bool SCH_190U = false;
uint8_t battery_level = 0;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -78,8 +78,13 @@ void ftmsrower::update() {
}
if (initRequest) {
uint8_t write[] = {FTMS_START_RESUME};
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
if(I_ROWER) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(write, sizeof(write), "start", false, true);
} else {
uint8_t write[] = {FTMS_START_RESUME};
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
}
initRequest = false;
} else if (bluetoothDevice.isValid() &&
@@ -583,6 +588,9 @@ void ftmsrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("DFIT-L-R"))) {
DFIT_L_R = true;
qDebug() << "DFIT_L_R found!";
} else if (device.name().toUpper().startsWith(QStringLiteral("I-ROWER"))) {
I_ROWER = true;
qDebug() << "I_ROWER found!";
} else if (device.name().toUpper().startsWith(QStringLiteral("PM5"))) {
PM5 = true;
qDebug() << "PM5 found!";

View File

@@ -71,6 +71,7 @@ class ftmsrower : public rower {
bool WATER_ROWER = false;
bool DFIT_L_R = false;
bool I_ROWER = false;
QDateTime lastStroke = QDateTime::currentDateTime();
double lastStrokesCount = 0;

View File

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

View File

@@ -36,7 +36,7 @@
class horizongr7bike : public bike {
Q_OBJECT
public:
horizongr7bike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
horizongr7bike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
bool connected() override;
@@ -63,7 +63,7 @@ class horizongr7bike : public bike {
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool initDone = false;

View File

@@ -1,5 +1,5 @@
#include "horizontreadmill.h"
#include "homeform.h"
#include "devices/ftmsbike/ftmsbike.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
@@ -61,7 +61,11 @@ void horizontreadmill::writeCharacteristic(QLowEnergyService *service, QLowEnerg
}
writeBuffer = new QByteArray((const char *)data, data_len);
service->writeCharacteristic(characteristic, *writeBuffer);
if (characteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
service->writeCharacteristic(characteristic, *writeBuffer, QLowEnergyService::WriteWithoutResponse);
} else {
service->writeCharacteristic(characteristic, *writeBuffer);
}
if (!disable_log)
qDebug() << " >> " << writeBuffer->toHex(' ') << " // " << info;
@@ -803,6 +807,15 @@ void horizontreadmill::btinit() {
messageID = 0x10;
}
if(wellfit_treadmill) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
QThread::msleep(500);
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
}
initDone = true;
}
@@ -815,6 +828,8 @@ void horizontreadmill::update() {
return;
}
qDebug() << initRequest << firstStateChanged << bluetoothDevice.isValid();
if (initRequest && firstStateChanged) {
btinit();
initRequest = false;
@@ -897,6 +912,7 @@ void horizontreadmill::update() {
if (requestInclination != currentInclination().value() && requestInclination >= minInclination &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
@@ -1139,7 +1155,7 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill) {
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1205,7 +1221,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill) {
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1301,7 +1317,59 @@ void horizontreadmill::forceIncline(double requestIncline) {
writeS[1] = conversion[r];
writeS[2] = conversion1[r];
} else if(ICONCEPT_FTMS_treadmill) {
if(requestInclination > 0 && requestInclination < 1) {
writeS[1] = 0x3C;
writeS[2] = 0x00;
} else if(requestInclination > 1 && requestInclination < 2) {
writeS[1] = 0x82;
writeS[2] = 0x00;
} else if(requestInclination > 2 && requestInclination < 3) {
writeS[1] = 0xC8;
writeS[2] = 0x00;
} else if(requestInclination > 3 && requestInclination < 4) {
writeS[1] = 0x04;
writeS[2] = 0x01;
} else if(requestInclination > 4 && requestInclination < 5) {
writeS[1] = 0x4A;
writeS[2] = 0x01;
} else if(requestInclination > 5 && requestInclination < 6) {
writeS[1] = 0x90;
writeS[2] = 0x01;
} else if(requestInclination > 6 && requestInclination < 7) {
writeS[1] = 0xCC;
writeS[2] = 0x01;
} else if(requestInclination > 7 && requestInclination < 8) {
writeS[1] = 0x12;
writeS[2] = 0x02;
} else if(requestInclination > 8 && requestInclination < 9) {
writeS[1] = 0x58;
writeS[2] = 0x02;
} else if(requestInclination > 9 && requestInclination < 10) {
writeS[1] = 0x94;
writeS[2] = 0x02;
} else if(requestInclination > 10 && requestInclination < 11) {
writeS[1] = 0xDA;
writeS[2] = 0x02;
} else if(requestInclination > 11 && requestInclination < 12) {
writeS[1] = 0x20;
writeS[2] = 0x03;
} else if(requestInclination > 12 && requestInclination < 13) {
writeS[1] = 0x5C;
writeS[2] = 0x03;
} else if(requestInclination > 13 && requestInclination < 14) {
writeS[1] = 0xA2;
writeS[2] = 0x03;
} else if(requestInclination > 14 && requestInclination < 15) {
writeS[1] = 0xE8;
writeS[2] = 0x03;
} else {
writeS[1] = 0x00;
writeS[2] = 0x00;
}
} else {
if(HORIZON_78AT_treadmill)
requestIncline = requestIncline / 2.0;
writeS[1] = ((int16_t)(requestIncline * 10.0)) & 0xFF;
writeS[2] = ((int16_t)(requestIncline * 10.0)) >> 8;
}
@@ -1331,6 +1399,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
QDateTime now = QDateTime::currentDateTime();
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
emit debug(QStringLiteral(" << ") + characteristic.uuid().toString() + " " + QString::number(newValue.length()) +
" " + newValue.toHex(' '));
@@ -1372,11 +1441,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
Inclination = treadmillInclinationOverride((double)((uint8_t)lastPacketComplete.at(30)) / 10.0);
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (firstDistanceCalculated && watts(weight))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
((((0.048 * ((double)watts(weight)) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
weight * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -1398,11 +1467,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
Inclination = treadmillInclinationOverride((double)((uint8_t)newValue.at(63)) / 10.0);
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (firstDistanceCalculated && watts(weight))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
((((0.048 * ((double)watts(weight)) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
weight * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -1422,11 +1491,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// Inclination = (double)((uint8_t)newValue.at(3)) / 10.0;
// emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
if (firstDistanceCalculated && watts(weight))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
((((0.048 * ((double)watts(weight)) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
weight * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
@@ -1448,6 +1517,164 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
Speed = 0;
horizonPaused = true;
qDebug() << "stop from the treadmill";
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
union flags {
struct {
uint16_t moreData : 1;
uint16_t avgSpeed : 1;
uint16_t instantCadence : 1;
uint16_t avgCadence : 1;
uint16_t totDistance : 1;
uint16_t resistanceLvl : 1;
uint16_t instantPower : 1;
uint16_t avgPower : 1;
uint16_t expEnergy : 1;
uint16_t heartRate : 1;
uint16_t metabolic : 1;
uint16_t elapsedTime : 1;
uint16_t remainingTime : 1;
uint16_t spare : 3;
};
uint16_t word_flags;
};
flags Flags;
int index = 0;
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
index += 2;
if (!Flags.moreData) {
Speed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
if (Flags.avgSpeed) {
double avgSpeed;
avgSpeed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
index += 2;
emit debug(QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed));
}
if (Flags.instantCadence) {
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0;
}
index += 2;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
}
if (Flags.avgCadence) {
double avgCadence;
avgCadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0;
index += 2;
emit debug(QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence));
}
if (Flags.totDistance) {
/*
* the distance sent from the most trainers is a total distance, so it's useless for QZ
*
Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
(uint32_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint32_t)((uint8_t)newValue.at(index)))) /
1000.0;*/
index += 3;
}
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (Flags.resistanceLvl) {
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
}
if (Flags.instantPower) {
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (Flags.avgPower && newValue.length() > index + 1) {
double avgPower;
avgPower = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
// energy per hour
index += 2;
// energy per minute
index += 1;
} else {
if (watts(weight))
KCal += ((((0.048 * ((double)watts(weight)) + 1.19) *
weight * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
Heart = ((double)(((uint8_t)newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
Flags.heartRate = false;
}
heart = Flags.heartRate;
}
if (Flags.metabolic) {
// todo
}
if (Flags.elapsedTime) {
// todo
}
if (Flags.remainingTime) {
// todo
}
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACD)) {
lastPacket = newValue;
@@ -1514,7 +1741,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (Flags.inclination) {
if(!tunturi_t60_treadmill)
if(!tunturi_t60_treadmill && !ICONCEPT_FTMS_treadmill)
Inclination = treadmillInclinationOverride((double)(
(int16_t)(
((int16_t)(int8_t)newValue.at(index + 1) << 8) |
@@ -1522,6 +1749,43 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
)
) /
10.0);
else if(ICONCEPT_FTMS_treadmill) {
uint8_t val1 = (uint8_t)newValue.at(index);
uint8_t val2 = (uint8_t)newValue.at(index + 1);
if(val1 == 0x3C && val2 == 0x00) {
Inclination = 1;
} else if(val1 == 0x82 && val2 == 0x00) {
Inclination = 2;
} else if(val1 == 0xC8 && val2 == 0x00) {
Inclination = 3;
} else if(val1 == 0x04 && val2 == 0x01) {
Inclination = 4;
} else if(val1 == 0x4A && val2 == 0x01) {
Inclination = 5;
} else if(val1 == 0x90 && val2 == 0x01) {
Inclination = 6;
} else if(val1 == 0xCC && val2 == 0x01) {
Inclination = 7;
} else if(val1 == 0x12 && val2 == 0x02) {
Inclination = 8;
} else if(val1 == 0x58 && val2 == 0x02) {
Inclination = 9;
} else if(val1 == 0x94 && val2 == 0x02) {
Inclination = 10;
} else if(val1 == 0xDA && val2 == 0x02) {
Inclination = 11;
} else if(val1 == 0x20 && val2 == 0x03) {
Inclination = 12;
} else if(val1 == 0x5C && val2 == 0x03) {
Inclination = 13;
} else if(val1 == 0xA2 && val2 == 0x03) {
Inclination = 14;
} else if(val1 == 0xE8 && val2 == 0x03) {
Inclination = 15;
} else {
Inclination = 0;
}
}
index += 4; // the ramo value is useless
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
}
@@ -1838,10 +2102,21 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
QBluetoothUuid _gattTreadmillDataId((quint16)0x2ACD);
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
QBluetoothUuid _DomyosServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
if(s->serviceUuid() == _DomyosServiceId && DOMYOS) {
settings.setValue(QZSettings::domyostreadmill_notfmts, true);
settings.sync();
if(homeform::singleton())
homeform::singleton()->setToastRequested("Domyos Treadmill presents itself like a FTMS but it's not. Restart QZ to apply the fix, thanks.");
return;
}
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
@@ -1868,9 +2143,9 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharControlPointId) {
if (c.uuid() == _gattWriteCharControlPointId) {
qDebug() << QStringLiteral("FTMS service and Control Point found");
gattWriteCharControlPointId = c;
gattFTMSService = s;
@@ -1881,7 +2156,10 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
// some treadmills doesn't have the control point and also are Cross Trainer devices so i need
// anyway to get the FTMS Service at least
gattFTMSService = s;
}
}/* else if (c.uuid() == _gattInclinationSupported) {
s->readCharacteristic(c);
qDebug() << s->serviceUuid() << c.uuid() << "reading!";
}*/
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService &&
!settings
@@ -1925,6 +2203,24 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("notification subscribed!");
} else if ((c.properties() & QLowEnergyCharacteristic::Indicate) == QLowEnergyCharacteristic::Indicate &&
// if it's a FTMS treadmill and has FTMS and/or RSC service too
((((gattFTMSService && s->serviceUuid() == gattFTMSService->serviceUuid()))
&& !gattCustomService))) {
QByteArray descriptor;
descriptor.append((char)0x02);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
notificationSubscribed++;
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
<< QStringLiteral(" is not valid");
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("indication subscribed!");
}
}
}
@@ -1996,6 +2292,17 @@ void horizontreadmill::characteristicWritten(const QLowEnergyCharacteristic &cha
void horizontreadmill::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' ');
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
if(characteristic.uuid() == _gattInclinationSupported && newValue.length() > 2) {
minInclination = ((double)(
(int16_t)(
((int16_t)(int8_t)newValue.at(1) << 8) |
(uint8_t)newValue.at(0)
)
) /
10.0);
qDebug() << "new minInclination is " << minInclination;
}
}
void horizontreadmill::serviceScanDone(void) {
@@ -2004,25 +2311,13 @@ void horizontreadmill::serviceScanDone(void) {
initRequest = false;
firstStateChanged = 0;
auto services_list = m_control->services();
QBluetoothUuid ftmsService((quint16)0x1826);
QBluetoothUuid CustomService((quint16)0xFFF0);
for (const QBluetoothUuid &s : qAsConst(services_list)) {
#ifdef Q_OS_WIN
if (s == ftmsService || s == CustomService)
#endif
{
qDebug() << s << "discovering...";
gattCommunicationChannelService.append(m_control->createServiceObject(s));
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&horizontreadmill::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
}
#ifdef Q_OS_WIN
else {
qDebug() << s << "NOT discovering!";
}
#endif
}
}
@@ -2052,12 +2347,18 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TM"))) {
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TMP"))) {
mobvoi_tmp_treadmill = true;
qDebug() << QStringLiteral("MOBVOI TMP workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TM"))) {
mobvoi_treadmill = true;
qDebug() << QStringLiteral("MOBVOI TM workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL"))) {
kettler_treadmill = true;
qDebug() << QStringLiteral("KETTLER TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("WELLFIT TM"))) {
wellfit_treadmill = true;
qDebug() << QStringLiteral("WELLFIT TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) {
anplus_treadmill = true;
qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!");
@@ -2082,6 +2383,18 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("TREADMILL"))) { // Technogym Run
technogymrun = true;
qDebug() << QStringLiteral("Technogym Run TREADMILL workaround ON!");
} else if(device.name().toUpper().startsWith(QStringLiteral("SW"))) {
qDebug() << QStringLiteral("SW TREADMILL workaround ON!");
disableAutoPause = true;
} else if(device.name().toUpper().startsWith("HORIZON_7.8AT")) {
HORIZON_78AT_treadmill = true;
qDebug() << QStringLiteral("HORIZON_7.8AT workaround ON!");
} else if(device.name().toUpper().startsWith("T01_")) {
ICONCEPT_FTMS_treadmill = true;
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
} else if ((device.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
DOMYOS = true;
}
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
@@ -2090,6 +2403,11 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = false;
#endif
} else if(device.name().toUpper().startsWith(QStringLiteral("TRX4500"))) {
qDebug() << QStringLiteral("TRX4500 TREADMILL workaround ON!");
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = false;
#endif
} else {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
@@ -2180,6 +2498,8 @@ int horizontreadmill::GenerateCRC_CCITT(uint8_t *PUPtr8, int PU16_Count, int crc
}
bool horizontreadmill::autoPauseWhenSpeedIsZero() {
if(disableAutoPause == true)
return false;
if (lastStart == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStart + 10000))
return true;
else
@@ -2799,7 +3119,7 @@ void horizontreadmill::testProfileCRC() {
double horizontreadmill::minStepInclination() {
QSettings settings;
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill)
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill)
return 1.0;
else
return 0.5;

View File

@@ -71,8 +71,6 @@ class horizontreadmill : public treadmill {
uint8_t firstStateChanged = 0;
double lastSpeed = 0.0;
double lastInclination = 0;
int64_t lastStart = 0;
int64_t lastStop = 0;
bool horizonPaused = false;
double lastHorizonForceSpeed = 0;
double minInclination = 0.0;
@@ -88,7 +86,9 @@ class horizontreadmill : public treadmill {
int32_t messageID = 0;
bool mobvoi_treadmill = false;
bool mobvoi_tmp_treadmill = false;
bool kettler_treadmill = false;
bool wellfit_treadmill = false;
bool sole_tt8_treadmill = false;
bool anplus_treadmill = false;
bool tunturi_t60_treadmill = false;
@@ -97,6 +97,10 @@ class horizontreadmill : public treadmill {
bool sole_f89_treadmill = false;
bool schwinn_810_treadmill = false;
bool technogymrun = false;
bool disableAutoPause = false;
bool HORIZON_78AT_treadmill = false;
bool ICONCEPT_FTMS_treadmill = false;
bool DOMYOS = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -22,7 +22,13 @@ iconceptbike::iconceptbike() {
void iconceptbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
if (device.name().toUpper().startsWith(QStringLiteral("BH DUALKIT"))) {
//if (device.name().toUpper().startsWith(QStringLiteral("BH DUALKIT")))
{
if (device.name().toUpper().startsWith(QStringLiteral("BH-"))) {
i_Nexor = true;
qDebug() << "BH i-Nexor workaround enabled";
}
bluetoothDevice = device;
// Create a discovery agent and connect to its signals
@@ -79,6 +85,8 @@ void iconceptbike::serviceDiscovered(const QBluetoothServiceInfo &service) {
connect(socket, &QBluetoothSocket::disconnected, this, &iconceptbike::disconnected);
connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error), this,
&iconceptbike::onSocketErrorOccurred);
} else {
qDebug () << QStringLiteral("service ignored!");
}
}
}
@@ -130,50 +138,109 @@ void iconceptbike::update() {
void iconceptbike::rfCommConnected() {
emit debug(QStringLiteral("connected ") + socket->peerName());
const uint8_t init1[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
const uint8_t init2[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
const uint8_t init3[] = {0x55, 0x17, 0x01, 0x01, 0x55, 0xb5, 0x01, 0xff};
const uint8_t init4[] = {0x55, 0x01, 0x06, 0x1e, 0x00, 0x3c, 0x00, 0xaa, 0x00};
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
const uint8_t init9[] = {0x55, 0x11, 0x01, 0x08};
if(i_Nexor) {
const uint8_t init01[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
const uint8_t init02[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
socket->write((char *)init01, sizeof(init01));
qDebug() << QStringLiteral(" init01 write");
socket->write((char *)init02, sizeof(init02));
qDebug() << QStringLiteral(" init02 write");
QThread::msleep(2000);
socket->write((char *)init1, sizeof(init1));
qDebug() << QStringLiteral(" init1 write");
socket->write((char *)init2, sizeof(init2));
qDebug() << QStringLiteral(" init2 write");
QThread::msleep(2000);
socket->write((char *)init3, sizeof(init3));
qDebug() << QStringLiteral(" init3 write");
QThread::msleep(2000);
socket->write((char *)init3, sizeof(init3));
qDebug() << QStringLiteral(" init3 write");
QThread::msleep(500);
socket->write((char *)init4, sizeof(init4));
qDebug() << QStringLiteral(" init4 write");
QThread::msleep(600);
socket->write((char *)init5, sizeof(init5));
qDebug() << QStringLiteral(" init5 write");
QThread::msleep(600);
socket->write((char *)init6, sizeof(init6));
qDebug() << QStringLiteral(" init6 write");
QThread::msleep(600);
socket->write((char *)init7, sizeof(init7));
qDebug() << QStringLiteral(" init7 write");
QThread::msleep(600);
socket->write((char *)init8, sizeof(init8));
qDebug() << QStringLiteral(" init8 write");
QThread::msleep(600);
socket->write((char *)init9, sizeof(init9));
qDebug() << QStringLiteral(" init9 write");
const uint8_t init1[] = {0x55, 0x0a, 0x01, 0x02, 0x53};
socket->write((char *)init1, sizeof(init1));
qDebug() << QStringLiteral(" init1 write");
const uint8_t init2[] = {0x55, 0x17, 0x01, 0x01, 0x4f};
socket->write((char *)init2, sizeof(init2));
qDebug() << QStringLiteral(" init2 write");
QThread::msleep(600);
const uint8_t init3[] = {0x55, 0x01, 0x06, 0x21, 0x01, 0x50, 0x00, 0xb4, 0x00};
socket->write((char *)init3, sizeof(init3));
qDebug() << QStringLiteral(" init3 write");
QThread::msleep(400);
const uint8_t init4[] = {0x55, 0x17, 0x01, 0x01};
socket->write((char *)init4, sizeof(init4));
qDebug() << QStringLiteral(" init4 write");
QThread::msleep(200);
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
socket->write((char *)init5, sizeof(init5));
qDebug() << QStringLiteral(" init5 write");
QThread::msleep(600);
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
socket->write((char *)init6, sizeof(init6));
qDebug() << QStringLiteral(" init6 write");
QThread::msleep(200);
socket->write((char *)init4, sizeof(init4));
qDebug() << QStringLiteral(" init4 write");
QThread::msleep(400);
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
socket->write((char *)init7, sizeof(init7));
qDebug() << QStringLiteral(" init7 write");
QThread::msleep(600);
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
socket->write((char *)init8, sizeof(init8));
qDebug() << QStringLiteral(" init8 write");
QThread::msleep(600);
} else {
const uint8_t init1[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
const uint8_t init2[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
const uint8_t init3[] = {0x55, 0x17, 0x01, 0x01, 0x55, 0xb5, 0x01, 0xff};
const uint8_t init4[] = {0x55, 0x01, 0x06, 0x1e, 0x00, 0x3c, 0x00, 0xaa, 0x00};
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
const uint8_t init9[] = {0x55, 0x11, 0x01, 0x08};
socket->write((char *)init1, sizeof(init1));
qDebug() << QStringLiteral(" init1 write");
socket->write((char *)init2, sizeof(init2));
qDebug() << QStringLiteral(" init2 write");
QThread::msleep(2000);
socket->write((char *)init3, sizeof(init3));
qDebug() << QStringLiteral(" init3 write");
QThread::msleep(2000);
socket->write((char *)init3, sizeof(init3));
qDebug() << QStringLiteral(" init3 write");
QThread::msleep(500);
socket->write((char *)init4, sizeof(init4));
qDebug() << QStringLiteral(" init4 write");
QThread::msleep(600);
socket->write((char *)init5, sizeof(init5));
qDebug() << QStringLiteral(" init5 write");
QThread::msleep(600);
socket->write((char *)init6, sizeof(init6));
qDebug() << QStringLiteral(" init6 write");
QThread::msleep(600);
socket->write((char *)init7, sizeof(init7));
qDebug() << QStringLiteral(" init7 write");
QThread::msleep(600);
socket->write((char *)init8, sizeof(init8));
qDebug() << QStringLiteral(" init8 write");
QThread::msleep(600);
socket->write((char *)init9, sizeof(init9));
qDebug() << QStringLiteral(" init9 write");
}
initDone = true;
// requestStart = 1;
@@ -195,7 +262,9 @@ void iconceptbike::readSocket() {
bool bh_spada_2_watt =
settings.value(QZSettings::bh_spada_2_watt, QZSettings::default_bh_spada_2_watt).toBool();
elapsed = GetElapsedTimeFromPacket(line);
Distance = GetDistanceFromPacket(line);
//Distance = GetDistanceFromPacket(line);
QDateTime now = QDateTime::currentDateTime();
Distance += ((Speed.value() / 3600000.0) * ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
KCal = GetCaloriesFromPacket(line);
if (bh_spada_2_watt) {
m_watt = GetWattFromPacket(line);
@@ -213,7 +282,7 @@ void iconceptbike::readSocket() {
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
} else {
Speed = GetSpeedFromPacket(line);
@@ -226,7 +295,7 @@ void iconceptbike::readSocket() {
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {

View File

@@ -53,6 +53,7 @@ class iconceptbike : public bike {
QTimer *refresh;
bool initDone = false;
uint8_t firstStateChanged = 0;
bool i_Nexor = false;
uint16_t GetElapsedTimeFromPacket(const QByteArray &packet);
uint16_t GetDistanceFromPacket(const QByteArray &packet);

View File

@@ -9,7 +9,7 @@
using namespace std::chrono_literals;
iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;

View File

@@ -34,7 +34,7 @@
class iconceptelliptical : public elliptical {
Q_OBJECT
public:
explicit iconceptelliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
explicit iconceptelliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
public slots:
@@ -52,7 +52,7 @@ class iconceptelliptical : public elliptical {
private:
bool noWriteResistance = false;
bool noHeartService = false;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
QBluetoothServiceDiscoveryAgent *discoveryAgent;

View File

@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
keepbike::keepbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
keepbike::keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
@@ -355,7 +355,9 @@ void keepbike::stateChanged(QLowEnergyService::ServiceState state) {
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
if(!gattWriteCharacteristic.isValid()) {
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
}
Q_ASSERT(gattNotifyCharacteristic.isValid());
// establish hook into notifications

View File

@@ -36,7 +36,7 @@
class keepbike : public bike {
Q_OBJECT
public:
keepbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
bool connected() override;
@@ -62,7 +62,7 @@ class keepbike : public bike {
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotifyCharacteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;

View File

@@ -83,8 +83,6 @@ class kingsmithr1protreadmill : public treadmill {
bool initDone = false;
bool initRequest = false;
bool requestUnlock = false;
int64_t lastStart = 0;
int64_t lastStop = 0;
double lastTargetSpeed = -1;
bool targetSpeedMatchesSpeed = false;
double lastTargetSpeedMatchesSpeed = -1;

View File

@@ -27,6 +27,12 @@ kingsmithr2treadmill::kingsmithr2treadmill(uint32_t pollDeviceTime, bool noConso
if (forceInitInclination > 0) {
lastInclination = forceInitInclination;
}
if (lastControlMode != UNKNOWN_CONTROL_MODE) {
lastControlMode = UNKNOWN_CONTROL_MODE;
}
if (lastRunState != UNKNOWN_RUN_STATE) {
lastRunState = UNKNOWN_RUN_STATE;
}
refresh = new QTimer(this);
initDone = false;
@@ -205,16 +211,25 @@ void kingsmithr2treadmill::update() {
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
if (lastControlMode != MANUAL) {
writeCharacteristic(QStringLiteral("props ControlMode 1"), QStringLiteral("turn on treadmill to manual mode"),
false, true);
}
if (lastRunState != START) {
writeCharacteristic(QStringLiteral("props runState 1"), QStringLiteral("starting"), false, true);
}
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
// btinit(true);
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
if (lastRunState != STOP) {
writeCharacteristic(QStringLiteral("props runState 0"), QStringLiteral("stopping"), false, true);
}
// don't go to standby mode automatically
requestStop = -1;
}
if (requestFanSpeed != -1) {
@@ -297,6 +312,11 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
QStringList _props = data.split(QStringLiteral(" "), QString::SkipEmptyParts);
for (int i = 1; i < _props.size(); i += 2) {
QString key = _props.at(i);
// Error key only can have error code
// props Error "ErrorCode" -5000
if (!key.compare(QStringLiteral("Error"))) {
break;
}
// skip string params
if (!key.compare(QStringLiteral("mcu_version")) || !key.compare(QStringLiteral("goal"))) {
continue;
@@ -312,6 +332,8 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
double speed = props.value("CurrentSpeed", 0);
Cadence = props.value("spm", 0);
KINGSMITH_R2_CONTROL_MODE controlMode = (KINGSMITH_R2_CONTROL_MODE)(int)props.value("ControlMode", (double)UNKNOWN_CONTROL_MODE);
KINGSMITH_R2_RUN_STATE runState = (KINGSMITH_R2_RUN_STATE)(int)props.value("runState", (double)UNKNOWN_RUN_STATE);
// TODO:
// - RunningDistance (int; meter) : update each 10miters / 0.01 mile
@@ -320,6 +342,9 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
// - RunningTotalTime (int; sec)
// - spm (int) : steps per minute
// TODO: check 'ControlMode' and 'runState' of treadmill side
// then update current running status of application.
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
@@ -376,6 +401,16 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
// lastInclination = incline;
}
if (lastControlMode != controlMode) {
lastControlMode = controlMode;
if (controlMode != UNKNOWN_CONTROL_MODE) {
emit debug(QStringLiteral("kingsmith r2 is ready"));
initDone = true;
}
}
if (lastRunState != runState) {
lastRunState = runState;
}
firstCharacteristicChanged = false;
}
@@ -403,7 +438,6 @@ void kingsmithr2treadmill::btinit(bool startTape) {
// QStringLiteral("servers getProp 1 3 7 8 9 16 17 18 19 21 22 23 24 31"), QStringLiteral("init"), false, true);
writeCharacteristic(QStringLiteral("servers getProp 1 2 7 12 23 24 31"), QStringLiteral("init"), false, true);
// TODO need reset BurnCalories & RunningDistance
initDone = true;
}
@@ -415,7 +449,7 @@ void kingsmithr2treadmill::stateChanged(QLowEnergyService::ServiceState state) {
if (KS_NACH_X21C) {
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED7-0000-1000-8000-00805f9b34fb"));
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED8-0000-1000-8000-00805f9b34fb"));
} else if (KS_NGCH_G1C || KS_NACH_MXG) {
} else if (KS_NGCH_G1C || KS_NACH_MXG || KS_NACH_X21C_2) {
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED7-0000-1000-8000-00805f9b34fb"));
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED8-0000-1000-8000-00805f9b34fb"));
}
@@ -475,6 +509,13 @@ void kingsmithr2treadmill::serviceScanDone(void) {
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if(gattCommunicationChannelService == nullptr && KS_NACH_X21C) {
KS_NACH_X21C_2 = true;
KS_NACH_X21C = false;
qDebug() << "KS_NACH_X21C default service id not found";
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&kingsmithr2treadmill::stateChanged);
gattCommunicationChannelService->discoverDetails();

View File

@@ -82,6 +82,11 @@ class kingsmithr2treadmill : public treadmill {
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
enum KINGSMITH_R2_CONTROL_MODE { AUTOMODE = 0, MANUAL, STANDBY, UNKNOWN_CONTROL_MODE };
enum KINGSMITH_R2_RUN_STATE { STOP = 0, START, UNKNOWN_RUN_STATE };
KINGSMITH_R2_CONTROL_MODE lastControlMode = UNKNOWN_CONTROL_MODE;
KINGSMITH_R2_RUN_STATE lastRunState = UNKNOWN_RUN_STATE;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
@@ -92,6 +97,7 @@ class kingsmithr2treadmill : public treadmill {
bool initRequest = false;
bool KS_NACH_X21C = false;
bool KS_NACH_X21C_2 = false;
bool KS_NGCH_G1C = false;
bool KS_NACH_MXG = false;

View File

@@ -77,8 +77,6 @@ class lifefitnesstreadmill : public treadmill {
uint8_t firstStateChanged = 0;
double lastSpeed = 0.0;
double lastInclination = 0;
int64_t lastStart = 0;
int64_t lastStop = 0;
bool initDone = false;
bool initRequest = false;

View File

@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain) {
mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif

View File

@@ -35,7 +35,7 @@
class mcfbike : public bike {
Q_OBJECT
public:
mcfbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
resistance_t maxResistance() override { return max_resistance; }
@@ -60,7 +60,7 @@ class mcfbike : public bike {
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;
QByteArray lastPacket;

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;

View File

@@ -36,7 +36,7 @@
class mepanelbike : public bike {
Q_OBJECT
public:
mepanelbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected() override;
private:
@@ -55,7 +55,7 @@ class mepanelbike : 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;

View File

@@ -15,7 +15,7 @@
using namespace std::chrono_literals;
nautilusbike::nautilusbike(bool noWriteResistance, bool noHeartService, bool testResistance,
uint8_t bikeResistanceOffset, double bikeResistanceGain) {
int8_t bikeResistanceOffset, double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);

View File

@@ -32,7 +32,7 @@ class nautilusbike : public bike {
Q_OBJECT
public:
nautilusbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
~nautilusbike();
bool connected() override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
@@ -64,7 +64,7 @@ class nautilusbike : public bike {
bool noWriteResistance = false;
bool noHeartService = false;
bool testResistance = false;
uint8_t bikeResistanceOffset = 4;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool searchStopped = false;
uint8_t sec1Update = 0;

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