mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
839 Commits
2.16.57
...
build-1007
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc47fd19c | ||
|
|
e876ef97cd | ||
|
|
903409d962 | ||
|
|
9295554195 | ||
|
|
8d6cfe03ac | ||
|
|
70d5051a6f | ||
|
|
124e4ec561 | ||
|
|
036db83321 | ||
|
|
23dfa67fe5 | ||
|
|
79d7a09203 | ||
|
|
96d9fb485b | ||
|
|
68c4d954ef | ||
|
|
ef7bedacb8 | ||
|
|
e7a1373305 | ||
|
|
cfd06df25e | ||
|
|
89bc6d0529 | ||
|
|
0446000270 | ||
|
|
9908e8ca98 | ||
|
|
326f09c903 | ||
|
|
2b52206795 | ||
|
|
4cadcddac1 | ||
|
|
8910b8bf28 | ||
|
|
fc3287758e | ||
|
|
c94a03bb23 | ||
|
|
2c5ba21b99 | ||
|
|
4f00550400 | ||
|
|
dfd622c948 | ||
|
|
a7d66727f3 | ||
|
|
06a5c412bd | ||
|
|
0a3616ec0e | ||
|
|
b4226306b0 | ||
|
|
4f3353303a | ||
|
|
81b832071a | ||
|
|
d1966df73c | ||
|
|
e194291efb | ||
|
|
af88f6cd0d | ||
|
|
62838da761 | ||
|
|
7236608f59 | ||
|
|
2570f2843c | ||
|
|
a9fe9bebaf | ||
|
|
15a7c3abd0 | ||
|
|
7872950f65 | ||
|
|
4a711368e3 | ||
|
|
fbcc7e4478 | ||
|
|
e23af2e5f5 | ||
|
|
9c6fed4d48 | ||
|
|
26ac25d3ba | ||
|
|
19beae66bb | ||
|
|
037f660825 | ||
|
|
47e719bff0 | ||
|
|
020f30d8df | ||
|
|
7078508ba9 | ||
|
|
eb002332ed | ||
|
|
ea1da07e71 | ||
|
|
e1d32cd747 | ||
|
|
1bb3450512 | ||
|
|
ce1a78156e | ||
|
|
9d95e52d12 | ||
|
|
73c072583a | ||
|
|
253e2b7eab | ||
|
|
650c6de692 | ||
|
|
1ac4e20efb | ||
|
|
f08ea4346e | ||
|
|
c206886639 | ||
|
|
61bf953b1a | ||
|
|
1dcd35e825 | ||
|
|
5a7bb8b103 | ||
|
|
c4be4f068f | ||
|
|
4534c334bc | ||
|
|
9fa6d6d8b1 | ||
|
|
a5b34161c1 | ||
|
|
bf2c6929e1 | ||
|
|
2a451c3120 | ||
|
|
1169714908 | ||
|
|
14de4e4760 | ||
|
|
712f527ce0 | ||
|
|
0631c64ba5 | ||
|
|
85c43db53e | ||
|
|
8394bf3f19 | ||
|
|
bd1f25f016 | ||
|
|
95f340063a | ||
|
|
2be1d82e8d | ||
|
|
501af18298 | ||
|
|
724292bd34 | ||
|
|
cbbdebdf84 | ||
|
|
02c17dcf55 | ||
|
|
23d1f9d8c0 | ||
|
|
f4e0d3596d | ||
|
|
3b012bc946 | ||
|
|
33a5a2c80f | ||
|
|
e8b481d517 | ||
|
|
dcfa58b3a9 | ||
|
|
fd4106cf00 | ||
|
|
87dddac5f4 | ||
|
|
5488af7e35 | ||
|
|
0a3bd56f15 | ||
|
|
a5ae8f994b | ||
|
|
6a0b3e7fc4 | ||
|
|
a7620c38d0 | ||
|
|
0060e316dc | ||
|
|
b71321f301 | ||
|
|
c99ef80d78 | ||
|
|
2adf3fe27b | ||
|
|
ade033eb59 | ||
|
|
42666cf1e9 | ||
|
|
530f11f67c | ||
|
|
0391db60aa | ||
|
|
486c90a112 | ||
|
|
d1767797d7 | ||
|
|
fbe03d23f3 | ||
|
|
361280c131 | ||
|
|
04e0fc6e7c | ||
|
|
ab52eee127 | ||
|
|
94825252f7 | ||
|
|
93f13817be | ||
|
|
739ea4e841 | ||
|
|
fe3ad9ffb4 | ||
|
|
8fce809ee9 | ||
|
|
c156cbff99 | ||
|
|
268be8e0f5 | ||
|
|
5581e1c0e1 | ||
|
|
7fea2d442f | ||
|
|
74276764a6 | ||
|
|
a3e54782bb | ||
|
|
b7bc80b2a3 | ||
|
|
b869a41f3d | ||
|
|
9c7954945f | ||
|
|
13cd666718 | ||
|
|
c3e627e85b | ||
|
|
f23c24ae9b | ||
|
|
d27da35beb | ||
|
|
6457b205e4 | ||
|
|
bea7b61dcc | ||
|
|
2cc8d51a6c | ||
|
|
5410b806bb | ||
|
|
b937d8bd71 | ||
|
|
cd25cfab8e | ||
|
|
229e6ad461 | ||
|
|
977cae1cbd | ||
|
|
c8a9be2ca6 | ||
|
|
c3acf82a9b | ||
|
|
ddfc60bbf5 | ||
|
|
445646fe02 | ||
|
|
3dd3c8fb40 | ||
|
|
fb390b3618 | ||
|
|
ca5fb75f3a | ||
|
|
e881ce5f0f | ||
|
|
8002e47551 | ||
|
|
5b66b5705d | ||
|
|
d1c5521d2a | ||
|
|
74151edfb3 | ||
|
|
00f6747d7d | ||
|
|
0101955ad3 | ||
|
|
f6f9a95f06 | ||
|
|
3d82b89db0 | ||
|
|
8c7b549a45 | ||
|
|
3ad4dc1cfe | ||
|
|
7524314f74 | ||
|
|
94545e8958 | ||
|
|
2c74b2d2e2 | ||
|
|
108c190254 | ||
|
|
466209307e | ||
|
|
acccba59dc | ||
|
|
9325e2f9d1 | ||
|
|
36ebff2667 | ||
|
|
6d0d08b5fb | ||
|
|
e695a1e291 | ||
|
|
133488221b | ||
|
|
b186b672ea | ||
|
|
2badef3daf | ||
|
|
f8700296fb | ||
|
|
0f79fb56c7 | ||
|
|
d8412c95d4 | ||
|
|
469c239eed | ||
|
|
7fad542553 | ||
|
|
d0c0aeab84 | ||
|
|
9fd7123649 | ||
|
|
5b922043ec | ||
|
|
2953589ece | ||
|
|
5836990903 | ||
|
|
acd7e24382 | ||
|
|
71827e0546 | ||
|
|
7e8139e5a5 | ||
|
|
20d2b6ec9e | ||
|
|
be7d0e58a7 | ||
|
|
f20c449279 | ||
|
|
bf059715ec | ||
|
|
98cd3f22a2 | ||
|
|
bad290d104 | ||
|
|
3c55d025ce | ||
|
|
5c775ac5b4 | ||
|
|
9295aa58a7 | ||
|
|
96d68bbd39 | ||
|
|
7ddb6bc1ca | ||
|
|
a0145793a2 | ||
|
|
ecb37d67cc | ||
|
|
969476f368 | ||
|
|
3ae203d7ad | ||
|
|
e979b5aebe | ||
|
|
d568bccc28 | ||
|
|
301429182d | ||
|
|
8df78b9387 | ||
|
|
ba57309bcd | ||
|
|
8d573b3ee6 | ||
|
|
ba43ba8c21 | ||
|
|
47a3c24b03 | ||
|
|
40579fd376 | ||
|
|
bb17c1cc1a | ||
|
|
1cc8862a04 | ||
|
|
ff4606caa4 | ||
|
|
aff12a0462 | ||
|
|
3c5054acbd | ||
|
|
9a854f7810 | ||
|
|
1e731f7cbe | ||
|
|
c0cd6234f3 | ||
|
|
9cc79ab33a | ||
|
|
181de73a13 | ||
|
|
57e03c39f1 | ||
|
|
ff8a89d688 | ||
|
|
36f6fa7feb | ||
|
|
7d7e9cc79d | ||
|
|
2616ebe229 | ||
|
|
2b568c6260 | ||
|
|
f49f539e71 | ||
|
|
1845f3a5ae | ||
|
|
9aa337cd47 | ||
|
|
9b12c5c4bf | ||
|
|
1d12f7e475 | ||
|
|
e463ab9aae | ||
|
|
baa9de9059 | ||
|
|
e3b706f537 | ||
|
|
438bd2c195 | ||
|
|
5b546911ff | ||
|
|
b544e325ce | ||
|
|
c01fad2e29 | ||
|
|
a4d2f53207 | ||
|
|
a8b3fc3129 | ||
|
|
b7dec6d223 | ||
|
|
2d76ea554d | ||
|
|
b0f03dbe0a | ||
|
|
46429e04b4 | ||
|
|
7d479b7d88 | ||
|
|
c86651cdf6 | ||
|
|
63bfdba992 | ||
|
|
6767c42b14 | ||
|
|
70f2f2ecb5 | ||
|
|
691ec420b0 | ||
|
|
278f130906 | ||
|
|
531a88e326 | ||
|
|
d8e3e193b8 | ||
|
|
cda99d6f21 | ||
|
|
3cf3ae9135 | ||
|
|
7333361190 | ||
|
|
f75ebbb47f | ||
|
|
ce35fad608 | ||
|
|
8160d711a1 | ||
|
|
2a595ced57 | ||
|
|
9cb14b91ba | ||
|
|
92e008b3c8 | ||
|
|
f6d8924e18 | ||
|
|
ef955a03bc | ||
|
|
67c12f7342 | ||
|
|
5cfbc6855b | ||
|
|
aa9e9e20fe | ||
|
|
417f9c370d | ||
|
|
3f3bdbb83e | ||
|
|
c1c58a7a4d | ||
|
|
a6a3dd4c28 | ||
|
|
55c3dbe3b6 | ||
|
|
50e0c5aab9 | ||
|
|
fde0566abf | ||
|
|
be83fcbddb | ||
|
|
a7ccc1997a | ||
|
|
f6339a9f70 | ||
|
|
6d15d61f68 | ||
|
|
7b0b81694c | ||
|
|
e9ab643aab | ||
|
|
2e1ef17861 | ||
|
|
2527a3d303 | ||
|
|
62973d0564 | ||
|
|
59c428a14b | ||
|
|
064a4de214 | ||
|
|
998d95f6b2 | ||
|
|
e609d9ea93 | ||
|
|
0d191e6f02 | ||
|
|
3cb3aa4d1c | ||
|
|
f580e98d26 | ||
|
|
e1ad8aab73 | ||
|
|
340cdd9323 | ||
|
|
82ba5debcd | ||
|
|
d9a677b4ca | ||
|
|
cd56463286 | ||
|
|
3faa726fcd | ||
|
|
ef938df79f | ||
|
|
470ca0a98e | ||
|
|
fe9bb7e26a | ||
|
|
a30b1f5298 | ||
|
|
e7196d3033 | ||
|
|
09977ac703 | ||
|
|
ea9ed85bf7 | ||
|
|
1f3d819b24 | ||
|
|
096a025b79 | ||
|
|
0926f0b484 | ||
|
|
b6f6641204 | ||
|
|
65418c6e9a | ||
|
|
6abd0e677b | ||
|
|
ecfae5f416 | ||
|
|
e2f4d4e376 | ||
|
|
fb8de4606a | ||
|
|
09af8f98b3 | ||
|
|
70abcee27d | ||
|
|
d0deb6bee5 | ||
|
|
3494349961 | ||
|
|
5abc9ac9c0 | ||
|
|
d0360ea87b | ||
|
|
d31c5c4d53 | ||
|
|
f2f49464fc | ||
|
|
f6d0e068f6 | ||
|
|
17f699234c | ||
|
|
bf4c07aba4 | ||
|
|
a7fadf55aa | ||
|
|
4912807ae4 | ||
|
|
4a61c34d58 | ||
|
|
109dc901e9 | ||
|
|
9b8b6643f8 | ||
|
|
a26e79dac7 | ||
|
|
291c5d68f2 | ||
|
|
c85e838e33 | ||
|
|
94975e0117 | ||
|
|
eebb9359a6 | ||
|
|
40e6609297 | ||
|
|
af028504eb | ||
|
|
bfe4564659 | ||
|
|
85cdcaa457 | ||
|
|
cd5c10835c | ||
|
|
ce97c92645 | ||
|
|
d27b3b9ba0 | ||
|
|
0f96923006 | ||
|
|
bc7ac73de9 | ||
|
|
8d2fe9dd25 | ||
|
|
2d99106254 | ||
|
|
04d01efd0f | ||
|
|
da10fff966 | ||
|
|
991ee8674a | ||
|
|
7f24950498 | ||
|
|
405fb415d4 | ||
|
|
0aa4c27e02 | ||
|
|
add12366a0 | ||
|
|
2c496f6fe2 | ||
|
|
1381c19723 | ||
|
|
a8ddfc8b70 | ||
|
|
6680ff1cd9 | ||
|
|
863dc6378c | ||
|
|
be0465d094 | ||
|
|
42d2dcef7e | ||
|
|
c70fd8608d | ||
|
|
e217f929e8 | ||
|
|
b95c51a2be | ||
|
|
e3b635d107 | ||
|
|
ae1642d052 | ||
|
|
13e9f49a8b | ||
|
|
13f174fb1e | ||
|
|
3ef9e6304d | ||
|
|
398909b809 | ||
|
|
dc140a3dd5 | ||
|
|
b2fe23c32e | ||
|
|
46f0761f50 | ||
|
|
63258f2029 | ||
|
|
eeaf8fbe19 | ||
|
|
934e6dfa57 | ||
|
|
a9dd04f4fa | ||
|
|
34df54b96c | ||
|
|
e5ebd3c925 | ||
|
|
e99660ce40 | ||
|
|
d5357ed1c3 | ||
|
|
5a357c43e0 | ||
|
|
287ef5bdc7 | ||
|
|
89db56ae58 | ||
|
|
8f8aa888ca | ||
|
|
f9321a7bde | ||
|
|
4c59d2e2cb | ||
|
|
26d346bdf1 | ||
|
|
ba741ada31 | ||
|
|
2e8c4ebf9a | ||
|
|
579e30683a | ||
|
|
b1f893e944 | ||
|
|
415e305415 | ||
|
|
adb6928772 | ||
|
|
dc70bd1513 | ||
|
|
57f6a7d1a5 | ||
|
|
deafbd45d0 | ||
|
|
b2fa338e03 | ||
|
|
4e6a98e789 | ||
|
|
72ca19e3e7 | ||
|
|
2c7dac1837 | ||
|
|
de22d58e75 | ||
|
|
2f6d5415cc | ||
|
|
b8101ffa76 | ||
|
|
281590cf63 | ||
|
|
6759fb9ec0 | ||
|
|
c7dad4f1ad | ||
|
|
d29726632b | ||
|
|
95e5c58a92 | ||
|
|
8c5a3693c8 | ||
|
|
7aa1061b06 | ||
|
|
46bd172d59 | ||
|
|
d4dbaf5c57 | ||
|
|
6b4d47c79d | ||
|
|
1bd32ade9f | ||
|
|
5e1f3abd14 | ||
|
|
5bf7ecab64 | ||
|
|
dd75df0af8 | ||
|
|
72de08f9a3 | ||
|
|
13213edb4f | ||
|
|
872b618ea1 | ||
|
|
78e7fe76c6 | ||
|
|
980245bbfc | ||
|
|
2fd98a0be0 | ||
|
|
700f5debe5 | ||
|
|
06b4604e59 | ||
|
|
a6fd6b71d6 | ||
|
|
da194caf7c | ||
|
|
fa45e1040f | ||
|
|
09f0357763 | ||
|
|
f82e106fc1 | ||
|
|
906431b3a6 | ||
|
|
e82a76492a | ||
|
|
8c75c01017 | ||
|
|
dfabd2b414 | ||
|
|
8199dea809 | ||
|
|
1cb20088b2 | ||
|
|
203a9e5ca5 | ||
|
|
5a70586756 | ||
|
|
478beca96d | ||
|
|
637b57158a | ||
|
|
a2009fa91f | ||
|
|
4ee5fa3b00 | ||
|
|
36dde79dac | ||
|
|
9071d8a000 | ||
|
|
1c815e9d47 | ||
|
|
9b16779293 | ||
|
|
c702477dc8 | ||
|
|
b54df1d299 | ||
|
|
548a254262 | ||
|
|
d7330ad654 | ||
|
|
cfe02d3489 | ||
|
|
973bc4309d | ||
|
|
2112ed111f | ||
|
|
95d714ea0c | ||
|
|
cad60e3343 | ||
|
|
c1f0640eda | ||
|
|
d511f0ea95 | ||
|
|
96ad01f78c | ||
|
|
bdb5ed9ec1 | ||
|
|
49b054330c | ||
|
|
40feaa010d | ||
|
|
fbae0a48dc | ||
|
|
84a0f93cc1 | ||
|
|
642a89548c | ||
|
|
6ac19bd6b5 | ||
|
|
5eaf54ccf1 | ||
|
|
78f64180c7 | ||
|
|
d921c426e4 | ||
|
|
efb09e7a81 | ||
|
|
cb8939849b | ||
|
|
60e990a6c4 | ||
|
|
7c258dc4a4 | ||
|
|
bae7abb765 | ||
|
|
9b5eee64d8 | ||
|
|
edfdc0ae6c | ||
|
|
ff7bc5dbec | ||
|
|
cd918f3664 | ||
|
|
88ba9563ad | ||
|
|
b12a3d39a7 | ||
|
|
0bc0885439 | ||
|
|
9b38e93cf4 | ||
|
|
e87687f175 | ||
|
|
1e681de8a3 | ||
|
|
27bf0667fa | ||
|
|
732cfce4a0 | ||
|
|
516f301822 | ||
|
|
21a1a7b765 | ||
|
|
f1e57967d3 | ||
|
|
c6bf70b3e1 | ||
|
|
f2e9f5b28a | ||
|
|
02737c8b41 | ||
|
|
2455298bb1 | ||
|
|
779afb5b17 | ||
|
|
969843dde4 | ||
|
|
f371a5337d | ||
|
|
ef9e97a588 | ||
|
|
41de930b49 | ||
|
|
4e1adee102 | ||
|
|
c66f623173 | ||
|
|
625f62b057 | ||
|
|
a996cb32b9 | ||
|
|
d1fd8f6a70 | ||
|
|
4a33008c61 | ||
|
|
9d808b28a4 | ||
|
|
f69aee817c | ||
|
|
b07a75df90 | ||
|
|
1e2af212ca | ||
|
|
c36afc3173 | ||
|
|
1d5d29bf1d | ||
|
|
deb5eab79e | ||
|
|
e767e964ab | ||
|
|
eb540dc579 | ||
|
|
01cd02ef94 | ||
|
|
77b2ec46d1 | ||
|
|
cf6b1953e0 | ||
|
|
533fba4c6e | ||
|
|
60068dea5b | ||
|
|
1b597c16dd | ||
|
|
5d4b2a1fe1 | ||
|
|
c39f80bdeb | ||
|
|
a220efa9a4 | ||
|
|
23c803add1 | ||
|
|
e38d8f24b6 | ||
|
|
5eae092c52 | ||
|
|
fb374d966d | ||
|
|
04141fbb9f | ||
|
|
36ab693ae6 | ||
|
|
ad19afcb8f | ||
|
|
f0828fb66a | ||
|
|
17f4bd4d63 | ||
|
|
968c724480 | ||
|
|
533328dabc | ||
|
|
b11db80e1c | ||
|
|
bacc84a25b | ||
|
|
d37939ee37 | ||
|
|
716e99c943 | ||
|
|
93a4ef1771 | ||
|
|
8bf9feacf1 | ||
|
|
4441090687 | ||
|
|
eff72fddc1 | ||
|
|
e54a9e5961 | ||
|
|
eb4e320679 | ||
|
|
bbe0a4091c | ||
|
|
15f013071c | ||
|
|
7ea23b0ddc | ||
|
|
f132a00d30 | ||
|
|
70c0bd9120 | ||
|
|
45d0b78ec2 | ||
|
|
a276861729 | ||
|
|
9846dc65a4 | ||
|
|
c1bcdc045c | ||
|
|
f18cd53c80 | ||
|
|
e6b70c03a4 | ||
|
|
30562f0ed4 | ||
|
|
5d66c6c513 | ||
|
|
762c33440e | ||
|
|
c96ab6b86a | ||
|
|
b96cc70d51 | ||
|
|
7f987c110a | ||
|
|
20473f1b31 | ||
|
|
cd5ce73913 | ||
|
|
5b3e089b40 | ||
|
|
a3252bd47d | ||
|
|
1bf4c33a7a | ||
|
|
e1748022f2 | ||
|
|
ea93ab925c | ||
|
|
77a361905b | ||
|
|
bc9e33aead | ||
|
|
7305e4fab6 | ||
|
|
3b3d893447 | ||
|
|
bb69c9a86a | ||
|
|
5b6012ebbc | ||
|
|
d27335410b | ||
|
|
8801f1d6cf | ||
|
|
114ee5317a | ||
|
|
a675451f5e | ||
|
|
7cdf9782af | ||
|
|
1a2c5683ad | ||
|
|
99bb36b7f5 | ||
|
|
9b0971973f | ||
|
|
f8a1a33144 | ||
|
|
73ef8b4f03 | ||
|
|
47fea6ee8e | ||
|
|
9ba5bdbb1b | ||
|
|
79a898d32b | ||
|
|
b4a81243b8 | ||
|
|
573394366b | ||
|
|
bce2d2605c | ||
|
|
3b943784c0 | ||
|
|
52d91de7e4 | ||
|
|
ca830a988b | ||
|
|
2955ac9751 | ||
|
|
930cc4e016 | ||
|
|
9bac3f8e7c | ||
|
|
02fbff3f84 | ||
|
|
e8fb563b77 | ||
|
|
b9d5e6141f | ||
|
|
83185e0e95 | ||
|
|
fe801547dc | ||
|
|
1ade078827 | ||
|
|
36cf326fa4 | ||
|
|
0812f419c6 | ||
|
|
ee3f6a1d1f | ||
|
|
5e9e82e3f5 | ||
|
|
f1636f7915 | ||
|
|
c8555e543a | ||
|
|
496ea9f2be | ||
|
|
892d949e72 | ||
|
|
5981e7cd00 | ||
|
|
a6cc8b5e87 | ||
|
|
f07e7f9c1f | ||
|
|
7e3eab5b8c | ||
|
|
a720717d5c | ||
|
|
ff16880fae | ||
|
|
aba3ff502c | ||
|
|
4fe689ac55 | ||
|
|
6b0777233d | ||
|
|
c8c32a0860 | ||
|
|
f4b6663c5d | ||
|
|
328f0992b6 | ||
|
|
f942282b7e | ||
|
|
c90849063e | ||
|
|
4f057bb88f | ||
|
|
4cae862455 | ||
|
|
4b6da2bb95 | ||
|
|
7f0c589c50 | ||
|
|
df928caf99 | ||
|
|
8142e75323 | ||
|
|
95f51682c9 | ||
|
|
4b74c22f95 | ||
|
|
4bb4aba109 | ||
|
|
10f39dac68 | ||
|
|
352aa40d0b | ||
|
|
b6c2704e4f | ||
|
|
fc7b043c8f | ||
|
|
f365fb3423 | ||
|
|
f6ffb08ed6 | ||
|
|
78101b6191 | ||
|
|
d4f74c3287 | ||
|
|
e09afb91db | ||
|
|
f87c08d580 | ||
|
|
2029579c87 | ||
|
|
d8a9e88736 | ||
|
|
ee1ed94692 | ||
|
|
73798b87db | ||
|
|
c98bf5ca35 | ||
|
|
f7a2d30554 | ||
|
|
b826e93644 | ||
|
|
e9a446a2e7 | ||
|
|
7aab56ea93 | ||
|
|
3abf955713 | ||
|
|
430f41e5b9 | ||
|
|
fdbb70fd73 | ||
|
|
bddd8cfaae | ||
|
|
b81656c369 | ||
|
|
4fe2cf6ea6 | ||
|
|
c23b936eca | ||
|
|
445e8691f6 | ||
|
|
a100d4cc96 | ||
|
|
6384268aff | ||
|
|
b5c68f5e6c | ||
|
|
6092bbd3c3 | ||
|
|
3e6c170289 | ||
|
|
24fe3c625d | ||
|
|
8dd03df9a2 | ||
|
|
f81929fe60 | ||
|
|
dd7a5cb82b | ||
|
|
d233f04f67 | ||
|
|
09e51d6013 | ||
|
|
06f72ba937 | ||
|
|
13f9502592 | ||
|
|
71afb2881f | ||
|
|
2094222a54 | ||
|
|
369653bd24 | ||
|
|
3327b8b1e6 | ||
|
|
fc59813aef | ||
|
|
01cf3a1f95 | ||
|
|
faa62aae2b | ||
|
|
0e22f92002 | ||
|
|
9fdf9326c5 | ||
|
|
ff538529ec | ||
|
|
cc517fc7ee | ||
|
|
0fc300c451 | ||
|
|
233862ec99 | ||
|
|
a44158730a | ||
|
|
e97c2e4a89 | ||
|
|
d3ba36ac53 | ||
|
|
a4b54a5669 | ||
|
|
8365d4dae6 | ||
|
|
76dcac37d6 | ||
|
|
c44bc1087d | ||
|
|
38cde07626 | ||
|
|
8a849c093c | ||
|
|
ef7cae7b38 | ||
|
|
b7bcfddcee | ||
|
|
4e702a62d4 | ||
|
|
0c990135eb | ||
|
|
25cb605d6d | ||
|
|
4717a79b5a | ||
|
|
8d39ace35b | ||
|
|
236c3bc7d0 | ||
|
|
e93caebe2a | ||
|
|
291d09ce41 | ||
|
|
98128f3fa9 | ||
|
|
148bcb3548 | ||
|
|
f3bcbd3312 | ||
|
|
736dfefc31 | ||
|
|
91217a51c9 | ||
|
|
de8fcada5b | ||
|
|
afffaa6a85 | ||
|
|
b54d4cea42 | ||
|
|
fc62fcf461 | ||
|
|
57ef6071b7 | ||
|
|
ad86c1abff | ||
|
|
9963508b79 | ||
|
|
5f958b4618 | ||
|
|
e4930ecbcb | ||
|
|
9d8be8ae4f | ||
|
|
6ddbfe4a86 | ||
|
|
ebf49d20db | ||
|
|
5196db1a84 | ||
|
|
491f05dc14 | ||
|
|
51dabda33e | ||
|
|
1286c2105d | ||
|
|
6accc034ab | ||
|
|
6de18ee563 | ||
|
|
a2dcda53df | ||
|
|
84d60f0301 | ||
|
|
49c91df0b7 | ||
|
|
e82d2de889 | ||
|
|
c558aadc8f | ||
|
|
21c5b62d71 | ||
|
|
8ae32e7daf | ||
|
|
f195ef1c30 | ||
|
|
1bd865f142 | ||
|
|
de5c37189b | ||
|
|
d4595c7bdb | ||
|
|
608e240046 | ||
|
|
2b76b27006 | ||
|
|
288709ca27 | ||
|
|
28a629fa62 | ||
|
|
69f440ecee | ||
|
|
fb9e0c285e | ||
|
|
954948de9e | ||
|
|
b0b702733f | ||
|
|
3a88433d1c | ||
|
|
a1095b4219 | ||
|
|
7a7619438c | ||
|
|
12dff6404c | ||
|
|
a8e3a672d4 | ||
|
|
3ebd94a278 | ||
|
|
4a7f22f699 | ||
|
|
e0ac6c2ec4 | ||
|
|
7723be4356 | ||
|
|
c53bf1a2ab | ||
|
|
6f8d1fefac | ||
|
|
9edea7d50d | ||
|
|
17e6afd09d | ||
|
|
27d58a6f26 | ||
|
|
d4028290ed | ||
|
|
5cf21ee3ce | ||
|
|
671be1d3ab | ||
|
|
846f921c8f | ||
|
|
ec67d56de3 | ||
|
|
b13d3b907b | ||
|
|
79054d4ef2 | ||
|
|
233f9e27b2 | ||
|
|
5709b9570f | ||
|
|
ef0152da09 | ||
|
|
d5c7fc894e | ||
|
|
7a1a4f7a61 | ||
|
|
bebed7e6ca | ||
|
|
487a6da9d4 | ||
|
|
9cee8ea043 | ||
|
|
5f83862ad7 | ||
|
|
42545caa21 | ||
|
|
1a6ca728d5 | ||
|
|
71fc9218c2 | ||
|
|
5116a1260a | ||
|
|
4a70fdf554 | ||
|
|
807e144c5e | ||
|
|
08fb218f8c | ||
|
|
7787e6468a | ||
|
|
2a059cf493 | ||
|
|
d84e52073a | ||
|
|
3c34ec7d45 | ||
|
|
2c54b30974 | ||
|
|
6631f7d1ba | ||
|
|
01a7bb8e49 | ||
|
|
d226c65cc5 | ||
|
|
4eeac31d06 | ||
|
|
f8c3415db3 | ||
|
|
bded1f8697 | ||
|
|
4ff72dccbf | ||
|
|
afae83e923 | ||
|
|
6851eaf386 | ||
|
|
025384ef5b | ||
|
|
b37fef3bbd | ||
|
|
034e1dafb5 | ||
|
|
f95713dc80 | ||
|
|
90ece532db | ||
|
|
8a5d418ada | ||
|
|
5ea354fd19 | ||
|
|
fdf5eca726 | ||
|
|
70f5dc2cc4 | ||
|
|
f48e774d31 | ||
|
|
2195dcb1cb | ||
|
|
e5e7ccb777 | ||
|
|
efbf6148ac | ||
|
|
1380d79909 | ||
|
|
f5d1e6f7d2 | ||
|
|
d7acfc4dea | ||
|
|
352049a4c1 | ||
|
|
002ae1d62b | ||
|
|
674f01b9f4 | ||
|
|
93255eee35 | ||
|
|
53aa1b361b | ||
|
|
c6485f891c | ||
|
|
ab73c97c36 | ||
|
|
49d56b1fc6 | ||
|
|
5a6afbb500 | ||
|
|
b0a76d1aa0 | ||
|
|
9a99d0fc7b | ||
|
|
2b43974dae | ||
|
|
33e5eaeb41 | ||
|
|
75c04e3f6b | ||
|
|
6a74c2015e | ||
|
|
595a472780 | ||
|
|
e937cd7481 | ||
|
|
5a72504eba | ||
|
|
acb1f3964a | ||
|
|
00afd12f34 | ||
|
|
54abdcda76 | ||
|
|
7c1280c4e1 | ||
|
|
50df26cbb9 | ||
|
|
db1de5ef31 | ||
|
|
461068532a | ||
|
|
4a23556257 | ||
|
|
a6ebc614e3 | ||
|
|
a6b530dc60 | ||
|
|
96c5b7e595 | ||
|
|
51413841cc | ||
|
|
fae69702fb | ||
|
|
f0fdf428d2 |
313
.github/workflows/main.yml
vendored
313
.github/workflows/main.yml
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
@@ -140,7 +141,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
@@ -167,7 +168,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
@@ -195,14 +196,14 @@ jobs:
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-binary
|
||||
path: windows-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-binary-no-python
|
||||
path: windows-binary-no-python.zip
|
||||
@@ -337,7 +338,7 @@ jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
linux-x86-build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
@@ -433,7 +434,7 @@ jobs:
|
||||
run: qmake; make -j8
|
||||
|
||||
- name: Archive linux-desktop binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-desktop-binary
|
||||
path: src/qdomyos-zwift
|
||||
@@ -442,7 +443,7 @@ jobs:
|
||||
run: cd tst; GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests; cd ..
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test_results_xml
|
||||
@@ -585,7 +586,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '11'
|
||||
java-version: '11.0.23+9'
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
|
||||
@@ -593,14 +594,6 @@ jobs:
|
||||
- name: download 3rd party files for qthttpserver
|
||||
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
|
||||
|
||||
- name: Build qthttpserver
|
||||
run: |
|
||||
cd src/qthttpserver
|
||||
qmake
|
||||
make -j8
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
- name: Set Android NDK 21 && build
|
||||
run: |
|
||||
# Install NDK 21 after GitHub update
|
||||
@@ -622,6 +615,14 @@ jobs:
|
||||
|
||||
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
|
||||
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
|
||||
|
||||
# QTHTTPSERVER must use the same NDK
|
||||
cd src/qthttpserver
|
||||
qmake
|
||||
make -j8
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
|
||||
sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json
|
||||
cat src/android-qdomyos-zwift-deployment-settings.json
|
||||
@@ -630,7 +631,7 @@ jobs:
|
||||
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
|
||||
|
||||
- name: Archive apk binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fdroid-android-trial
|
||||
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
|
||||
@@ -651,7 +652,7 @@ jobs:
|
||||
|
||||
ios-build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: macos-12
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -824,14 +825,44 @@ jobs:
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
cd ..
|
||||
|
||||
- name: Clone vcpkg
|
||||
run: git clone https://github.com/microsoft/vcpkg.git
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Bootstrap vcpkg
|
||||
run: .\vcpkg\bootstrap-vcpkg.bat
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Create vcpkg.json
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
echo '{
|
||||
"name": "qdomyos-zwift",
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
|
||||
"dependencies": [
|
||||
"protobuf",
|
||||
"protobuf-c",
|
||||
"abseil"
|
||||
],
|
||||
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
|
||||
}' > vcpkg.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
@@ -839,6 +870,7 @@ jobs:
|
||||
cp ../../windows/*.py .
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
mkdir python
|
||||
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
|
||||
@@ -849,21 +881,25 @@ jobs:
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Build without python
|
||||
run: |
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
@@ -883,14 +919,14 @@ jobs:
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-binary
|
||||
path: windows-msvc2019-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-binary-no-python
|
||||
path: windows-msvc2019-binary-no-python.zip
|
||||
@@ -930,6 +966,9 @@ jobs:
|
||||
repository: qt-labs/qthttpserver
|
||||
path: "src/qthttpserver"
|
||||
|
||||
- name: Install CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
@@ -973,8 +1012,38 @@ jobs:
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
cd ..
|
||||
|
||||
- name: Clone vcpkg
|
||||
run: git clone https://github.com/microsoft/vcpkg.git
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Bootstrap vcpkg
|
||||
run: .\vcpkg\bootstrap-vcpkg.bat
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Create vcpkg.json
|
||||
working-directory: ${{ runner.workspace }}
|
||||
run: |
|
||||
echo '{
|
||||
"name": "qdomyos-zwift",
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
|
||||
"dependencies": [
|
||||
"protobuf",
|
||||
"protobuf-c",
|
||||
"abseil"
|
||||
],
|
||||
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
|
||||
}' > vcpkg.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
cd src
|
||||
echo "#define AISERVER" >> aiserver.h
|
||||
cd ..
|
||||
@@ -983,7 +1052,7 @@ jobs:
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cp qdomyos-zwift.* output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
@@ -993,6 +1062,7 @@ jobs:
|
||||
cp ../../windows/zwift-workout-ai-server.py zwift-workout.py
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
@@ -1006,19 +1076,203 @@ jobs:
|
||||
run: Compress-Archive src/debug/output windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-ai-server-binary
|
||||
path: windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
raspberry-pi-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Secrets
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
echo "#define LICENSE" >> secret.h
|
||||
cd ..
|
||||
|
||||
- name: Build for Raspberry Pi
|
||||
uses: docker://arm32v7/debian:bullseye-20241016
|
||||
with:
|
||||
args: >
|
||||
bash -c "
|
||||
set -ex &&
|
||||
apt-get update &&
|
||||
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
|
||||
export QT_SELECT=qt5 &&
|
||||
export PATH=/usr/lib/qt5/bin:$PATH &&
|
||||
cd /github/workspace &&
|
||||
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
|
||||
cat qdomyos-zwift.pro &&
|
||||
qmake &&
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/qdomyos-zwift src/qdomyos-zwift-32bit
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-binary
|
||||
path: src/qdomyos-zwift-32bit
|
||||
|
||||
raspberry-pi-build-and-image-64bit:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Secrets
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
echo "#define LICENSE" >> secret.h
|
||||
cd ..
|
||||
|
||||
- name: Build for Raspberry Pi 64-bit
|
||||
uses: docker://arm64v8/debian:bullseye-20241016
|
||||
with:
|
||||
args: >
|
||||
bash -c "
|
||||
set -ex &&
|
||||
apt-get update &&
|
||||
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
|
||||
export QT_SELECT=qt5 &&
|
||||
export PATH=/usr/lib/qt5/bin:$PATH &&
|
||||
cd /github/workspace &&
|
||||
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
|
||||
cat qdomyos-zwift.pro &&
|
||||
qmake &&
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-64bit-binary
|
||||
path: src/qdomyos-zwift-64bit
|
||||
|
||||
- name: Download and expand Raspberry Pi OS image
|
||||
run: |
|
||||
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz
|
||||
xz -d 2024-10-22-raspios-bookworm-arm64-lite.img.xz
|
||||
ORIGINAL_SIZE=$(stat -c %s 2024-10-22-raspios-bookworm-arm64-lite.img)
|
||||
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
|
||||
truncate -s $NEW_SIZE 2024-10-22-raspios-bookworm-arm64-lite.img
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y parted
|
||||
sudo parted 2024-10-22-raspios-bookworm-arm64-lite.img resizepart 2 100%
|
||||
|
||||
- name: Mount Raspberry Pi image
|
||||
run: |
|
||||
sudo apt-get install -y kpartx qemu-user-static
|
||||
LOOP_DEVICE=$(sudo losetup -f --show 2024-10-22-raspios-bookworm-arm64-lite.img)
|
||||
echo "Loop device is $LOOP_DEVICE"
|
||||
sudo kpartx -av $LOOP_DEVICE
|
||||
sudo mkdir -p /mnt/raspbian
|
||||
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
|
||||
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
|
||||
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
|
||||
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
|
||||
sudo mkdir -p /mnt/raspbian_p1
|
||||
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p1 /mnt/raspbian_p1
|
||||
|
||||
- name: Install Qt and dependencies on Raspberry Pi image
|
||||
run: |
|
||||
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
|
||||
apt-get update
|
||||
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
|
||||
EOF
|
||||
|
||||
- name: Copy binary to Raspberry Pi image
|
||||
run: |
|
||||
sudo cp src/qdomyos-zwift-64bit /mnt/raspbian/home/pi/qdomyos-zwift
|
||||
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
|
||||
|
||||
- name: Setup auto-start for qdomyos-zwift
|
||||
run: |
|
||||
echo '[Unit]
|
||||
Description=QDomyos-Zwift
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/pi/qdomyos-zwift
|
||||
User=pi
|
||||
Environment=DISPLAY=:0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target' | sudo tee /mnt/raspbian/etc/systemd/system/qdomyos-zwift.service
|
||||
sudo chroot /mnt/raspbian systemctl enable qdomyos-zwift.service
|
||||
|
||||
- name: Modify boot config to enable bluetooth
|
||||
run: |
|
||||
# The following line makes it specific for Raspberry Pi Zero 2W.
|
||||
# though I expect it is needed for Raspberry Pi 3B and maybe others as well
|
||||
echo "[pi02]" | sudo tee -a /mnt/raspbian_p1/config.txt
|
||||
echo "dtoverlay=miniuart-bt" | sudo tee -a /mnt/raspbian_p1/config.txt
|
||||
|
||||
- name: Unmount Raspberry Pi image
|
||||
run: |
|
||||
sudo umount /mnt/raspbian
|
||||
sudo umount /mnt/raspbian_p1
|
||||
sudo kpartx -d ${{ env.LOOP_DEVICE }}
|
||||
sudo losetup -d ${{ env.LOOP_DEVICE }}
|
||||
|
||||
- name: Compress modified Raspberry Pi image
|
||||
run: |
|
||||
xz -z 2024-10-22-raspios-bookworm-arm64-lite.img
|
||||
|
||||
- name: Upload Raspberry Pi image as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-64bit-image
|
||||
path: 2024-10-22-raspios-bookworm-arm64-lite.img.xz
|
||||
|
||||
upload_to_release:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event_name == 'schedule'
|
||||
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build] # Specify the job dependencies
|
||||
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build, raspberry-pi-build-and-image-64bit, raspberry-pi-build] # Specify the job dependencies
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Update nightly release
|
||||
uses: andelf/nightly-release@main
|
||||
env:
|
||||
@@ -1044,3 +1298,6 @@ jobs:
|
||||
windows-binary-no-python/*
|
||||
windows-binary/*
|
||||
fdroid-android-trial/*
|
||||
raspberry-pi-binary/qdomyos-zwift-32bit
|
||||
raspberry-pi-64bit-binary/qdomyos-zwift-64bit
|
||||
2024-10-22-raspios-bookworm-arm64-lite.img.xz
|
||||
|
||||
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "(Windows) Launch",
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
|
||||
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
|
||||
"sourceFileMap": {
|
||||
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/"
|
||||
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
22
README.md
22
README.md
@@ -96,34 +96,36 @@ Zwift bridge for Treadmills and Bike!
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Resistance shifting with bluetooth remote|X||X|||
|
||||
|TTS support|X|X|X|X||
|
||||
|Zwift Play & Click support|X|||||
|
||||
|MQTT integration|X|X|X|X||
|
||||
|OpenSoundControl integration|X|X|X|X||
|
||||
|
||||
|
||||
### Installation
|
||||
|
||||
You can install it on multiple platforms.
|
||||
Read the [installation procedure](docs/10_Installation.md)
|
||||
You can install it on multiple platforms.
|
||||
Read the [installation procedure](docs/10_Installation.md)
|
||||
|
||||
|
||||
### Tested on
|
||||
|
||||
You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IOS and Android are also supported.
|
||||
|
||||
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
The QDomyos-Zwift application can run on [Macintosh or Linux devices](docs/10_Installation.md) iOS, and Android.
|
||||
It supports any [FTMS-compatible application](docs/20_supported_devices_and_applications.md) software and most [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
|
||||
### No GUI version
|
||||
|
||||
run as
|
||||
|
||||
$ sudo ./qdomyos-zwift -no-gui
|
||||
$ sudo ./qdomyos-zwift -no-gui
|
||||
|
||||
### Reference
|
||||
|
||||
https://github.com/ProH4Ck/treadmill-bridge
|
||||
=> GitHub Repository: [QDomyos-Zwift on GitHub](https://github.com/ProH4Ck/treadmill-bridge)
|
||||
|
||||
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
|
||||
=> Treadmill Incline Reference: [What Is 10 Degrees in Incline on a Treadmill?](https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/)
|
||||
|
||||
Icons used in this documentation come from [flaticon.com](https://www.flaticon.com)
|
||||
=> Icon Attribution: Icons used in this documentation are from [Flaticon.com](https://www.flaticon.com)
|
||||
|
||||
### Blog
|
||||
|
||||
https://robertoviola.cloud
|
||||
=> Related Blog: [Roberto Viola's Blog](https://robertoviola.cloud)
|
||||
|
||||
@@ -6,21 +6,6 @@
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */ = {
|
||||
isa = PBXAggregateTarget;
|
||||
buildConfigurationList = 6CC3B5D2136C7CD6A5CF5A59 /* Build configuration list for PBXNativeTarget "qdomyoszwift" */;
|
||||
buildPhases = (
|
||||
5E618435888B9D49F8540165 /* Qt Qmake */,
|
||||
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "Qt Preprocess";
|
||||
productName = "Qt Preprocess";
|
||||
};
|
||||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
023642106C14651D2E1F4D5D /* dialogplugin in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = B9CCC4658EA620ABBD832E71 /* dialogplugin */; };
|
||||
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 3088C65BF5992B009FFC93B5 /* virtualtreadmill.cpp */; settings = {ATTRIBUTES = (); }; };
|
||||
@@ -143,6 +128,8 @@
|
||||
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87083D9526678EFA0072410D /* zwiftworkout.cpp */; };
|
||||
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */; };
|
||||
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
|
||||
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */; };
|
||||
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB42CEF8FD200839641 /* technogymbike.cpp */; };
|
||||
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706B29C48AEA0094D0F3 /* handleurl.cpp */; };
|
||||
8710706E29C48AF30094D0F3 /* moc_handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */; };
|
||||
8710707329C4A5E70094D0F3 /* GarminConnect.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710707229C4A5E70094D0F3 /* GarminConnect.swift */; };
|
||||
@@ -152,6 +139,8 @@
|
||||
871189192893CECF006A04D1 /* libqavfmediaplayer.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189182893CECC006A04D1 /* libqavfmediaplayer.a */; };
|
||||
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */; };
|
||||
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */; };
|
||||
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */; };
|
||||
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E82C75E6DB009BAC05 /* antbike.cpp */; };
|
||||
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A08276BBAF600141463 /* virtualrower.cpp */; };
|
||||
87182A0B276BBB1200141463 /* moc_virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A0A276BBB1200141463 /* moc_virtualrower.cpp */; };
|
||||
8718CBA2263063BD004BF4EE /* soleelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8718CB9A263063BC004BF4EE /* soleelliptical.cpp */; };
|
||||
@@ -166,6 +155,24 @@
|
||||
871B9FD2265E6A8800DB41F4 /* powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */; };
|
||||
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */; };
|
||||
871E4CD125A6FB5A00E18D6D /* BLEPeripheralManager.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */; };
|
||||
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */; };
|
||||
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */; };
|
||||
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */; };
|
||||
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */; };
|
||||
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */; };
|
||||
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */; };
|
||||
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */; };
|
||||
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F52CE65451008C2C17 /* qmqttconnection.cpp */; };
|
||||
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089042CE65451008C2C17 /* qmqttsubscription.cpp */; };
|
||||
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F32CE65451008C2C17 /* qmqttclient.cpp */; };
|
||||
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */; };
|
||||
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890D2CE65451008C2C17 /* qmqtttype.cpp */; };
|
||||
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */; };
|
||||
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FE2CE65451008C2C17 /* qmqttmessage.cpp */; };
|
||||
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */; };
|
||||
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */; };
|
||||
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */; };
|
||||
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */; };
|
||||
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */; };
|
||||
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
|
||||
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
|
||||
@@ -174,6 +181,8 @@
|
||||
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */; };
|
||||
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */; };
|
||||
8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */; };
|
||||
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */; };
|
||||
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */; };
|
||||
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
|
||||
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
|
||||
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
|
||||
@@ -254,6 +263,8 @@
|
||||
873CD23027EF8EF5000131BC /* iosinapppurchasetransaction.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873CD22727EF8E4B000131BC /* iosinapppurchasetransaction.mm */; };
|
||||
873D388B29B0D745006A2611 /* ConnectIQ.xcframework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 873D388A29B0D744006A2611 /* ConnectIQ.xcframework */; };
|
||||
873D388C29B0D745006A2611 /* ConnectIQ.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 873D388A29B0D744006A2611 /* ConnectIQ.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
873D3C4A2C296B0100770CB9 /* moc_jumprope.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873D3C492C296B0100770CB9 /* moc_jumprope.cpp */; };
|
||||
873D3C4D2C296B3800770CB9 /* jumprope.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873D3C4B2C296B3700770CB9 /* jumprope.cpp */; };
|
||||
873F022F274BE471002D0349 /* mcfbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873F022D274BE471002D0349 /* mcfbike.cpp */; };
|
||||
873F0231274BE47D002D0349 /* moc_mcfbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 873F0230274BE47D002D0349 /* moc_mcfbike.cpp */; };
|
||||
87420DF6269D770F000C5EC6 /* libQt5WebView.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87420DF5269D770F000C5EC6 /* libQt5WebView.a */; };
|
||||
@@ -277,6 +288,10 @@
|
||||
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
|
||||
87586A4125B8340E00A243C4 /* proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4025B8340E00A243C4 /* proformbike.cpp */; };
|
||||
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4225B8341B00A243C4 /* moc_proformbike.cpp */; };
|
||||
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */; };
|
||||
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */; };
|
||||
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA94B2D130F8100667EE6 /* moc_osc.cpp */; };
|
||||
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9502D130FBC00667EE6 /* osc.cpp */; };
|
||||
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69B826342E8D0009FD78 /* spirittreadmill.cpp */; };
|
||||
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */; };
|
||||
8762D50F2601F7EA00F6F049 /* M3iNS.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D50B2601F7EA00F6F049 /* M3iNS.mm */; };
|
||||
@@ -350,8 +365,13 @@
|
||||
876F9B61275385D8006AE6FA /* moc_fitmetria_fanfit.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876F9B60275385D8006AE6FA /* moc_fitmetria_fanfit.cpp */; };
|
||||
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
|
||||
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */; };
|
||||
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */; };
|
||||
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877350F62D1C08E50070CBD8 /* SmartControl.cpp */; };
|
||||
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008129E876F7008E48B7 /* iconceptelliptical.cpp */; };
|
||||
8775008529E87713008E48B7 /* moc_iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */; };
|
||||
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */; };
|
||||
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B52C98629B00BB1697 /* sportstechelliptical.cpp */; };
|
||||
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74122893D705009A64C8 /* CoreVideo.framework */; };
|
||||
877A7609269D8E9F0024DD2C /* WebKit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 877A7608269D8E9F0024DD2C /* WebKit.framework */; };
|
||||
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */; };
|
||||
@@ -370,6 +390,8 @@
|
||||
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531672711A3EB004B153D /* moc_fakebike.cpp */; };
|
||||
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */; };
|
||||
8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
|
||||
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */; };
|
||||
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */; };
|
||||
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
|
||||
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
|
||||
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
|
||||
@@ -417,7 +439,11 @@
|
||||
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
|
||||
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
|
||||
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */; };
|
||||
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */; };
|
||||
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */; };
|
||||
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A4B75F25AF27CB0027EF3C /* metric.cpp */; };
|
||||
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */; };
|
||||
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */; };
|
||||
87ADD2BB27634C1500B7A0AB /* technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */; };
|
||||
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */; };
|
||||
87AE0CB227760DCB00E547E9 /* virtualtreadmill_zwift.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87AE0CB127760DCB00E547E9 /* virtualtreadmill_zwift.swift */; };
|
||||
@@ -429,6 +455,8 @@
|
||||
87B617F225F260150094A1CB /* moc_fitshowtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */; };
|
||||
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F025F260140094A1CB /* moc_snodebike.cpp */; };
|
||||
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F125F260150094A1CB /* moc_screencapture.cpp */; };
|
||||
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */; };
|
||||
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B871922CE1E94D009B06CA /* zwifthubbike.swift */; };
|
||||
87BAC3BF2BA497160003E925 /* PrivacyInfo.xcprivacy in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */; };
|
||||
87BAC3C12BA497350003E925 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */; };
|
||||
87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */; };
|
||||
@@ -518,6 +546,8 @@
|
||||
87E5D2C825E69F4700BDBE6C /* moc_horizontreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E5D2C725E69F4700BDBE6C /* moc_horizontreadmill.cpp */; };
|
||||
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */; };
|
||||
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85925B5C8B900371D28 /* flywheelbike.cpp */; };
|
||||
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */; };
|
||||
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */; };
|
||||
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917427EE5FB3002535E1 /* nautilusbike.cpp */; };
|
||||
87EB918227EE5FE7002535E1 /* moc_inapptransaction.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */; };
|
||||
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917827EE5FE7002535E1 /* moc_inappstore.cpp */; };
|
||||
@@ -542,6 +572,10 @@
|
||||
87FA11AB27C5ECD1008AC5D1 /* ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AA27C5ECD1008AC5D1 /* ultrasportbike.cpp */; };
|
||||
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */; };
|
||||
87FA94672B6B89FD00B6AB9A /* SwiftUI.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */; };
|
||||
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */; };
|
||||
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */; };
|
||||
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */; };
|
||||
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */; };
|
||||
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */; };
|
||||
87FFA13727BBE3FF00924E4E /* solebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FFA13527BBE3FE00924E4E /* solebike.cpp */; };
|
||||
@@ -614,13 +648,6 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
876E4E0E2594739400BD5714 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = E9F0AFC024A6F2D65CE84E08;
|
||||
remoteInfo = "Qt Preprocess";
|
||||
};
|
||||
876E4E1C2594748000BD5714 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
|
||||
@@ -903,6 +930,8 @@
|
||||
87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportsplusbike.cpp; path = ../src/devices/sportsplusbike/sportsplusbike.cpp; sourceTree = "<group>"; };
|
||||
87097D2E275EA9A20020EE6F /* sportsplusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportsplusbike.h; path = ../src/devices/sportsplusbike/sportsplusbike.h; sourceTree = "<group>"; };
|
||||
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
|
||||
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymbike.cpp; sourceTree = "<group>"; };
|
||||
870A5DB42CEF8FD200839641 /* technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = technogymbike.cpp; path = ../src/devices/technogymbike/technogymbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8710706A29C48AE90094D0F3 /* handleurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = handleurl.h; path = ../src/handleurl.h; sourceTree = "<group>"; };
|
||||
8710706B29C48AEA0094D0F3 /* handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = handleurl.cpp; path = ../src/handleurl.cpp; sourceTree = "<group>"; };
|
||||
8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_handleurl.cpp; sourceTree = "<group>"; };
|
||||
@@ -914,6 +943,9 @@
|
||||
871235BD26B297660012D0F2 /* kingsmithr1protreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kingsmithr1protreadmill.h; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h; sourceTree = "<group>"; };
|
||||
871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = kingsmithr1protreadmill.cpp; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
|
||||
871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
|
||||
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_antbike.cpp; sourceTree = "<group>"; };
|
||||
8715A3E82C75E6DB009BAC05 /* antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = antbike.cpp; path = ../src/devices/antbike/antbike.cpp; sourceTree = "<group>"; };
|
||||
8715A3E92C75E6DB009BAC05 /* antbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = antbike.h; path = ../src/devices/antbike/antbike.h; sourceTree = "<group>"; };
|
||||
87182A07276BBAF600141463 /* virtualrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = virtualrower.h; path = ../src/virtualdevices/virtualrower.h; sourceTree = "<group>"; };
|
||||
87182A08276BBAF600141463 /* virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = virtualrower.cpp; path = ../src/virtualdevices/virtualrower.cpp; sourceTree = "<group>"; };
|
||||
87182A0A276BBB1200141463 /* moc_virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_virtualrower.cpp; sourceTree = "<group>"; };
|
||||
@@ -936,6 +968,42 @@
|
||||
871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = powerzonepack.cpp; path = ../src/powerzonepack.cpp; sourceTree = "<group>"; };
|
||||
871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_powerzonepack.cpp; sourceTree = "<group>"; };
|
||||
871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BLEPeripheralManager.swift; path = ../src/ios/BLEPeripheralManager.swift; sourceTree = "<group>"; };
|
||||
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mqttpublisher.cpp; sourceTree = "<group>"; };
|
||||
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttclient.cpp; sourceTree = "<group>"; };
|
||||
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttconnection_p.cpp; sourceTree = "<group>"; };
|
||||
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttmessage.cpp; sourceTree = "<group>"; };
|
||||
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttsubscription.cpp; sourceTree = "<group>"; };
|
||||
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttauthenticationproperties.h; path = ../src/mqtt/qmqttauthenticationproperties.h; sourceTree = SOURCE_ROOT; };
|
||||
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttauthenticationproperties.cpp; path = ../src/mqtt/qmqttauthenticationproperties.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088F22CE65451008C2C17 /* qmqttclient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient.h; path = ../src/mqtt/qmqttclient.h; sourceTree = SOURCE_ROOT; };
|
||||
872088F32CE65451008C2C17 /* qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttclient.cpp; path = ../src/mqtt/qmqttclient.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088F42CE65451008C2C17 /* qmqttclient_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient_p.h; path = ../src/mqtt/qmqttclient_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872088F52CE65451008C2C17 /* qmqttconnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnection.cpp; path = ../src/mqtt/qmqttconnection.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088F62CE65451008C2C17 /* qmqttconnection_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnection_p.h; path = ../src/mqtt/qmqttconnection_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties.h; path = ../src/mqtt/qmqttconnectionproperties.h; sourceTree = SOURCE_ROOT; };
|
||||
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnectionproperties.cpp; path = ../src/mqtt/qmqttconnectionproperties.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties_p.h; path = ../src/mqtt/qmqttconnectionproperties_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttcontrolpacket.cpp; path = ../src/mqtt/qmqttcontrolpacket.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttcontrolpacket_p.h; path = ../src/mqtt/qmqttcontrolpacket_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872088FC2CE65451008C2C17 /* qmqttglobal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttglobal.h; path = ../src/mqtt/qmqttglobal.h; sourceTree = SOURCE_ROOT; };
|
||||
872088FD2CE65451008C2C17 /* qmqttmessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage.h; path = ../src/mqtt/qmqttmessage.h; sourceTree = SOURCE_ROOT; };
|
||||
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttmessage.cpp; path = ../src/mqtt/qmqttmessage.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage_p.h; path = ../src/mqtt/qmqttmessage_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872089002CE65451008C2C17 /* qmqttpublishproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties.h; path = ../src/mqtt/qmqttpublishproperties.h; sourceTree = SOURCE_ROOT; };
|
||||
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttpublishproperties.cpp; path = ../src/mqtt/qmqttpublishproperties.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties_p.h; path = ../src/mqtt/qmqttpublishproperties_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872089032CE65451008C2C17 /* qmqttsubscription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription.h; path = ../src/mqtt/qmqttsubscription.h; sourceTree = SOURCE_ROOT; };
|
||||
872089042CE65451008C2C17 /* qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscription.cpp; path = ../src/mqtt/qmqttsubscription.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872089052CE65451008C2C17 /* qmqttsubscription_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription_p.h; path = ../src/mqtt/qmqttsubscription_p.h; sourceTree = SOURCE_ROOT; };
|
||||
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscriptionproperties.h; path = ../src/mqtt/qmqttsubscriptionproperties.h; sourceTree = SOURCE_ROOT; };
|
||||
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscriptionproperties.cpp; path = ../src/mqtt/qmqttsubscriptionproperties.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872089082CE65451008C2C17 /* qmqtttopicfilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicfilter.h; path = ../src/mqtt/qmqtttopicfilter.h; sourceTree = SOURCE_ROOT; };
|
||||
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicfilter.cpp; path = ../src/mqtt/qmqtttopicfilter.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8720890A2CE65451008C2C17 /* qmqtttopicname.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicname.h; path = ../src/mqtt/qmqtttopicname.h; sourceTree = SOURCE_ROOT; };
|
||||
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicname.cpp; path = ../src/mqtt/qmqtttopicname.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8720890C2CE65451008C2C17 /* qmqtttype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttype.h; path = ../src/mqtt/qmqtttype.h; sourceTree = SOURCE_ROOT; };
|
||||
8720890D2CE65451008C2C17 /* qmqtttype.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttype.cpp; path = ../src/mqtt/qmqtttype.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mqttpublisher.cpp; path = ../src/mqttpublisher.cpp; sourceTree = SOURCE_ROOT; };
|
||||
872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackelliptical.cpp; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.cpp; sourceTree = "<group>"; };
|
||||
872261ED289EA873006A6F75 /* nordictrackelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackelliptical.h; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.h; sourceTree = "<group>"; };
|
||||
872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackelliptical.cpp; sourceTree = "<group>"; };
|
||||
@@ -949,6 +1017,9 @@
|
||||
8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_QTelnet.cpp; sourceTree = "<group>"; };
|
||||
8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtelnetbike.cpp; sourceTree = "<group>"; };
|
||||
8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "qdomyoszwift-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
|
||||
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbelliptical.cpp; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
|
||||
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbelliptical.h; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h; sourceTree = "<group>"; };
|
||||
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/devices/faketreadmill/faketreadmill.h; sourceTree = "<group>"; };
|
||||
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/devices/faketreadmill/faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1062,6 +1133,9 @@
|
||||
873CD22A27EF8E4B000131BC /* iosinapppurchasetransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iosinapppurchasetransaction.h; path = ../src/purchasing/ios/iosinapppurchasetransaction.h; sourceTree = "<group>"; };
|
||||
873CD22E27EF8EC1000131BC /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
873D388A29B0D744006A2611 /* ConnectIQ.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ConnectIQ.xcframework; path = ../src/ConnectIQ/iOS/ConnectIQ.xcframework; sourceTree = "<group>"; };
|
||||
873D3C492C296B0100770CB9 /* moc_jumprope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_jumprope.cpp; sourceTree = "<group>"; };
|
||||
873D3C4B2C296B3700770CB9 /* jumprope.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = jumprope.cpp; path = ../src/devices/jumprope.cpp; sourceTree = "<group>"; };
|
||||
873D3C4C2C296B3700770CB9 /* jumprope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = jumprope.h; path = ../src/devices/jumprope.h; sourceTree = "<group>"; };
|
||||
873F022D274BE471002D0349 /* mcfbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mcfbike.cpp; path = ../src/devices/mcfbike/mcfbike.cpp; sourceTree = "<group>"; };
|
||||
873F022E274BE471002D0349 /* mcfbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = mcfbike.h; path = ../src/devices/mcfbike/mcfbike.h; sourceTree = "<group>"; };
|
||||
873F0230274BE47D002D0349 /* moc_mcfbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mcfbike.cpp; sourceTree = "<group>"; };
|
||||
@@ -1098,6 +1172,18 @@
|
||||
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/devices/proformbike/proformbike.h; sourceTree = "<group>"; };
|
||||
87586A4025B8340E00A243C4 /* proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformbike.cpp; path = ../src/devices/proformbike/proformbike.cpp; sourceTree = "<group>"; };
|
||||
87586A4225B8341B00A243C4 /* moc_proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformbike.cpp; sourceTree = "<group>"; };
|
||||
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kineticinroadbike.cpp; sourceTree = "<group>"; };
|
||||
875CA9472D0C742500667EE6 /* kineticinroadbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = kineticinroadbike.h; path = ../src/devices/kineticinroadbike/kineticinroadbike.h; sourceTree = SOURCE_ROOT; };
|
||||
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = kineticinroadbike.cpp; path = ../src/devices/kineticinroadbike/kineticinroadbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94B2D130F8100667EE6 /* moc_osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_osc.cpp; sourceTree = "<group>"; };
|
||||
875CA94D2D130FBC00667EE6 /* client.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = client.hpp; path = ../src/oscpp/client.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94E2D130FBC00667EE6 /* error.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = error.hpp; path = ../src/oscpp/error.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA94F2D130FBC00667EE6 /* osc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osc.h; path = ../src/osc.h; sourceTree = SOURCE_ROOT; };
|
||||
875CA9502D130FBC00667EE6 /* osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = osc.cpp; path = ../src/osc.cpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9512D130FBC00667EE6 /* print.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = print.hpp; path = ../src/oscpp/print.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9522D130FBC00667EE6 /* server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = server.hpp; path = ../src/oscpp/server.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9532D130FBC00667EE6 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = types.hpp; path = ../src/oscpp/types.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875CA9542D130FBC00667EE6 /* util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = util.hpp; path = ../src/oscpp/util.hpp; sourceTree = SOURCE_ROOT; };
|
||||
875F69B726342E8D0009FD78 /* spirittreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = spirittreadmill.h; path = ../src/devices/spirittreadmill/spirittreadmill.h; sourceTree = "<group>"; };
|
||||
875F69B826342E8D0009FD78 /* spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = spirittreadmill.cpp; path = ../src/devices/spirittreadmill/spirittreadmill.cpp; sourceTree = "<group>"; };
|
||||
875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_spirittreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1217,9 +1303,17 @@
|
||||
8772A0E425E43AD90080718C /* trxappgateusbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = trxappgateusbbike.h; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.h; sourceTree = "<group>"; };
|
||||
8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbbike.cpp; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = deerruntreadmill.h; path = ../src/devices/deeruntreadmill/deerruntreadmill.h; sourceTree = SOURCE_ROOT; };
|
||||
877350F52D1C08E50070CBD8 /* SmartControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SmartControl.h; path = ../src/devices/kineticinroadbike/SmartControl.h; sourceTree = SOURCE_ROOT; };
|
||||
877350F62D1C08E50070CBD8 /* SmartControl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SmartControl.cpp; path = ../src/devices/kineticinroadbike/SmartControl.cpp; sourceTree = SOURCE_ROOT; };
|
||||
8775008129E876F7008E48B7 /* iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iconceptelliptical.cpp; path = ../src/devices/iconceptelliptical/iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
8775008229E876F7008E48B7 /* iconceptelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iconceptelliptical.h; path = ../src/devices/iconceptelliptical/iconceptelliptical.h; sourceTree = "<group>"; };
|
||||
8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportstechelliptical.cpp; sourceTree = "<group>"; };
|
||||
877758B42C98629B00BB1697 /* sportstechelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportstechelliptical.h; path = ../src/devices/sportstechelliptical/sportstechelliptical.h; sourceTree = "<group>"; };
|
||||
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechelliptical.cpp; path = ../src/devices/sportstechelliptical/sportstechelliptical.cpp; sourceTree = "<group>"; };
|
||||
877A7606269D8E0F0024DD2C /* libqtwebview_darwin_debug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtwebview_darwin_debug.a; path = ../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.a; sourceTree = "<group>"; };
|
||||
877A7608269D8E9F0024DD2C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
|
||||
877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bowflextreadmill.cpp; path = ../src/devices/bowflextreadmill/bowflextreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1247,6 +1341,9 @@
|
||||
8785D5402B3DD0EC005A2EB7 /* PlayerStateWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlayerStateWrapper.h; path = "../src/zwift-api/PlayerStateWrapper.h"; sourceTree = "<group>"; };
|
||||
8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_PlayerStateWrapper.cpp; sourceTree = "<group>"; };
|
||||
8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_zwift_client_auth.cpp; sourceTree = "<group>"; };
|
||||
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusrower.cpp; sourceTree = "<group>"; };
|
||||
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sportsplusrower.cpp; sourceTree = "<group>"; };
|
||||
87873AF22D09AADF005F86B4 /* sportsplusrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportsplusrower.h; path = ../src/devices/sportsplusrower/sportsplusrower.h; sourceTree = SOURCE_ROOT; };
|
||||
8789DCDB6A4F681A76DF3F92 /* Qt5Widgets */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = Qt5Widgets; path = "/Users/cagnulein/Qt/5.15.2/ios/lib/libQt5Widgets$(QT_LIBRARY_SUFFIX).a"; sourceTree = "<absolute>"; };
|
||||
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/devices/yesoulbike/yesoulbike.cpp; sourceTree = "<group>"; };
|
||||
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/devices/yesoulbike/yesoulbike.h; sourceTree = "<group>"; };
|
||||
@@ -1295,6 +1392,7 @@
|
||||
87A0770E29B641D500A368BF /* wahookickrheadwind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wahookickrheadwind.h; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.h; sourceTree = "<group>"; };
|
||||
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wahookickrheadwind.cpp; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.cpp; sourceTree = "<group>"; };
|
||||
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wahookickrheadwind.cpp; sourceTree = "<group>"; };
|
||||
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier2ad9.h; path = ../src/characteristics/characteristicnotifier2ad9.h; sourceTree = "<group>"; };
|
||||
87A0C4B7262329A600121A76 /* npecablebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = npecablebike.cpp; path = ../src/devices/npecablebike/npecablebike.cpp; sourceTree = "<group>"; };
|
||||
87A0C4B8262329A600121A76 /* cscbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cscbike.h; path = ../src/devices/cscbike/cscbike.h; sourceTree = "<group>"; };
|
||||
87A0C4B9262329A600121A76 /* cscbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cscbike.cpp; path = ../src/devices/cscbike/cscbike.cpp; sourceTree = "<group>"; };
|
||||
@@ -1315,11 +1413,17 @@
|
||||
87A3BC212656429400D302E3 /* rower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rower.h; path = ../src/devices/rower.h; sourceTree = "<group>"; };
|
||||
87A3BC24265642A200D302E3 /* moc_rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_rower.cpp; sourceTree = "<group>"; };
|
||||
87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonrower.cpp; sourceTree = "<group>"; };
|
||||
87A3DD982D3413790060BAEB /* lifespantreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = lifespantreadmill.h; path = ../src/devices/lifespantreadmill/lifespantreadmill.h; sourceTree = SOURCE_ROOT; };
|
||||
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lifespantreadmill.cpp; path = ../src/devices/lifespantreadmill/lifespantreadmill.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_lifespantreadmill.cpp; sourceTree = "<group>"; };
|
||||
87A3EBB925D2CFED0040EB4C /* sportstechbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportstechbike.h; path = ../src/devices/sportstechbike/sportstechbike.h; sourceTree = "<group>"; };
|
||||
87A3EBBA25D2CFED0040EB4C /* sportstechbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechbike.cpp; path = ../src/devices/sportstechbike/sportstechbike.cpp; sourceTree = "<group>"; };
|
||||
87A4B75F25AF27CB0027EF3C /* metric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metric.cpp; path = ../src/metric.cpp; sourceTree = "<group>"; };
|
||||
87A4B76025AF27CB0027EF3C /* metric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metric.h; path = ../src/metric.h; sourceTree = "<group>"; };
|
||||
87A659DB8BE7DBAA7B395EF4 /* fit_monitoring_info_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_monitoring_info_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_monitoring_info_mesg_listener.hpp"; sourceTree = "<absolute>"; };
|
||||
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sramAXSController.cpp; sourceTree = "<group>"; };
|
||||
87A6825B2CE3AB4000586A2A /* sramAXSController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sramAXSController.h; path = ../src/devices/sramAXSController/sramAXSController.h; sourceTree = SOURCE_ROOT; };
|
||||
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sramAXSController.cpp; path = ../src/devices/sramAXSController/sramAXSController.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = technogymmyruntreadmill.cpp; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
87ADD2BA27634C1400B7A0AB /* technogymmyruntreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = technogymmyruntreadmill.h; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.h; sourceTree = "<group>"; };
|
||||
87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1336,6 +1440,8 @@
|
||||
87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitshowtreadmill.cpp; sourceTree = "<group>"; };
|
||||
87B617F025F260140094A1CB /* moc_snodebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_snodebike.cpp; sourceTree = "<group>"; };
|
||||
87B617F125F260150094A1CB /* moc_screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_screencapture.cpp; sourceTree = "<group>"; };
|
||||
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Zwift hub.pb.swift"; path = "../src/devices/zwifthubbike/Zwift hub.pb.swift"; sourceTree = SOURCE_ROOT; };
|
||||
87B871922CE1E94D009B06CA /* zwifthubbike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwifthubbike.swift; path = ../src/devices/zwifthubbike/zwifthubbike.swift; sourceTree = SOURCE_ROOT; };
|
||||
87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../src/ios/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_focustreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1467,6 +1573,9 @@
|
||||
87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_flywheelbike.cpp; sourceTree = "<group>"; };
|
||||
87E6A85925B5C8B900371D28 /* flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = flywheelbike.cpp; path = ../src/devices/flywheelbike/flywheelbike.cpp; sourceTree = "<group>"; };
|
||||
87E6A85A25B5C8B900371D28 /* flywheelbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = flywheelbike.h; path = ../src/devices/flywheelbike/flywheelbike.h; sourceTree = "<group>"; };
|
||||
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pitpatbike.cpp; sourceTree = "<group>"; };
|
||||
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pitpatbike.h; path = ../src/devices/pitpatbike/pitpatbike.h; sourceTree = SOURCE_ROOT; };
|
||||
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pitpatbike.cpp; path = ../src/devices/pitpatbike/pitpatbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87EB917427EE5FB3002535E1 /* nautilusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautilusbike.cpp; path = ../src/devices/nautilusbike/nautilusbike.cpp; sourceTree = "<group>"; };
|
||||
87EB917527EE5FB3002535E1 /* nautilusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautilusbike.h; path = ../src/devices/nautilusbike/nautilusbike.h; sourceTree = "<group>"; };
|
||||
87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inapptransaction.cpp; sourceTree = "<group>"; };
|
||||
@@ -1502,7 +1611,13 @@
|
||||
87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ultrasportbike.cpp; sourceTree = "<group>"; };
|
||||
87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
87FA94682B6B8A5A00B6AB9A /* libSystem.B.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libSystem.B.tbd; path = usr/lib/libSystem.B.tbd; sourceTree = SDKROOT; };
|
||||
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cycleopsphantombike.h; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.h; sourceTree = SOURCE_ROOT; };
|
||||
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = cycleopsphantombike.cpp; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_cycleopsphantombike.cpp; sourceTree = "<group>"; };
|
||||
87FD05DDC378E9BAD82A818F /* fit_ohr_settings_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_ohr_settings_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_ohr_settings_mesg_listener.hpp"; sourceTree = "<absolute>"; };
|
||||
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbrower.cpp; sourceTree = "<group>"; };
|
||||
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = trxappgateusbrower.h; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.h; sourceTree = SOURCE_ROOT; };
|
||||
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbrower.cpp; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tacxneo2.cpp; path = ../src/devices/tacxneo2/tacxneo2.cpp; sourceTree = "<group>"; };
|
||||
87FE5BAE2692F3130056EFC8 /* tacxneo2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tacxneo2.h; path = ../src/devices/tacxneo2/tacxneo2.h; sourceTree = "<group>"; };
|
||||
87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_tacxneo2.cpp; sourceTree = "<group>"; };
|
||||
@@ -2053,6 +2168,90 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87A3DD982D3413790060BAEB /* lifespantreadmill.h */,
|
||||
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */,
|
||||
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */,
|
||||
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */,
|
||||
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */,
|
||||
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */,
|
||||
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */,
|
||||
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */,
|
||||
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */,
|
||||
877350F52D1C08E50070CBD8 /* SmartControl.h */,
|
||||
877350F62D1C08E50070CBD8 /* SmartControl.cpp */,
|
||||
875CA94D2D130FBC00667EE6 /* client.hpp */,
|
||||
875CA94E2D130FBC00667EE6 /* error.hpp */,
|
||||
875CA94F2D130FBC00667EE6 /* osc.h */,
|
||||
875CA9502D130FBC00667EE6 /* osc.cpp */,
|
||||
875CA9512D130FBC00667EE6 /* print.hpp */,
|
||||
875CA9522D130FBC00667EE6 /* server.hpp */,
|
||||
875CA9532D130FBC00667EE6 /* types.hpp */,
|
||||
875CA9542D130FBC00667EE6 /* util.hpp */,
|
||||
875CA94B2D130F8100667EE6 /* moc_osc.cpp */,
|
||||
875CA9472D0C742500667EE6 /* kineticinroadbike.h */,
|
||||
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */,
|
||||
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */,
|
||||
87873AF22D09AADF005F86B4 /* sportsplusrower.h */,
|
||||
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */,
|
||||
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */,
|
||||
870A5DB42CEF8FD200839641 /* technogymbike.cpp */,
|
||||
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */,
|
||||
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */,
|
||||
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */,
|
||||
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */,
|
||||
872088F22CE65451008C2C17 /* qmqttclient.h */,
|
||||
872088F32CE65451008C2C17 /* qmqttclient.cpp */,
|
||||
872088F42CE65451008C2C17 /* qmqttclient_p.h */,
|
||||
872088F52CE65451008C2C17 /* qmqttconnection.cpp */,
|
||||
872088F62CE65451008C2C17 /* qmqttconnection_p.h */,
|
||||
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */,
|
||||
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */,
|
||||
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */,
|
||||
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */,
|
||||
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */,
|
||||
872088FC2CE65451008C2C17 /* qmqttglobal.h */,
|
||||
872088FD2CE65451008C2C17 /* qmqttmessage.h */,
|
||||
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */,
|
||||
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */,
|
||||
872089002CE65451008C2C17 /* qmqttpublishproperties.h */,
|
||||
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */,
|
||||
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */,
|
||||
872089032CE65451008C2C17 /* qmqttsubscription.h */,
|
||||
872089042CE65451008C2C17 /* qmqttsubscription.cpp */,
|
||||
872089052CE65451008C2C17 /* qmqttsubscription_p.h */,
|
||||
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */,
|
||||
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */,
|
||||
872089082CE65451008C2C17 /* qmqtttopicfilter.h */,
|
||||
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */,
|
||||
8720890A2CE65451008C2C17 /* qmqtttopicname.h */,
|
||||
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */,
|
||||
8720890C2CE65451008C2C17 /* qmqtttype.h */,
|
||||
8720890D2CE65451008C2C17 /* qmqtttype.cpp */,
|
||||
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */,
|
||||
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */,
|
||||
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */,
|
||||
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */,
|
||||
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */,
|
||||
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */,
|
||||
87A6825B2CE3AB4000586A2A /* sramAXSController.h */,
|
||||
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */,
|
||||
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */,
|
||||
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
|
||||
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
|
||||
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
|
||||
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */,
|
||||
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */,
|
||||
877758B42C98629B00BB1697 /* sportstechelliptical.h */,
|
||||
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */,
|
||||
8715A3E82C75E6DB009BAC05 /* antbike.cpp */,
|
||||
8715A3E92C75E6DB009BAC05 /* antbike.h */,
|
||||
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */,
|
||||
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */,
|
||||
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */,
|
||||
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */,
|
||||
873D3C4B2C296B3700770CB9 /* jumprope.cpp */,
|
||||
873D3C4C2C296B3700770CB9 /* jumprope.h */,
|
||||
873D3C492C296B0100770CB9 /* moc_jumprope.cpp */,
|
||||
87C4E5BA2C1C1D0900D0750E /* crossrope.cpp */,
|
||||
87C4E5B92C1C1D0900D0750E /* crossrope.h */,
|
||||
874823FF2B935AE6006F3CA1 /* ergtable.h */,
|
||||
@@ -2452,6 +2651,10 @@
|
||||
8710707229C4A5E70094D0F3 /* GarminConnect.swift */,
|
||||
87A2E0202B2B024200E6168F /* swiftDebug.h */,
|
||||
8730A3912B404159007E336D /* zwift_protobuf_layer.swift */,
|
||||
87B871922CE1E94D009B06CA /* zwifthubbike.swift */,
|
||||
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
|
||||
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
|
||||
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -3053,7 +3256,6 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */,
|
||||
876E4E312594748100BD5714 /* PBXTargetDependency */,
|
||||
);
|
||||
name = qdomyoszwift;
|
||||
@@ -3123,9 +3325,6 @@
|
||||
DevelopmentTeam = 6335M7T29D;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
E9F0AFC024A6F2D65CE84E08 = {
|
||||
DevelopmentTeam = 6335M7T29D;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */;
|
||||
@@ -3145,7 +3344,6 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
|
||||
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */,
|
||||
876E4E102594747F00BD5714 /* watchkit */,
|
||||
876E4E192594748000BD5714 /* watchkit Extension */,
|
||||
);
|
||||
@@ -3196,31 +3394,6 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
5E618435888B9D49F8540165 /* Qt Qmake */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
name = "Qt Qmake";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_makeqmake.mak";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
name = "Qt Preprocessors";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_preprocess.mak";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
876E4E162594748000BD5714 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@@ -3242,6 +3415,7 @@
|
||||
files = (
|
||||
8738249627E646E3004F1B46 /* characteristicnotifier2acd.cpp in Compile Sources */,
|
||||
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
|
||||
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */,
|
||||
87C5F0BB26285E5F0067A1B5 /* mimetext.cpp in Compile Sources */,
|
||||
8732C17F27353464006DF424 /* iconceptbike.cpp in Compile Sources */,
|
||||
87CF5169293C879800A7CABC /* characteristicwriteprocessore005.cpp in Compile Sources */,
|
||||
@@ -3268,6 +3442,7 @@
|
||||
873824BC27E64707004F1B46 /* moc_resolver.cpp in Compile Sources */,
|
||||
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */,
|
||||
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */,
|
||||
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */,
|
||||
20A50533946A39CBD2C89104 /* bluetoothdevice.cpp in Compile Sources */,
|
||||
87C5F0D126285E7E0067A1B5 /* moc_stagesbike.cpp in Compile Sources */,
|
||||
873824E927E647A8004F1B46 /* mdns.cpp in Compile Sources */,
|
||||
@@ -3317,6 +3492,7 @@
|
||||
8768C8C92BBC11C80099DBE1 /* adb_client.c in Compile Sources */,
|
||||
873063C0259DF2C500DA0F44 /* moc_heartratebelt.cpp in Compile Sources */,
|
||||
DD5ED224478CB82859C61B9F /* fit_buffered_record_mesg_broadcaster.cpp in Compile Sources */,
|
||||
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */,
|
||||
87368825259C602800C71C7E /* watchAppStart.swift in Compile Sources */,
|
||||
87BCE6BF29F28F95001F70EB /* moc_ypooelliptical.cpp in Compile Sources */,
|
||||
876ED21925C3E9000065F3DC /* moc_ftmsbike.cpp in Compile Sources */,
|
||||
@@ -3329,6 +3505,7 @@
|
||||
E7F190E59DC975BA4CA65F0C /* fit_crc.cpp in Compile Sources */,
|
||||
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */,
|
||||
DA1DC0B761BD7A3004BCF43D /* fit_date_time.cpp in Compile Sources */,
|
||||
873D3C4D2C296B3800770CB9 /* jumprope.cpp in Compile Sources */,
|
||||
87E1CA7D2B8DF04900E3C414 /* moc_trxappgateusbelliptical.cpp in Compile Sources */,
|
||||
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */,
|
||||
87DAE16526E9FF3A00B0527E /* solef80treadmill.cpp in Compile Sources */,
|
||||
@@ -3338,6 +3515,7 @@
|
||||
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */,
|
||||
8730A3922B404159007E336D /* zwift_protobuf_layer.swift in Compile Sources */,
|
||||
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */,
|
||||
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */,
|
||||
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
|
||||
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
|
||||
DF1FD9718B75FA591A7E3D80 /* fit_decode.cpp in Compile Sources */,
|
||||
@@ -3370,11 +3548,13 @@
|
||||
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */,
|
||||
A4BD6DF51CFFF867B7B5AED4 /* fit_developer_field_definition.cpp in Compile Sources */,
|
||||
87EB918B27EE5FE7002535E1 /* moc_inappproductqmltype.cpp in Compile Sources */,
|
||||
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */,
|
||||
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */,
|
||||
3015F9B9FF4CA6D653D46CCA /* fit_developer_field_description.cpp in Compile Sources */,
|
||||
87310B22266FBB78008BA0D6 /* moc_homefitnessbuddy.cpp in Compile Sources */,
|
||||
87958F1B27628D5400124B24 /* moc_elitesterzosmart.cpp in Compile Sources */,
|
||||
8768C8D82BBC12890099DBE1 /* centraldir.c in Compile Sources */,
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */,
|
||||
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
|
||||
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
|
||||
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
|
||||
@@ -3395,9 +3575,11 @@
|
||||
87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */,
|
||||
87F4FB5C29D550E00061BB4A /* moc_schwinn170bike.cpp in Compile Sources */,
|
||||
87C5F0C226285E5F0067A1B5 /* emailaddress.cpp in Compile Sources */,
|
||||
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */,
|
||||
873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */,
|
||||
25F2400F80DAFBD41FE5CC75 /* fit_field.cpp in Compile Sources */,
|
||||
873824E227E647A8004F1B46 /* dns.cpp in Compile Sources */,
|
||||
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */,
|
||||
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */,
|
||||
8768C9092BBC12B80099DBE1 /* socket_local_server.c in Compile Sources */,
|
||||
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */,
|
||||
@@ -3416,16 +3598,33 @@
|
||||
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */,
|
||||
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */,
|
||||
87C424262BC1294000503687 /* moc_treadmillErgTable.cpp in Compile Sources */,
|
||||
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */,
|
||||
873824ED27E647A9004F1B46 /* resolver.cpp in Compile Sources */,
|
||||
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */,
|
||||
878531652711A3E1004B153D /* activiotreadmill.cpp in Compile Sources */,
|
||||
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */,
|
||||
48BA9CE9D6F256A15E8FB25D /* fit_mesg.cpp in Compile Sources */,
|
||||
DD2E0091F3318F053D2995AA /* fit_mesg_broadcaster.cpp in Compile Sources */,
|
||||
FE77C778768741F1A161682E /* fit_mesg_definition.cpp in Compile Sources */,
|
||||
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */,
|
||||
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */,
|
||||
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */,
|
||||
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */,
|
||||
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */,
|
||||
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */,
|
||||
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */,
|
||||
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */,
|
||||
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */,
|
||||
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */,
|
||||
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */,
|
||||
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */,
|
||||
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */,
|
||||
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */,
|
||||
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */,
|
||||
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */,
|
||||
87BCE6BD29F28F72001F70EB /* ypooelliptical.cpp in Compile Sources */,
|
||||
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
|
||||
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */,
|
||||
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */,
|
||||
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */,
|
||||
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
|
||||
@@ -3433,6 +3632,7 @@
|
||||
6DC5D7C695B8763F9E2E029F /* fit_profile.cpp in Compile Sources */,
|
||||
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */,
|
||||
87C5F0B726285E5F0067A1B5 /* mimecontentformatter.cpp in Compile Sources */,
|
||||
873D3C4A2C296B0100770CB9 /* moc_jumprope.cpp in Compile Sources */,
|
||||
23191C28CB29474279752FD3 /* fit_protocol_validator.cpp in Compile Sources */,
|
||||
275D55B5D956B2E5F1B7E46E /* fit_unicode.cpp in Compile Sources */,
|
||||
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */,
|
||||
@@ -3451,6 +3651,7 @@
|
||||
7CF08714869DA569C2EA551C /* keepawakehelper.cpp in Compile Sources */,
|
||||
87EB918727EE5FE7002535E1 /* moc_nautilusbike.cpp in Compile Sources */,
|
||||
873824B827E64707004F1B46 /* moc_cache.cpp in Compile Sources */,
|
||||
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */,
|
||||
873824E427E647A8004F1B46 /* cache.cpp in Compile Sources */,
|
||||
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */,
|
||||
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */,
|
||||
@@ -3484,6 +3685,7 @@
|
||||
87C5F0C026285E5F0067A1B5 /* mimepart.cpp in Compile Sources */,
|
||||
47E45EE0BB22C1E4332F1D1D /* trxappgateusbtreadmill.cpp in Compile Sources */,
|
||||
873824BB27E64707004F1B46 /* moc_prober_p.cpp in Compile Sources */,
|
||||
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */,
|
||||
8742C2B227C92C30007D3FA0 /* wahookickrsnapbike.cpp in Compile Sources */,
|
||||
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */,
|
||||
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */,
|
||||
@@ -3502,6 +3704,7 @@
|
||||
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */,
|
||||
8742C2B427C92C48007D3FA0 /* moc_wahookickrsnapbike.cpp in Compile Sources */,
|
||||
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */,
|
||||
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */,
|
||||
873824B027E64706004F1B46 /* moc_cache_p.cpp in Compile Sources */,
|
||||
87EB918627EE5FE7002535E1 /* moc_inapppurchasebackend.cpp in Compile Sources */,
|
||||
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */,
|
||||
@@ -3512,6 +3715,12 @@
|
||||
87A0C4BB262329A600121A76 /* npecablebike.cpp in Compile Sources */,
|
||||
873CD22D27EF8E4B000131BC /* iosinapppurchaseproduct.mm in Compile Sources */,
|
||||
873CD20727EF8D8A000131BC /* inappproduct.cpp in Compile Sources */,
|
||||
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */,
|
||||
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */,
|
||||
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */,
|
||||
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */,
|
||||
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */,
|
||||
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */,
|
||||
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */,
|
||||
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */,
|
||||
872DCC392A18D4A800EC9F68 /* virtualdevice.cpp in Compile Sources */,
|
||||
@@ -3536,6 +3745,7 @@
|
||||
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
|
||||
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
|
||||
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
|
||||
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */,
|
||||
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
|
||||
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
|
||||
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
|
||||
@@ -3548,6 +3758,7 @@
|
||||
8783153C25E8DAFD0007817C /* sportstechbike.cpp in Compile Sources */,
|
||||
873824E527E647A8004F1B46 /* message.cpp in Compile Sources */,
|
||||
210F6A0A7E2FA7CDD3CA0084 /* qdomyoszwift_qml_plugin_import.cpp in Compile Sources */,
|
||||
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */,
|
||||
87062644259480A600D06586 /* APIFetcher.swift in Compile Sources */,
|
||||
8768C8C72BBC11C80099DBE1 /* adb_auth_host.c in Compile Sources */,
|
||||
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */,
|
||||
@@ -3565,6 +3776,7 @@
|
||||
8768C9062BBC12B80099DBE1 /* socket_local_client.c in Compile Sources */,
|
||||
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */,
|
||||
C3D1FD2587BF6F15B58BA675 /* moc_bluetooth.cpp in Compile Sources */,
|
||||
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */,
|
||||
87062648259480B700D06586 /* WorkoutTracking.swift in Compile Sources */,
|
||||
8C3422A825EF7ECD78951307 /* moc_bluetoothdevice.cpp in Compile Sources */,
|
||||
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */,
|
||||
@@ -3593,9 +3805,11 @@
|
||||
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
|
||||
87C5F0D726285E7E0067A1B5 /* moc_mimefile.cpp in Compile Sources */,
|
||||
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */,
|
||||
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */,
|
||||
8762D5102601F7EA00F6F049 /* M3iNSQT.cpp in Compile Sources */,
|
||||
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */,
|
||||
8718CBA3263063BD004BF4EE /* templateinfosender.cpp in Compile Sources */,
|
||||
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */,
|
||||
E8B499F921FB0AB55C7A8A8B /* moc_gpx.cpp in Compile Sources */,
|
||||
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */,
|
||||
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */,
|
||||
@@ -3608,6 +3822,7 @@
|
||||
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */,
|
||||
873824B227E64706004F1B46 /* moc_hostname.cpp in Compile Sources */,
|
||||
873824EB27E647A8004F1B46 /* prober.cpp in Compile Sources */,
|
||||
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */,
|
||||
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */,
|
||||
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */,
|
||||
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */,
|
||||
@@ -3620,10 +3835,13 @@
|
||||
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */,
|
||||
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */,
|
||||
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
|
||||
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */,
|
||||
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
|
||||
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */,
|
||||
8738248127E646C1004F1B46 /* characteristicnotifier2ad2.cpp in Compile Sources */,
|
||||
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */,
|
||||
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
|
||||
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */,
|
||||
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
|
||||
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
|
||||
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
|
||||
@@ -3662,6 +3880,8 @@
|
||||
87EB918827EE5FE7002535E1 /* moc_inappstoreqmltype.cpp in Compile Sources */,
|
||||
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */,
|
||||
87C5F0B826285E5F0067A1B5 /* stagesbike.cpp in Compile Sources */,
|
||||
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */,
|
||||
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */,
|
||||
87C5F0D526285E7E0067A1B5 /* moc_mimehtml.cpp in Compile Sources */,
|
||||
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */,
|
||||
87A0C4BF262329B500121A76 /* moc_cscbike.cpp in Compile Sources */,
|
||||
@@ -3669,6 +3889,8 @@
|
||||
8790FDE1277B0AC600247550 /* moc_nautilustreadmill.cpp in Compile Sources */,
|
||||
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */,
|
||||
8783153B25E8D81E0007817C /* moc_sportstechbike.cpp in Compile Sources */,
|
||||
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */,
|
||||
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */,
|
||||
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */,
|
||||
8768C8BF2BBC11C80099DBE1 /* transport.c in Compile Sources */,
|
||||
);
|
||||
@@ -3688,11 +3910,6 @@
|
||||
target = 876E4E102594747F00BD5714 /* watchkit */;
|
||||
targetProxy = 876E4E302594748100BD5714 /* PBXContainerItemProxy */;
|
||||
};
|
||||
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */;
|
||||
targetProxy = 876E4E0E2594739400BD5714 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -4016,7 +4233,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4049,6 +4266,9 @@
|
||||
../../Qt/5.15.2/ios/include/QtMultimedia,
|
||||
../src/devices,
|
||||
../src/ios/adb/include,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
|
||||
@@ -4095,7 +4315,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
"-g",
|
||||
@@ -4207,7 +4427,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4242,6 +4462,9 @@
|
||||
../../Qt/5.15.2/ios/include/QtMultimedia,
|
||||
../src/devices,
|
||||
../src/ios/adb/include,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
|
||||
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
|
||||
@@ -4288,7 +4511,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
@@ -4434,7 +4657,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4459,7 +4682,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4530,7 +4753,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4551,7 +4774,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4622,7 +4845,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4667,7 +4890,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4736,7 +4959,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 804;
|
||||
CURRENT_PROJECT_VERSION = 1007;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4777,7 +5000,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
|
||||
96
docker/linux_gui_vnc/Dockerfile
Normal file
96
docker/linux_gui_vnc/Dockerfile
Normal file
@@ -0,0 +1,96 @@
|
||||
# Define build image
|
||||
FROM ubuntu:latest AS build
|
||||
|
||||
# Install essential build dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
git \
|
||||
ca-certificates \
|
||||
qtquickcontrols2-5-dev \
|
||||
qtconnectivity5-dev \
|
||||
qtbase5-private-dev \
|
||||
qtpositioning5-dev \
|
||||
libqt5charts5-dev \
|
||||
libqt5networkauth5-dev \
|
||||
libqt5websockets5-dev \
|
||||
qml-module* \
|
||||
libqt5texttospeech5-dev \
|
||||
qtlocation5-dev \
|
||||
qtmultimedia5-dev \
|
||||
g++ \
|
||||
make \
|
||||
wget \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Define runtime image
|
||||
FROM ubuntu:latest AS runtime
|
||||
|
||||
# Install essential runtime dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
libqt5bluetooth5 \
|
||||
libqt5widgets5 \
|
||||
libqt5positioning5 \
|
||||
libqt5xml5 \
|
||||
libqt5charts5 \
|
||||
qt5-assistant \
|
||||
libqt5networkauth5 \
|
||||
libqt5websockets5 \
|
||||
qml-module* \
|
||||
libqt5texttospeech5 \
|
||||
libqt5location5-plugins \
|
||||
libqt5multimediawidgets5 \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5multimedia5 \
|
||||
qml-module-qtquick-controls2 \
|
||||
libqt5location5 \
|
||||
bluez \
|
||||
dbus \
|
||||
tigervnc-standalone-server \
|
||||
tigervnc-tools \
|
||||
libgl1-mesa-dri \
|
||||
xfonts-base \
|
||||
x11-xserver-utils \
|
||||
tigervnc-common \
|
||||
net-tools \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Stage 1: Build
|
||||
FROM build AS builder
|
||||
|
||||
# Clone the project and build it
|
||||
WORKDIR /usr/local/src
|
||||
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
|
||||
WORKDIR /usr/local/src/qdomyos-zwift
|
||||
RUN git submodule update --init src/smtpclient/ \
|
||||
&& git submodule update --init src/qmdnsengine/ \
|
||||
&& git submodule update --init tst/googletest/
|
||||
WORKDIR /usr/local/src/qdomyos-zwift/src
|
||||
RUN qmake qdomyos-zwift.pro \
|
||||
&& make -j4
|
||||
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM runtime
|
||||
|
||||
# Copy the built binary to /usr/local/bin
|
||||
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
|
||||
|
||||
# VNC configuration
|
||||
RUN mkdir -p ~/.vnc && \
|
||||
echo "securepassword" | vncpasswd -f > ~/.vnc/passwd && \
|
||||
chmod 600 ~/.vnc/passwd
|
||||
|
||||
# .Xauthority configuration
|
||||
RUN touch /root/.Xauthority
|
||||
ENV DISPLAY=:99
|
||||
|
||||
# Start VNC server with authentication
|
||||
CMD vncserver :99 -depth 24 -localhost no -xstartup qdomyos-zwift && \
|
||||
sleep infinity
|
||||
|
||||
2
docker/linux_gui_vnc/build.sh
Executable file
2
docker/linux_gui_vnc/build.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker build -t qdomyos-zwift-vnc .
|
||||
10
docker/linux_gui_vnc/docker-compose.yml
Normal file
10
docker/linux_gui_vnc/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
qdomyos-zwift-vnc:
|
||||
image: qdomyos-zwift-vnc
|
||||
container_name: qdomyos-zwift-vnc
|
||||
privileged: true # Required for Bluetooth functionality
|
||||
network_mode: "host" # Used to access host Bluetooth and D-Bus
|
||||
volumes:
|
||||
- /dev:/dev # Forward host devices (for Bluetooth)
|
||||
- /run/dbus:/run/dbus # Forward D-Bus for Bluetooth interaction
|
||||
restart: "no" # Do not restart the container automatically
|
||||
95
docker/linux_webgl/Dockerfile
Normal file
95
docker/linux_webgl/Dockerfile
Normal file
@@ -0,0 +1,95 @@
|
||||
# Define build image
|
||||
FROM ubuntu:latest AS build
|
||||
|
||||
# Install essential build dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
git \
|
||||
ca-certificates \
|
||||
qtquickcontrols2-5-dev \
|
||||
qtconnectivity5-dev \
|
||||
qtbase5-private-dev \
|
||||
qtpositioning5-dev \
|
||||
libqt5charts5-dev \
|
||||
libqt5networkauth5-dev \
|
||||
libqt5websockets5-dev \
|
||||
qml-module* \
|
||||
libqt5texttospeech5-dev \
|
||||
qtlocation5-dev \
|
||||
qtmultimedia5-dev \
|
||||
g++ \
|
||||
make \
|
||||
wget \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Define runtime image
|
||||
FROM ubuntu:latest AS runtime
|
||||
|
||||
# Install essential runtime dependencies
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && apt upgrade -y \
|
||||
&& apt install --no-install-recommends -y \
|
||||
libqt5bluetooth5 \
|
||||
libqt5widgets5 \
|
||||
libqt5positioning5 \
|
||||
libqt5xml5 \
|
||||
libqt5charts5 \
|
||||
qt5-assistant \
|
||||
libqt5networkauth5 \
|
||||
libqt5websockets5 \
|
||||
qml-module* \
|
||||
libqt5texttospeech5 \
|
||||
libqt5location5-plugins \
|
||||
libqt5multimediawidgets5 \
|
||||
libqt5multimedia5-plugins \
|
||||
libqt5multimedia5 \
|
||||
qml-module-qtquick-controls2 \
|
||||
libqt5location5 \
|
||||
bluez \
|
||||
dbus \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# Stage 1: Build
|
||||
FROM build AS builder
|
||||
|
||||
# Define variables for Qt versions
|
||||
ARG QT_VERSION=5.15
|
||||
ARG QT_SUBVERSION=5.15.13
|
||||
ARG QT_WEBPLUGIN_NAME=qtwebglplugin-everywhere-opensource-src
|
||||
|
||||
# Build WebGL plugin
|
||||
WORKDIR /usr/local/src
|
||||
RUN wget https://download.qt.io/official_releases/qt/${QT_VERSION}/${QT_SUBVERSION}/submodules/${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
|
||||
&& unzip ${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
|
||||
&& mv *-${QT_SUBVERSION} qtwebglplugin-everywhere \
|
||||
&& cd qtwebglplugin-everywhere \
|
||||
&& qmake \
|
||||
&& make
|
||||
|
||||
# Clone the project and build it
|
||||
WORKDIR /usr/local/src
|
||||
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
|
||||
WORKDIR /usr/local/src/qdomyos-zwift
|
||||
RUN git submodule update --init src/smtpclient/ \
|
||||
&& git submodule update --init src/qmdnsengine/ \
|
||||
&& git submodule update --init tst/googletest/
|
||||
WORKDIR /usr/local/src/qdomyos-zwift/src
|
||||
RUN qmake qdomyos-zwift.pro \
|
||||
&& make -j4
|
||||
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM runtime
|
||||
|
||||
# Copy the built binary to /usr/local/bin
|
||||
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
|
||||
|
||||
# Copy WebGL plugin to the appropriate location
|
||||
COPY --from=builder /usr/local/src/qtwebglplugin-everywhere/plugins/platforms/libqwebgl.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqwebgl.so
|
||||
|
||||
# Set the default command to run the application with WebGL
|
||||
CMD ["qdomyos-zwift", "-qml", "-platform", "webgl:port=8080"]
|
||||
2
docker/linux_webgl/build.sh
Executable file
2
docker/linux_webgl/build.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
docker build -t qdomyos-zwift-webgl .
|
||||
19
docker/linux_webgl/docker-compose.yml
Normal file
19
docker/linux_webgl/docker-compose.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
qdomyos-zwift-webgl:
|
||||
image: qdomyos-zwift-webgl
|
||||
container_name: qdomyos-zwift-webgl
|
||||
privileged: true
|
||||
network_mode: "host"
|
||||
environment:
|
||||
- DISPLAY=${DISPLAY}
|
||||
volumes:
|
||||
- /dev:/dev
|
||||
- /run/dbus:/run/dbus
|
||||
- ./.config:/root/.config
|
||||
- /tmp/.X11-unix:/tmp/.X11-unix
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: "no"
|
||||
# command: qdomyos-zwift -qml -platform webgl:port=8080
|
||||
# command: ["qdomyos-zwift", "-no-gui"]
|
||||
|
||||
@@ -10,7 +10,7 @@ These instructions build the app itself, not the test project.
|
||||
|
||||
```buildoutcfg
|
||||
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
|
||||
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
|
||||
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
|
||||
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
$ cd qdomyos-zwift
|
||||
$ git submodule update --init src/smtpclient/
|
||||
@@ -106,7 +106,7 @@ This operation takes a moment to complete.
|
||||
#### Install qdomyos-zwift from sources
|
||||
|
||||
```bash
|
||||
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
|
||||
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
|
||||
git clone https://github.com/cagnulein/qdomyos-zwift.git
|
||||
cd qdomyos-zwift
|
||||
git submodule update --init src/smtpclient/
|
||||
@@ -117,6 +117,11 @@ qmake qdomyos-zwift.pro
|
||||
make
|
||||
```
|
||||
|
||||
If you need GUI also do a
|
||||
```
|
||||
apt install qml-module*
|
||||
```
|
||||
|
||||
Please note :
|
||||
- Don't build the application with `-j4` option (this will fail)
|
||||
- Build operation is circa 45 minutes (subsequent builds are faster)
|
||||
@@ -172,6 +177,124 @@ If everything is working as expected, **enable your service at boot time** :
|
||||
|
||||
Then reboot to check operations (`sudo reboot`)
|
||||
|
||||
### (optional) Treadmill Auto-Detection and Service Management
|
||||
This section provides a reliable way to manage the QZ service based on the treadmill's power state. Using a `bluetoothctl`-based Bash script, this solution ensures the QZ service starts when the treadmill is detected and stops when it is not.
|
||||
|
||||
- **Bluetooth Discovery**: Monitors treadmill availability via `bluetoothctl`.
|
||||
- **Service Control**: Automatically starts and stops the QZ service.
|
||||
- **Logging**: Tracks treadmill status and actions in a log file.
|
||||
|
||||
**Notes:**
|
||||
- Ensure `bluetoothctl` is installed and working on your system.
|
||||
- Replace `I_TL` in the script with your treadmill's Bluetooth name. You can find your device name via `bluetoothctl scan on`
|
||||
- Adjust the sleep interval (`sleep 30`) in the script as needed for your use case.
|
||||
|
||||
Step 1: Save the following script as `/root/qz-treadmill-monitor.sh`:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
LOG_FILE="/var/log/qz-treadmill-monitor.log"
|
||||
TARGET_DEVICE="I_TL"
|
||||
SCAN_INTERVAL=30 # Time in seconds between checks
|
||||
SERVICE_NAME="qz"
|
||||
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
is_service_running() {
|
||||
systemctl is-active --quiet "$SERVICE_NAME"
|
||||
return $?
|
||||
}
|
||||
|
||||
scan_for_device() {
|
||||
log "Starting Bluetooth scan for $TARGET_DEVICE..."
|
||||
|
||||
# Run bluetoothctl scan in the background and capture output
|
||||
bluetoothctl scan on &>/dev/null &
|
||||
SCAN_PID=$!
|
||||
|
||||
# Allow some time for devices to appear
|
||||
sleep 5
|
||||
|
||||
# Check if the target device appears in the list
|
||||
bluetoothctl devices | grep -q "$TARGET_DEVICE"
|
||||
DEVICE_FOUND=$?
|
||||
|
||||
# Stop scanning
|
||||
kill "$SCAN_PID"
|
||||
bluetoothctl scan off &>/dev/null
|
||||
|
||||
if [ $DEVICE_FOUND -eq 0 ]; then
|
||||
log "Device '$TARGET_DEVICE' found."
|
||||
return 0
|
||||
else
|
||||
log "Device '$TARGET_DEVICE' not found."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
manage_service() {
|
||||
local device_found=$1
|
||||
if $device_found; then
|
||||
if ! is_service_running; then
|
||||
log "Starting QZ service..."
|
||||
systemctl start "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already running."
|
||||
fi
|
||||
else
|
||||
if is_service_running; then
|
||||
log "Stopping QZ service..."
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
else
|
||||
log "QZ service is already stopped."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
log "Checking for treadmill status..."
|
||||
if scan_for_device; then
|
||||
manage_service true
|
||||
else
|
||||
manage_service false
|
||||
fi
|
||||
log "Waiting for $SCAN_INTERVAL seconds before next check..."
|
||||
sleep "$SCAN_INTERVAL"
|
||||
done
|
||||
```
|
||||
|
||||
Step2: To ensure the script runs continuously, create a systemd service file at `/etc/systemd/system/qz-treadmill-monitor.service`
|
||||
```bash
|
||||
[Unit]
|
||||
Description=QZ Treadmill Monitor Service
|
||||
After=bluetooth.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/root/qz-treadmill-monitor.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
User=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Step 3: Enable and Start the Service
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable qz-treadmill-monitor
|
||||
sudo systemctl start qz-treadmill-monitor
|
||||
```
|
||||
|
||||
Monitor logs are written to `/var/log/qz-treadmill-monitor.log`. Use the following command to check logs in real-time:
|
||||
```bash
|
||||
sudo tail -f /var/log/qz-treadmill-monitor.log
|
||||
```
|
||||
|
||||
|
||||
|
||||
### (optional) Enable overlay FS
|
||||
|
||||
|
||||
@@ -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
143
helpers/winbt.py
Normal 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))
|
||||
37
qdomyos-zwift.code-workspace
Normal file
37
qdomyos-zwift.code-workspace
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.associations": {
|
||||
"list": "cpp",
|
||||
"chrono": "cpp",
|
||||
"complex": "cpp",
|
||||
"functional": "cpp",
|
||||
"optional": "cpp",
|
||||
"system_error": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"qhttpserver": "cpp",
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"map": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"xstring": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"xutility": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"bitset": "cpp",
|
||||
"iterator": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xtree": "cpp",
|
||||
"ostream": "cpp",
|
||||
"locale": "cpp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,59 +10,87 @@ ColumnLayout {
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
default property alias accordionContent: contentLoader.sourceComponent
|
||||
|
||||
Layout.fillWidth: true;
|
||||
// Signal emitted when content becomes visible
|
||||
signal contentBecameVisible()
|
||||
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
Layout.fillWidth: true
|
||||
height: 48
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
Rectangle {
|
||||
id: indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x:34;y:13
|
||||
x: 34; y: 13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
|
||||
Image {
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
y: 13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
if(rootElement.isOpen) {
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
}else{
|
||||
} else {
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
// Loader with enhanced visibility handling
|
||||
Loader {
|
||||
id: contentLoader
|
||||
active: rootElement.isOpen
|
||||
visible: false // Start invisible
|
||||
Layout.fillWidth: true
|
||||
asynchronous: false
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
item.Layout.fillWidth = true
|
||||
visible = true
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle visibility changes
|
||||
onVisibleChanged: {
|
||||
if (visible && status === Loader.Ready) {
|
||||
rootElement.contentBecameVisible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle accordion closing
|
||||
onIsOpenChanged: {
|
||||
if (!isOpen) {
|
||||
contentLoader.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
115
src/EventHandler.h
Normal file
115
src/EventHandler.h
Normal 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
|
||||
568
src/Home.qml
568
src/Home.qml
@@ -7,7 +7,7 @@ import Qt.labs.settings 1.0
|
||||
import Qt.labs.platform 1.1
|
||||
import QtMultimedia 5.15
|
||||
|
||||
HomeForm{
|
||||
HomeForm {
|
||||
objectName: "home"
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
@@ -33,6 +33,7 @@ HomeForm{
|
||||
property bool theme_tile_shadow_enabled: true
|
||||
property string theme_tile_shadow_color: "#9C27B0"
|
||||
property int theme_tile_secondline_textsize: 12
|
||||
property bool skipLocationServicesDialog: false
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
@@ -86,6 +87,49 @@ HomeForm{
|
||||
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
|
||||
}
|
||||
|
||||
property bool locationServiceRequsted: false
|
||||
|
||||
MessageDialog {
|
||||
id: locationServicesDialog
|
||||
text: "Permissions Required"
|
||||
informativeText: "QZ requires both Bluetooth and Location Services to be enabled.\nLocation Services are necessary on Android to allow the app to find Bluetooth devices.\nThe GPS will not be used.\n\nWould you like to enable them?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {
|
||||
locationServiceRequsted = true
|
||||
rootItem.enableLocationServices()
|
||||
}
|
||||
onNoClicked: remindLocationServicesDialog.visible = true
|
||||
visible: !rootItem.locationServices() && !locationServiceRequsted && !settings.skipLocationServicesDialog
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: remindLocationServicesDialog
|
||||
text: "Reminder Preference"
|
||||
informativeText: "Would you like to be reminded about enabling Location Services next time?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: settings.skipLocationServicesDialog = false
|
||||
onNoClicked: settings.skipLocationServicesDialog = true
|
||||
visible: false
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
text: "Restart the app"
|
||||
informativeText: "To apply the changes, you need to restart the app.\nWould you like to do that now?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: Qt.callLater(Qt.quit)
|
||||
onNoClicked: this.visible = false;
|
||||
visible: locationServiceRequsted
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 200; running: true; repeat: false
|
||||
onTriggered: {
|
||||
if(rootItem.firstRun()) {
|
||||
stackView.push("Wizard.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inner_stop() {
|
||||
stop_clicked();
|
||||
rootItem.save_screenshot();
|
||||
@@ -101,266 +145,298 @@ HomeForm{
|
||||
}
|
||||
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
|
||||
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
Component.onCompleted: {
|
||||
console.log("home.qml completed");
|
||||
}
|
||||
|
||||
GridView {
|
||||
GridView {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175 * settings.ui_zoom / 100
|
||||
cellHeight: 130 * settings.ui_zoom / 100
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
|
||||
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
|
||||
id: gridView
|
||||
objectName: "gridview"
|
||||
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
|
||||
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
|
||||
Screen.onPrimaryOrientationChanged:{
|
||||
if(OS_VERSION === "Android")
|
||||
gridView.leftMargin = (Screen.width % cellWidth) / 2;
|
||||
else
|
||||
gridView.leftMargin = (parent.width % cellWidth) / 2;
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 170 * settings.ui_zoom / 100
|
||||
height: 125 * settings.ui_zoom / 100
|
||||
|
||||
visible: visibleItem
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
|
||||
Behavior on x {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
SequentialAnimation on rotation {
|
||||
NumberAnimation { to: 2; duration: 60 }
|
||||
NumberAnimation { to: -2; duration: 120 }
|
||||
NumberAnimation { to: 0; duration: 60 }
|
||||
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
|
||||
loops: Animation.Infinite; alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "active"; when: loc.currentId === gridId && window.lockTiles
|
||||
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
|
||||
}
|
||||
|
||||
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
|
||||
|
||||
Rectangle {
|
||||
width: 168 * settings.ui_zoom / 100
|
||||
height: 123 * settings.ui_zoom / 100
|
||||
radius: 3
|
||||
border.width: 1
|
||||
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
|
||||
color: settings.theme_tile_background_color
|
||||
id: rect
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
visible: settings.theme_tile_shadow_enabled
|
||||
anchors.fill: rect
|
||||
cached: true
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: settings.theme_tile_shadow_color
|
||||
source: rect
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toggleIconTimer
|
||||
interval: 500; running: true; repeat: true
|
||||
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
bottom: id1.bottom
|
||||
}
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
source: icon
|
||||
visible: settings.theme_tile_icon_enabled && !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: valueFontColor
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: valueFontSize * settings.ui_zoom / 100
|
||||
font.bold: true
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "secondLine"
|
||||
id: secondLineText
|
||||
color: "white"
|
||||
y: myValue.bottom
|
||||
anchors {
|
||||
top: myValue.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: secondLine
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
|
||||
font.bold: false
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
id: myText
|
||||
anchors {
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
font.pointSize: labelFontSize
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55 * settings.ui_zoom / 100
|
||||
anchors.topMargin: 20 * settings.ui_zoom / 100
|
||||
visible: !largeButton
|
||||
}
|
||||
RoundButton {
|
||||
objectName: minusName
|
||||
autoRepeat: true
|
||||
text: "-"
|
||||
onClicked: minus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: plusName
|
||||
text: "+"
|
||||
onClicked: plus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: identificator
|
||||
text: largeButtonLabel
|
||||
onClicked: largeButton_clicked(objectName)
|
||||
visible: largeButton
|
||||
anchors.fill: rect
|
||||
background: Rectangle {
|
||||
color: largeButtonColor
|
||||
radius: 20
|
||||
}
|
||||
font.pointSize: 20 * settings.ui_zoom / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
id: footerItem
|
||||
width: parent.width
|
||||
height: footerHeight
|
||||
property real footerHeight: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
|
||||
property real minHeight: parent.height / 4
|
||||
property real maxHeight: parent.height * 3 / 4
|
||||
anchors.bottom: parent.bottom
|
||||
clip: true
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
width: parent.width / 5
|
||||
height: 10
|
||||
color: "#9C27B0"
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175 * settings.ui_zoom / 100
|
||||
cellHeight: 130 * settings.ui_zoom / 100
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
|
||||
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
|
||||
id: gridView
|
||||
objectName: "gridview"
|
||||
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
|
||||
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
|
||||
Screen.onPrimaryOrientationChanged:{
|
||||
if(OS_VERSION === "Android")
|
||||
gridView.leftMargin = (Screen.width % cellWidth) / 2;
|
||||
else
|
||||
gridView.leftMargin = (parent.width % cellWidth) / 2;
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Canvas {
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.strokeStyle = "#FFFFFF";
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, (i + 1) * parent.height / 4);
|
||||
ctx.lineTo(parent.width, (i + 1) * parent.height / 4);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// highlight: Rectangle {
|
||||
// width: 150
|
||||
// height: 150
|
||||
// color: "lightsteelblue"
|
||||
// }
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 170 * settings.ui_zoom / 100
|
||||
height: 125 * settings.ui_zoom / 100
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
|
||||
visible: visibleItem
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
property real startY: 0
|
||||
property real startHeight: 0
|
||||
|
||||
Behavior on x {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
onPressed: {
|
||||
startY = mouseY
|
||||
startHeight = footerItem.height
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
SequentialAnimation on rotation {
|
||||
NumberAnimation { to: 2; duration: 60 }
|
||||
NumberAnimation { to: -2; duration: 120 }
|
||||
NumberAnimation { to: 0; duration: 60 }
|
||||
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
|
||||
loops: Animation.Infinite; alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "active"; when: loc.currentId === gridId && window.lockTiles
|
||||
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
|
||||
}
|
||||
|
||||
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
|
||||
|
||||
Rectangle {
|
||||
width: 168 * settings.ui_zoom / 100
|
||||
height: 123 * settings.ui_zoom / 100
|
||||
radius: 3
|
||||
border.width: 1
|
||||
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
|
||||
color: settings.theme_tile_background_color
|
||||
id: rect
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
visible: settings.theme_tile_shadow_enabled
|
||||
anchors.fill: rect
|
||||
cached: true
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: settings.theme_tile_shadow_color
|
||||
source: rect
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toggleIconTimer
|
||||
interval: 500; running: true; repeat: true
|
||||
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
bottom: id1.bottom
|
||||
onMouseYChanged: {
|
||||
if (pressed) {
|
||||
var newHeight = Math.max(footerItem.minHeight, Math.min(footerItem.maxHeight, startHeight + startY - mouseY))
|
||||
footerItem.footerHeight = newHeight
|
||||
}
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
source: icon
|
||||
visible: settings.theme_tile_icon_enabled && !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: valueFontColor
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: valueFontSize * settings.ui_zoom / 100
|
||||
font.bold: true
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "secondLine"
|
||||
id: secondLineText
|
||||
color: "white"
|
||||
y: myValue.bottom
|
||||
anchors {
|
||||
top: myValue.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: secondLine
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
|
||||
font.bold: false
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
id: myText
|
||||
anchors {
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
font.pointSize: labelFontSize
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55 * settings.ui_zoom / 100
|
||||
anchors.topMargin: 20 * settings.ui_zoom / 100
|
||||
visible: !largeButton
|
||||
}
|
||||
RoundButton {
|
||||
objectName: minusName
|
||||
autoRepeat: true
|
||||
text: "-"
|
||||
onClicked: minus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: plusName
|
||||
text: "+"
|
||||
onClicked: plus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: identificator
|
||||
text: largeButtonLabel
|
||||
onClicked: largeButton_clicked(objectName)
|
||||
visible: largeButton
|
||||
anchors.fill: rect
|
||||
background: Rectangle {
|
||||
color: largeButtonColor
|
||||
radius: 20
|
||||
}
|
||||
font.pointSize: 20 * settings.ui_zoom / 100
|
||||
//width: 48 * settings.ui_zoom / 100
|
||||
//height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
|
||||
/*MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: parent.GridView.view.currentIndex = index
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
footer:
|
||||
Item {
|
||||
width: parent.width
|
||||
height: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
|
||||
anchors.top: gridView.bottom
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Rectangle {
|
||||
id: chartFooterRectangle
|
||||
visible: rootItem.chartFooterVisible
|
||||
anchors.fill: parent
|
||||
ChartFooter {
|
||||
anchors.fill: parent
|
||||
visible: rootItem.chartFooterVisible
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.fill: parent
|
||||
// Removed Timer, Play/Pause/Resume is now done via Homeform.cpp
|
||||
/*
|
||||
Timer {
|
||||
id: pauseTimer
|
||||
interval: 1000; running: true; repeat: true
|
||||
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
|
||||
videoPlaybackHalf.play() :
|
||||
videoPlaybackHalf.pause()) } }
|
||||
}
|
||||
*/
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
//videoPlaybackHalf.playbackRate = rootItem.videoRate
|
||||
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: chartFooterRectangle
|
||||
visible: rootItem.chartFooterVisible
|
||||
anchors.top: dragHandle.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
ChartFooter {
|
||||
anchors.fill: parent
|
||||
visible: rootItem.chartFooterVisible
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.top: dragHandle.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id: videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property int currentId: -1 // Original position in model
|
||||
property int newIndex // Current Position in model
|
||||
|
||||
20
src/IndicatorOnlySwitch.qml
Normal file
20
src/IndicatorOnlySwitch.qml
Normal file
@@ -0,0 +1,20 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Dialogs 1.0
|
||||
|
||||
SwitchDelegate {
|
||||
id: root
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
if (mouse.x > parent.width - parent.indicator.width) {
|
||||
root.checked = !root.checked
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
StaticAccordionElement {
|
||||
title: qsTr("Settings folder")
|
||||
indicatRectColor: Material.color(Material.Grey)
|
||||
textColor: Material.color(Material.Grey)
|
||||
|
||||
68
src/StaticAccordionElement.qml
Normal file
68
src/StaticAccordionElement.qml
Normal file
@@ -0,0 +1,68 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
ColumnLayout {
|
||||
id: rootElement
|
||||
property bool isOpen: false
|
||||
property string title: ""
|
||||
property alias color: accordionHeader.color
|
||||
property alias textColor: accordionText.color
|
||||
property alias textFont: accordionText.font.family
|
||||
property alias textFontSize: accordionText.font.pixelSize
|
||||
property alias indicatRectColor: indicatRect.color
|
||||
default property alias accordionContent: contentPlaceholder.data
|
||||
spacing: 0
|
||||
|
||||
Layout.fillWidth: true;
|
||||
|
||||
Rectangle {
|
||||
id: accordionHeader
|
||||
color: "red"
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true;
|
||||
height: 48
|
||||
|
||||
Rectangle{
|
||||
id:indicatRect
|
||||
x: 16; y: 20
|
||||
width: 8; height: 8
|
||||
radius: 8
|
||||
color: "white"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: accordionText
|
||||
x:34;y:13
|
||||
color: "#FFFFFF"
|
||||
text: rootElement.title
|
||||
}
|
||||
Image {
|
||||
y:13
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
width: 30; height: 30
|
||||
id: indicatImg
|
||||
source: "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
rootElement.isOpen = !rootElement.isOpen
|
||||
if(rootElement.isOpen)
|
||||
{
|
||||
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
|
||||
}else{
|
||||
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will get filled with the content
|
||||
ColumnLayout {
|
||||
id: contentPlaceholder
|
||||
visible: rootElement.isOpen
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
}
|
||||
1428
src/Wizard.qml
Normal file
1428
src/Wizard.qml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.16.57" android:versionCode="804" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.17" android:versionCode="999" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
@@ -78,7 +78,8 @@
|
||||
<service
|
||||
android:name=".ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="false"></service>
|
||||
<service
|
||||
android:name=".WearableMessageListenerService"
|
||||
android:enabled="true"
|
||||
@@ -97,6 +98,7 @@
|
||||
|
||||
<service android:name="com.cgutman.androidremotedebugger.service.ShellService"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="false" >
|
||||
</service>
|
||||
|
||||
@@ -109,13 +111,21 @@
|
||||
android:value="ocr" />
|
||||
|
||||
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
|
||||
|
||||
<receiver android:name=".MediaButtonReceiver" android:exported="true" android:permission="android.permission.MODIFY_AUDIO_SETTINGS">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_MEDIA_BUTTON" />
|
||||
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
|
||||
@@ -126,6 +136,7 @@
|
||||
<uses-permission android:name="com.android.vending.BILLING"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
@@ -18,7 +18,6 @@ repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://dl.bintray.com/rvalerio/maven' }
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
@@ -28,11 +27,11 @@ def amazon = System.getenv('AMAZON')
|
||||
println(amazon)
|
||||
|
||||
dependencies {
|
||||
compile 'com.rvalerio:fgchecker:1.1.0'
|
||||
implementation "androidx.core:core:1.12.0"
|
||||
implementation "androidx.core:core-ktx:1.12.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
|
||||
|
||||
|
||||
if(amazon == "1") {
|
||||
// amazon app store
|
||||
implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
|
||||
@@ -45,13 +44,13 @@ dependencies {
|
||||
|
||||
def appcompat_version = "1.3.1"
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation "com.android.billingclient:billing:6.0.0"
|
||||
implementation "com.android.billingclient:billing:6.0.1"
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.6'
|
||||
implementation files('libs/usb-serial-for-android-3.8.1.aar')
|
||||
androidTestImplementation "com.android.support:support-annotations:28.0.0"
|
||||
implementation 'com.google.android.gms:play-services-wearable:+'
|
||||
|
||||
@@ -130,7 +129,7 @@ android {
|
||||
resConfig "en"
|
||||
compileSdkVersion 33
|
||||
minSdkVersion = 21
|
||||
targetSdkVersion = 33
|
||||
targetSdkVersion = 34
|
||||
}
|
||||
|
||||
tasks.all { task ->
|
||||
|
||||
BIN
src/android/libs/android-antplus-plugin-lib-release_3.9.0.aar
Normal file
BIN
src/android/libs/android-antplus-plugin-lib-release_3.9.0.aar
Normal file
Binary file not shown.
BIN
src/android/libs/ciq-companion-app-sdk-2.0.3.aar
Normal file
BIN
src/android/libs/ciq-companion-app-sdk-2.0.3.aar
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/android/libs/usb-serial-for-android-3.8.1.aar
Normal file
BIN
src/android/libs/usb-serial-for-android-3.8.1.aar
Normal file
Binary file not shown.
@@ -31,7 +31,7 @@ public class Ant {
|
||||
private ChannelService.ChannelServiceComm mChannelService = null;
|
||||
private boolean mChannelServiceBound = false;
|
||||
private final String TAG = "Ant";
|
||||
private Activity activity = null;
|
||||
public static Activity activity = null;
|
||||
static boolean speedRequest = false;
|
||||
static boolean heartRequest = false;
|
||||
static boolean garminKey = false;
|
||||
|
||||
@@ -37,9 +37,10 @@ import java.util.UUID;
|
||||
|
||||
public class BleAdvertiser {
|
||||
private static final UUID SERVICE_UUID = UUID.fromString("00001826-0000-1000-8000-00805f9b34fb");
|
||||
private static final byte[] SERVICE_DATA = {0x01, 0x10, 0x00};
|
||||
private static final byte[] SERVICE_DATA_ROWER = {0x01, 0x10, 0x00};
|
||||
private static final byte[] SERVICE_DATA_TREADMILL = {0x01, 0x01, 0x00};
|
||||
|
||||
public static void startAdvertising(Context context) {
|
||||
public static void startAdvertisingRower(Context context) {
|
||||
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (bluetoothManager != null) {
|
||||
android.bluetooth.le.BluetoothLeAdvertiser advertiser = bluetoothManager.getAdapter().getBluetoothLeAdvertiser();
|
||||
@@ -53,7 +54,30 @@ public class BleAdvertiser {
|
||||
AdvertiseData advertiseData = new AdvertiseData.Builder()
|
||||
.setIncludeDeviceName(true)
|
||||
.addServiceUuid(new ParcelUuid(SERVICE_UUID))
|
||||
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA)
|
||||
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA_ROWER)
|
||||
.build();
|
||||
|
||||
if (advertiser != null) {
|
||||
advertiser.startAdvertising(settings, advertiseData, advertiseCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void startAdvertisingTreadmill(Context context) {
|
||||
BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
|
||||
if (bluetoothManager != null) {
|
||||
android.bluetooth.le.BluetoothLeAdvertiser advertiser = bluetoothManager.getAdapter().getBluetoothLeAdvertiser();
|
||||
|
||||
AdvertiseSettings settings = new AdvertiseSettings.Builder()
|
||||
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
|
||||
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
|
||||
.setConnectable(true)
|
||||
.build();
|
||||
|
||||
AdvertiseData advertiseData = new AdvertiseData.Builder()
|
||||
.setIncludeDeviceName(true)
|
||||
.addServiceUuid(new ParcelUuid(SERVICE_UUID))
|
||||
.addServiceData(new ParcelUuid(SERVICE_UUID), SERVICE_DATA_TREADMILL)
|
||||
.build();
|
||||
|
||||
if (advertiser != null) {
|
||||
|
||||
@@ -36,6 +36,8 @@ import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -153,7 +155,7 @@ public class ChannelService extends Service {
|
||||
|
||||
public void openAllChannels() throws ChannelNotAvailableException {
|
||||
if (Ant.heartRequest && heartChannelController == null)
|
||||
heartChannelController = new HeartChannelController(acquireChannel());
|
||||
heartChannelController = new HeartChannelController();
|
||||
|
||||
if (Ant.speedRequest) {
|
||||
if(Ant.treadmill && sdmChannelController == null) {
|
||||
@@ -258,8 +260,12 @@ public class ChannelService extends Service {
|
||||
private void doBindAntRadioService() {
|
||||
if (BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
|
||||
|
||||
// Start listing for channel available intents
|
||||
registerReceiver(mChannelProviderStateChangedReceiver, new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED));
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
mChannelProviderStateChangedReceiver,
|
||||
new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED),
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
|
||||
// Creating the intent and calling context.bindService() is handled by
|
||||
// the static bindService() method in AntService
|
||||
|
||||
@@ -9,9 +9,14 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.util.Log;
|
||||
|
||||
public class ForegroundService extends Service {
|
||||
public static final String CHANNEL_ID = "ForegroundServiceChannel";
|
||||
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
|
||||
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
@@ -29,7 +34,18 @@ public class ForegroundService extends Service {
|
||||
.setSmallIcon(R.drawable.icon)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
startForeground(1, notification);
|
||||
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(1, notification, serviceType);
|
||||
} else {
|
||||
startForeground(1, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
//do heavy work on a background thread
|
||||
//stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.Toast;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -48,12 +49,24 @@ public class Garmin {
|
||||
|
||||
private static Integer HR = 0;
|
||||
private static Integer FootCad = 0;
|
||||
private static Double Speed = 0.0;
|
||||
private static Integer Power = 0;
|
||||
|
||||
public static int getHR() {
|
||||
Log.d(TAG, "getHR " + HR);
|
||||
return HR;
|
||||
}
|
||||
|
||||
public static int getPower() {
|
||||
Log.d(TAG, "getPower " + Power);
|
||||
return Power;
|
||||
}
|
||||
|
||||
public static double getSpeed() {
|
||||
Log.d(TAG, "getSpeed " + Speed);
|
||||
return Speed;
|
||||
}
|
||||
|
||||
public static int getFootCad() {
|
||||
Log.d(TAG, "getFootCad " + FootCad);
|
||||
return FootCad;
|
||||
@@ -152,7 +165,12 @@ public class Garmin {
|
||||
synchronized (receiverToWrapper) {
|
||||
receiverToWrapper.put(receiver, wrappedRecv);
|
||||
}
|
||||
return super.registerReceiver(wrappedRecv, filter);
|
||||
return ContextCompat.registerReceiver(
|
||||
super.getBaseContext(),
|
||||
wrappedRecv,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,13 +239,21 @@ public class Garmin {
|
||||
if (status == ConnectIQ.IQMessageStatus.SUCCESS) {
|
||||
//MessageHandler.getInstance().handleMessageFromWatchUsingCIQ(message, status, context);
|
||||
Log.d(TAG, "onMessageReceived, status: " + status.toString() + message.get(0));
|
||||
String var[] = message.toArray()[0].toString().split(",");
|
||||
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 1) {
|
||||
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
try {
|
||||
String var[] = message.toArray()[0].toString().split(",");
|
||||
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 1) {
|
||||
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 2) {
|
||||
Power = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
Speed = Double.parseDouble(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "HR " + HR);
|
||||
Log.d(TAG, "FootCad " + FootCad);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Processing error", e);
|
||||
}
|
||||
Log.d(TAG, "HR " + HR);
|
||||
Log.d(TAG, "FootCad " + FootCad);
|
||||
} else {
|
||||
Log.d(TAG, "onMessageReceived error, status: " + status.toString());
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
/*
|
||||
* Copyright 2012 Dynastream Innovations Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.app.Activity;
|
||||
|
||||
// ANT+ Plugin imports
|
||||
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc;
|
||||
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.DataState;
|
||||
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.IHeartRateDataReceiver;
|
||||
import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState;
|
||||
import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag;
|
||||
import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult;
|
||||
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IDeviceStateChangeReceiver;
|
||||
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver;
|
||||
import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle;
|
||||
|
||||
// Basic ANT imports for legacy support
|
||||
import com.dsi.ant.channel.AntChannel;
|
||||
import com.dsi.ant.channel.AntCommandFailedException;
|
||||
import com.dsi.ant.channel.IAntChannelEventHandler;
|
||||
@@ -30,220 +28,103 @@ import com.dsi.ant.message.fromant.ChannelEventMessage;
|
||||
import com.dsi.ant.message.fromant.MessageFromAntType;
|
||||
import com.dsi.ant.message.ipc.AntMessageParcel;
|
||||
|
||||
// Java imports
|
||||
import java.math.BigDecimal;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Random;
|
||||
|
||||
public class HeartChannelController {
|
||||
// The device type and transmission type to be part of the channel ID message
|
||||
private static final int CHANNEL_HEART_DEVICE_TYPE = 0x78;
|
||||
private static final int CHANNEL_HEART_TRANSMISSION_TYPE = 1;
|
||||
private static final String TAG = HeartChannelController.class.getSimpleName();
|
||||
|
||||
private Context context;
|
||||
private AntPlusHeartRatePcc hrPcc = null;
|
||||
private PccReleaseHandle<AntPlusHeartRatePcc> releaseHandle = null;
|
||||
private boolean isConnected = false;
|
||||
public int heart = 0; // Public to be accessible from ChannelService
|
||||
|
||||
// The period and frequency values the channel will be configured to
|
||||
private static final int CHANNEL_HEART_PERIOD = 8118; // 1 Hz
|
||||
private static final int CHANNEL_HEART_FREQUENCY = 57;
|
||||
public HeartChannelController() {
|
||||
this.context = Ant.activity;
|
||||
openChannel();
|
||||
}
|
||||
|
||||
private static final String TAG = HeartChannelController.class.getSimpleName();
|
||||
public boolean openChannel() {
|
||||
// Request access to first available heart rate device
|
||||
releaseHandle = AntPlusHeartRatePcc.requestAccess((Activity)context, 0, 0, // 0 means first available device
|
||||
new IPluginAccessResultReceiver<AntPlusHeartRatePcc>() {
|
||||
@Override
|
||||
public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
|
||||
switch(resultCode) {
|
||||
case SUCCESS:
|
||||
hrPcc = result;
|
||||
isConnected = true;
|
||||
Log.d(TAG, "Connected to heart rate monitor: " + result.getDeviceName());
|
||||
subscribeToHrEvents();
|
||||
break;
|
||||
case CHANNEL_NOT_AVAILABLE:
|
||||
Log.e(TAG, "Channel Not Available");
|
||||
break;
|
||||
case ADAPTER_NOT_DETECTED:
|
||||
Log.e(TAG, "ANT Adapter Not Available");
|
||||
break;
|
||||
case BAD_PARAMS:
|
||||
Log.e(TAG, "Bad request parameters");
|
||||
break;
|
||||
case OTHER_FAILURE:
|
||||
Log.e(TAG, "RequestAccess failed");
|
||||
break;
|
||||
case DEPENDENCY_NOT_INSTALLED:
|
||||
Log.e(TAG, "Dependency not installed");
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unrecognized result: " + resultCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
new IDeviceStateChangeReceiver() {
|
||||
@Override
|
||||
public void onDeviceStateChange(DeviceState newDeviceState) {
|
||||
Log.d(TAG, "Device State Changed to: " + newDeviceState);
|
||||
if (newDeviceState == DeviceState.DEAD) {
|
||||
isConnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private static Random randGen = new Random();
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
private AntChannel mAntChannel;
|
||||
private void subscribeToHrEvents() {
|
||||
if (hrPcc != null) {
|
||||
hrPcc.subscribeHeartRateDataEvent(new IHeartRateDataReceiver() {
|
||||
@Override
|
||||
public void onNewHeartRateData(long estTimestamp, EnumSet<EventFlag> eventFlags,
|
||||
int computedHeartRate, long heartBeatCount,
|
||||
BigDecimal heartBeatEventTime, DataState dataState) {
|
||||
|
||||
heart = computedHeartRate;
|
||||
Log.d(TAG, "Heart Rate: " + heart);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
|
||||
public void close() {
|
||||
if (releaseHandle != null) {
|
||||
releaseHandle.close();
|
||||
releaseHandle = null;
|
||||
}
|
||||
hrPcc = null;
|
||||
isConnected = false;
|
||||
Log.d(TAG, "Channel Closed");
|
||||
}
|
||||
|
||||
public int getHeartRate() {
|
||||
return heart;
|
||||
}
|
||||
|
||||
private boolean mIsOpen;
|
||||
int heart = 0;
|
||||
|
||||
public HeartChannelController(AntChannel antChannel) {
|
||||
mAntChannel = antChannel;
|
||||
openChannel();
|
||||
}
|
||||
|
||||
boolean openChannel() {
|
||||
if (null != mAntChannel) {
|
||||
if (mIsOpen) {
|
||||
Log.w(TAG, "Channel was already open");
|
||||
} else {
|
||||
// Channel ID message contains device number, type and transmission type. In
|
||||
// order for master (TX) channels and slave (RX) channels to connect, they
|
||||
// must have the same channel ID, or wildcard (0) is used.
|
||||
ChannelId channelId = new ChannelId(0,
|
||||
CHANNEL_HEART_DEVICE_TYPE, CHANNEL_HEART_TRANSMISSION_TYPE);
|
||||
|
||||
try {
|
||||
// Setting the channel event handler so that we can receive messages from ANT
|
||||
mAntChannel.setChannelEventHandler(mChannelEventCallback);
|
||||
|
||||
// Performs channel assignment by assigning the type to the channel. Additional
|
||||
// features (such as, background scanning and frequency agility) can be enabled
|
||||
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
|
||||
mAntChannel.assign(ChannelType.SLAVE_RECEIVE_ONLY);
|
||||
|
||||
/*
|
||||
* Configures the channel ID, messaging period and rf frequency after assigning,
|
||||
* then opening the channel.
|
||||
*
|
||||
* For any additional ANT features such as proximity search or background scanning, refer to
|
||||
* the ANT Protocol Doc found at:
|
||||
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
|
||||
*/
|
||||
mAntChannel.setChannelId(channelId);
|
||||
mAntChannel.setPeriod(CHANNEL_HEART_PERIOD);
|
||||
mAntChannel.setRfFrequency(CHANNEL_HEART_FREQUENCY);
|
||||
mAntChannel.open();
|
||||
mIsOpen = true;
|
||||
|
||||
Log.d(TAG, "Opened channel with device number");
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
} catch (AntCommandFailedException e) {
|
||||
// This will release, and therefore unassign if required
|
||||
channelError("Open failed", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No channel available");
|
||||
}
|
||||
|
||||
return mIsOpen;
|
||||
}
|
||||
|
||||
void channelError(RemoteException e) {
|
||||
String logString = "Remote service communication failed.";
|
||||
|
||||
Log.e(TAG, logString);
|
||||
}
|
||||
|
||||
void channelError(String error, AntCommandFailedException e) {
|
||||
StringBuilder logString;
|
||||
|
||||
if (e.getResponseMessage() != null) {
|
||||
String initiatingMessageId = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getInitiatingMessageId());
|
||||
String rawResponseCode = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getRawResponseCode());
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(initiatingMessageId)
|
||||
.append(" failed with code ")
|
||||
.append(rawResponseCode);
|
||||
} else {
|
||||
String attemptedMessageId = "0x" + Integer.toHexString(
|
||||
e.getAttemptedMessageType().getMessageId());
|
||||
String failureReason = e.getFailureReason().toString();
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(attemptedMessageId)
|
||||
.append(" failed with reason ")
|
||||
.append(failureReason);
|
||||
}
|
||||
|
||||
Log.e(TAG, logString.toString());
|
||||
|
||||
mAntChannel.release();
|
||||
|
||||
Log.e(TAG, "ANT Command Failed");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// TODO kill all our resources
|
||||
if (null != mAntChannel) {
|
||||
mIsOpen = false;
|
||||
|
||||
// Releasing the channel to make it available for others.
|
||||
// After releasing, the AntChannel instance cannot be reused.
|
||||
mAntChannel.release();
|
||||
mAntChannel = null;
|
||||
}
|
||||
|
||||
Log.e(TAG, "Channel Closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Channel Event Handler Interface so that messages can be
|
||||
* received and channel death events can be handled.
|
||||
*/
|
||||
public class ChannelEventCallback implements IAntChannelEventHandler {
|
||||
int revCounts = 0;
|
||||
int ucMessageCount = 0;
|
||||
byte ucPageChange = 0;
|
||||
byte ucExtMesgType = 1;
|
||||
long lastTime = 0;
|
||||
double way;
|
||||
int rev;
|
||||
double remWay;
|
||||
double wheel = 0.1;
|
||||
|
||||
@Override
|
||||
public void onChannelDeath() {
|
||||
// Display channel death message when channel dies
|
||||
Log.e(TAG, "Channel Death");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
|
||||
Log.d(TAG, "Rx: " + antParcel);
|
||||
Log.d(TAG, "Message Type: " + messageType);
|
||||
|
||||
// Switching on message type to handle different types of messages
|
||||
switch (messageType) {
|
||||
// If data message, construct from parcel and update channel data
|
||||
case BROADCAST_DATA:
|
||||
// Rx Data
|
||||
//updateData(new BroadcastDataMessage(antParcel).getPayload());
|
||||
BroadcastDataMessage m = new BroadcastDataMessage(antParcel);
|
||||
Log.d(TAG, "BROADCAST_DATA: " + m.getPayload());
|
||||
heart = m.getPayload()[7];
|
||||
Log.d(TAG, "BROADCAST_DATA: " + heart);
|
||||
break;
|
||||
case ACKNOWLEDGED_DATA:
|
||||
// Rx Data
|
||||
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
|
||||
Log.d(TAG, "ACKNOWLEDGED_DATA: " + new AcknowledgedDataMessage(antParcel).getPayload());
|
||||
break;
|
||||
case CHANNEL_EVENT:
|
||||
// Constructing channel event message from parcel
|
||||
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
|
||||
EventCode code = eventMessage.getEventCode();
|
||||
Log.d(TAG, "Event Code: " + code);
|
||||
|
||||
// Switching on event code to handle the different types of channel events
|
||||
switch (code) {
|
||||
case TX:
|
||||
break;
|
||||
case CHANNEL_COLLISION:
|
||||
ucPageChange += 0x20;
|
||||
ucPageChange &= 0xF0;
|
||||
ucMessageCount += 1;
|
||||
break;
|
||||
case RX_SEARCH_TIMEOUT:
|
||||
// TODO May want to keep searching
|
||||
Log.e(TAG, "No Device Found");
|
||||
break;
|
||||
case CHANNEL_CLOSED:
|
||||
case RX_FAIL:
|
||||
case RX_FAIL_GO_TO_SEARCH:
|
||||
case TRANSFER_RX_FAILED:
|
||||
case TRANSFER_TX_COMPLETED:
|
||||
case TRANSFER_TX_FAILED:
|
||||
case TRANSFER_TX_START:
|
||||
case UNKNOWN:
|
||||
// TODO More complex communication will need to handle these events
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ANT_VERSION:
|
||||
case BURST_TRANSFER_DATA:
|
||||
case CAPABILITIES:
|
||||
case CHANNEL_ID:
|
||||
case CHANNEL_RESPONSE:
|
||||
case CHANNEL_STATUS:
|
||||
case SERIAL_NUMBER:
|
||||
case OTHER:
|
||||
// TODO More complex communication will need to handle these message types
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public boolean isConnected() {
|
||||
return isConnected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* This class is used for talking to hid of the dongle, connecting, disconnencting and enumerating the devices.
|
||||
@@ -88,7 +89,12 @@ public class HidBridge {
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(_context, 0, new Intent(ACTION_USB_PERMISSION), flags);
|
||||
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
|
||||
_context.registerReceiver(mUsbReceiver, filter);
|
||||
ContextCompat.registerReceiver(
|
||||
_context,
|
||||
mUsbReceiver,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
|
||||
_usbManager.requestPermission(_usbDevice, mPermissionIntent);
|
||||
Log("Found the device");
|
||||
|
||||
65
src/android/src/LocationHelper.java
Normal file
65
src/android/src/LocationHelper.java
Normal 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);
|
||||
}
|
||||
}
|
||||
71
src/android/src/MediaButtonReceiver.java
Normal file
71
src/android/src/MediaButtonReceiver.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
private static MediaButtonReceiver instance;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("MediaButtonReceiver", "Received intent: " + intent.toString());
|
||||
String intentAction = intent.getAction();
|
||||
if ("android.media.VOLUME_CHANGED_ACTION".equals(intentAction)) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
||||
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
|
||||
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
|
||||
|
||||
Log.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
|
||||
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
|
||||
}
|
||||
}
|
||||
|
||||
private native void nativeOnMediaButtonEvent(int prev, int current, int max);
|
||||
|
||||
public static void registerReceiver(Context context) {
|
||||
try {
|
||||
if (instance == null) {
|
||||
instance = new MediaButtonReceiver();
|
||||
}
|
||||
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
|
||||
|
||||
if (context == null) {
|
||||
Log.e("MediaButtonReceiver", "Context is null, cannot register receiver");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
try {
|
||||
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
|
||||
} catch (SecurityException se) {
|
||||
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
context.registerReceiver(instance, filter);
|
||||
} catch (SecurityException se) {
|
||||
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
|
||||
}
|
||||
}
|
||||
Log.d("MediaButtonReceiver", "Receiver registered successfully");
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
Log.e("MediaButtonReceiver", "Unexpected error while registering receiver: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void unregisterReceiver(Context context) {
|
||||
if (instance != null) {
|
||||
context.unregisterReceiver(instance);
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,12 +16,18 @@ public class NotificationClient
|
||||
private static Context _context;
|
||||
private static Intent serviceIntent = null;
|
||||
|
||||
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
|
||||
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
|
||||
|
||||
public NotificationClient() {}
|
||||
|
||||
public static void notify(Context context, String message) {
|
||||
_context = context;
|
||||
serviceIntent = new Intent(context, ForegroundService.class);
|
||||
serviceIntent.putExtra("inputExtra", "QZ is Running");
|
||||
serviceIntent.putExtra(EXTRA_FOREGROUND_SERVICE_TYPE,
|
||||
FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(serviceIntent);
|
||||
} else {
|
||||
|
||||
@@ -49,6 +49,9 @@ public class QZAdbRemote implements DeviceConnectionListener {
|
||||
|
||||
private static QZAdbRemote INSTANCE;
|
||||
|
||||
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
|
||||
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
|
||||
|
||||
public static QZAdbRemote getInstance() {
|
||||
if(INSTANCE == null) {
|
||||
INSTANCE = new QZAdbRemote();
|
||||
@@ -181,13 +184,14 @@ public class QZAdbRemote implements DeviceConnectionListener {
|
||||
|
||||
if (binder == null) {
|
||||
service = new Intent(_context, ShellService.class);
|
||||
service.putExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
|
||||
/* Bind the service if we're not bound already. After binding, the callback will
|
||||
* perform the initial connection. */
|
||||
_context.bindService(service, QZAdbRemote.getInstance().serviceConn, Service.BIND_AUTO_CREATE);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
_context.startForegroundService(service);
|
||||
_context.startForegroundService(service);
|
||||
}
|
||||
else {
|
||||
_context.startService(service);
|
||||
|
||||
@@ -43,6 +43,8 @@ import android.graphics.Rect;
|
||||
import android.graphics.Point;
|
||||
|
||||
import androidx.core.util.Pair;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
|
||||
public class ScreenCaptureService extends Service {
|
||||
|
||||
@@ -56,6 +58,9 @@ public class ScreenCaptureService extends Service {
|
||||
|
||||
private static int IMAGES_PRODUCED;
|
||||
|
||||
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
|
||||
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
|
||||
|
||||
private MediaProjection mMediaProjection;
|
||||
private String mStoreDir;
|
||||
private ImageReader mImageReader;
|
||||
@@ -296,7 +301,18 @@ public class ScreenCaptureService extends Service {
|
||||
if (isStartCommand(intent)) {
|
||||
// create notification
|
||||
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
|
||||
startForeground(notification.first, notification.second);
|
||||
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(notification.first, notification.second, serviceType);
|
||||
} else {
|
||||
startForeground(notification.first, notification.second);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
// start projection
|
||||
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
|
||||
Intent data = intent.getParcelableExtra(DATA);
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.app.Service;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
|
||||
@@ -68,7 +69,12 @@ public class Usbserial {
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.cagnulen.qdomyoszwift.USB_PERMISSION"), flags);
|
||||
IntentFilter filter = new IntentFilter("org.cagnulen.qdomyoszwift.USB_PERMISSION");
|
||||
context.registerReceiver(usbReceiver, filter);
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
usbReceiver,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
manager.requestPermission(driver.getDevice(), permissionIntent);
|
||||
for(int i=0; i<5000; i++) {
|
||||
if(granted[0] != null) break;
|
||||
|
||||
74
src/android/src/ZwiftHubBike.java
Normal file
74
src/android/src/ZwiftHubBike.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import com.garmin.android.connectiq.ConnectIQ;
|
||||
import com.garmin.android.connectiq.ConnectIQAdbStrategy;
|
||||
import com.garmin.android.connectiq.IQApp;
|
||||
import com.garmin.android.connectiq.IQDevice;
|
||||
import com.garmin.android.connectiq.exception.InvalidStateException;
|
||||
import com.garmin.android.connectiq.exception.ServiceUnavailableException;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class ZwiftHubBike {
|
||||
|
||||
private static Context context;
|
||||
|
||||
private static final String TAG = "ZwiftHubBike: ";
|
||||
|
||||
public static byte[] inclinationCommand(double inclination) throws InvalidProtocolBufferException {
|
||||
ZwiftHub.SimulationParam.Builder simulation = ZwiftHub.SimulationParam.newBuilder();
|
||||
simulation.setInclineX100((int)(inclination * 100.0));
|
||||
|
||||
ZwiftHub.HubCommand.Builder command = ZwiftHub.HubCommand.newBuilder();
|
||||
command.setSimulation(simulation.build());
|
||||
|
||||
byte[] data = command.build().toByteArray();
|
||||
byte[] fullData = new byte[data.length + 1];
|
||||
fullData[0] = 0x04;
|
||||
System.arraycopy(data, 0, fullData, 1, data.length);
|
||||
|
||||
return fullData;
|
||||
}
|
||||
|
||||
public static byte[] setGearCommand(int gears) throws InvalidProtocolBufferException {
|
||||
ZwiftHub.PhysicalParam.Builder physical = ZwiftHub.PhysicalParam.newBuilder();
|
||||
physical.setGearRatioX10000(gears);
|
||||
|
||||
ZwiftHub.HubCommand.Builder command = ZwiftHub.HubCommand.newBuilder();
|
||||
command.setPhysical(physical.build());
|
||||
|
||||
byte[] data = command.build().toByteArray();
|
||||
byte[] fullData = new byte[data.length + 1];
|
||||
fullData[0] = 0x04;
|
||||
System.arraycopy(data, 0, fullData, 1, data.length);
|
||||
|
||||
return fullData;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,12 @@ import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
public class ShellService extends Service implements DeviceConnectionListener {
|
||||
|
||||
private static final String EXTRA_FOREGROUND_SERVICE_TYPE = "FOREGROUND_SERVICE_TYPE";
|
||||
private static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 0x10;
|
||||
|
||||
private ShellServiceBinder binder = new ShellServiceBinder();
|
||||
private ShellListener listener = new ShellListener(this);
|
||||
@@ -98,10 +102,20 @@ public class ShellService extends Service implements DeviceConnectionListener {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (foregroundId == 0) {
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
// If we're not already running in the foreground, use a placeholder
|
||||
// notification until a real connection is established. After connection
|
||||
// establishment, the real notification will replace this one.
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification(), serviceType);
|
||||
} else {
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't restart if we've been killed. We will have already lost our connections
|
||||
@@ -220,7 +234,7 @@ public class ShellService extends Service implements DeviceConnectionListener {
|
||||
* and start it as foreground */
|
||||
foregroundId = getConnectedNotificationId(newConn);
|
||||
nm.cancel(foregroundId);
|
||||
startForeground(foregroundId, createConnectionNotification(newConn, true));
|
||||
startForeground(foregroundId, createConnectionNotification(newConn, true));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
126
src/android/src/com/rvalerio/fgchecker/AppChecker.java
Normal file
126
src/android/src/com/rvalerio/fgchecker/AppChecker.java
Normal 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);
|
||||
}
|
||||
}
|
||||
26
src/android/src/com/rvalerio/fgchecker/Utils.java
Normal file
26
src/android/src/com/rvalerio/fgchecker/Utils.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.rvalerio.fgchecker.detectors;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public interface Detector {
|
||||
String getForegroundApp(Context context);
|
||||
}
|
||||
@@ -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 ;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
162
src/android/src/main/proto/zwift_hub.proto
Normal file
162
src/android/src/main/proto/zwift_hub.proto
Normal file
@@ -0,0 +1,162 @@
|
||||
syntax = "proto2";
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
//-------------- Zwift Hub messages
|
||||
// The command code prepending this message is 0x00
|
||||
// This message is sent always following the change of the gear ratio probably to verify it was received properly
|
||||
message HubRequest {
|
||||
optional uint32 DataId = 1; // Value observed 520 and 0, 0 requests general info, 1-7 are the fields# in DeviceInformationContent, 520 requests the gear ratio
|
||||
// 512 to 534 responds unidentifiable data
|
||||
}
|
||||
|
||||
// The command code prepending this message is 0x03
|
||||
message HubRidingData {
|
||||
optional uint32 Power = 1;
|
||||
optional uint32 Cadence = 2;
|
||||
optional uint32 SpeedX100 = 3;
|
||||
optional uint32 HR = 4;
|
||||
optional uint32 Unknown1 = 5; // Values observed 0 when stopped, 2864, 4060, 4636, 6803
|
||||
optional uint32 Unknown2 = 6; // Values observed 25714, 30091 (constant during session)
|
||||
}
|
||||
|
||||
message SimulationParam {
|
||||
optional sint32 Wind = 1; // Wind in m/s * 100. In zwift there is no wind (0). Negative is backwind
|
||||
optional sint32 InclineX100 = 2; // Incline value * 100
|
||||
optional uint32 CWa = 3; // Wind coefficient CW * a * 10000. In zwift this is constant 0.51 (5100)
|
||||
optional uint32 Crr = 4; // Rolling resistance Crr * 100000. In zwift this is constant 0.004 (400)
|
||||
}
|
||||
|
||||
message PhysicalParam {
|
||||
optional uint32 GearRatioX10000 = 2;
|
||||
optional uint32 BikeWeightx100 = 4;
|
||||
optional uint32 RiderWeightx100 = 5;
|
||||
}
|
||||
|
||||
// The command code prepending this message is 0x04
|
||||
message HubCommand {
|
||||
optional uint32 PowerTarget = 3;
|
||||
optional SimulationParam Simulation = 4;
|
||||
optional PhysicalParam Physical = 5;
|
||||
}
|
||||
|
||||
//---------------- Zwift Play messages
|
||||
|
||||
enum PlayButtonStatus {
|
||||
ON = 0;
|
||||
OFF = 1;
|
||||
}
|
||||
// The command code prepending this message is 0x07
|
||||
message PlayKeyPadStatus {
|
||||
optional PlayButtonStatus RightPad = 1;
|
||||
optional PlayButtonStatus Button_Y_Up = 2;
|
||||
optional PlayButtonStatus Button_Z_Left = 3;
|
||||
optional PlayButtonStatus Button_A_Right = 4;
|
||||
optional PlayButtonStatus Button_B_Down = 5;
|
||||
optional PlayButtonStatus Button_On = 6;
|
||||
optional PlayButtonStatus Button_Shift = 7;
|
||||
optional sint32 Analog_LR = 8;
|
||||
optional sint32 Analog_UD = 9;
|
||||
}
|
||||
|
||||
|
||||
message PlayCommandParameters {
|
||||
optional uint32 param1 = 1;
|
||||
optional uint32 param2 = 2;
|
||||
optional uint32 HapticPattern = 3;
|
||||
}
|
||||
|
||||
message PlayCommandContents {
|
||||
optional PlayCommandParameters CommandParameters = 1;
|
||||
}
|
||||
|
||||
// The command code prepending this message is 0x12
|
||||
// This is sent to the control point to configure and make the controller vibrate
|
||||
message PlayCommand {
|
||||
optional PlayCommandContents CommandContents = 2;
|
||||
}
|
||||
|
||||
// The command code prepending this message is 0x19
|
||||
// This is sent periodically when there are no button presses
|
||||
message Idle {
|
||||
optional uint32 Unknown2 = 2;
|
||||
}
|
||||
|
||||
//----------------- Zwift Ride messages
|
||||
enum RideButtonMask {
|
||||
LEFT_BTN = 0x00001;
|
||||
UP_BTN = 0x00002;
|
||||
RIGHT_BTN = 0x00004;
|
||||
DOWN_BTN = 0x00008;
|
||||
A_BTN = 0x00010;
|
||||
B_BTN = 0x00020;
|
||||
Y_BTN = 0x00040;
|
||||
|
||||
Z_BTN = 0x00100;
|
||||
SHFT_UP_L_BTN = 0x00200;
|
||||
SHFT_DN_L_BTN = 0x00400;
|
||||
POWERUP_L_BTN = 0x00800;
|
||||
ONOFF_L_BTN = 0x01000;
|
||||
SHFT_UP_R_BTN = 0x02000;
|
||||
SHFT_DN_R_BTN = 0x04000;
|
||||
|
||||
POWERUP_R_BTN = 0x10000;
|
||||
ONOFF_R_BTN = 0x20000;
|
||||
}
|
||||
|
||||
enum RideAnalogLocation {
|
||||
LEFT = 0;
|
||||
RIGHT = 1;
|
||||
UP = 2;
|
||||
DOWN = 3;
|
||||
}
|
||||
|
||||
message RideAnalogKeyPress {
|
||||
optional RideAnalogLocation Location = 1;
|
||||
optional sint32 AnalogValue = 2;
|
||||
}
|
||||
|
||||
message RideAnalogKeyGroup {
|
||||
repeated RideAnalogKeyPress GroupStatus = 1;
|
||||
}
|
||||
|
||||
// The command code prepending this message is 0x23
|
||||
message RideKeyPadStatus {
|
||||
optional uint32 ButtonMap = 1;
|
||||
optional RideAnalogKeyGroup AnalogButtons = 2;
|
||||
}
|
||||
|
||||
//------------------ Zwift Click messages
|
||||
// The command code prepending this message is 0x37
|
||||
message ClickKeyPadStatus {
|
||||
optional PlayButtonStatus Button_Plus = 1;
|
||||
optional PlayButtonStatus Button_Minus = 2;
|
||||
}
|
||||
|
||||
//------------------ Device Information requested after connection
|
||||
// The command code prepending this message is 0x3c
|
||||
message DeviceInformationContent {
|
||||
optional uint32 Unknown1 = 1;
|
||||
repeated uint32 SoftwareVersion = 2;
|
||||
optional string DeviceName = 3;
|
||||
optional uint32 Unknown4 = 4;
|
||||
optional uint32 Unknown5 =5;
|
||||
optional string SerialNumber = 6;
|
||||
optional string HardwareVersion = 7;
|
||||
repeated uint32 ReplyData = 8;
|
||||
optional uint32 Unknown9 = 9;
|
||||
optional uint32 Unknown10 = 10;
|
||||
optional uint32 Unknown13 = 13;
|
||||
}
|
||||
|
||||
message SubContent {
|
||||
optional DeviceInformationContent Content = 1;
|
||||
optional uint32 Unknown2 = 2;
|
||||
optional uint32 Unknown4 = 4;
|
||||
optional uint32 Unknown5 = 5;
|
||||
optional uint32 Unknown6 = 6;
|
||||
}
|
||||
|
||||
message DeviceInformation {
|
||||
optional uint32 InformationId = 1;
|
||||
optional SubContent SubContent = 2;
|
||||
}
|
||||
1
src/build-qrc-qml.sh
Executable file
1
src/build-qrc-qml.sh
Executable file
@@ -0,0 +1 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/bin/rcc qml.qrc -o ../build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qrc_qml.cpp
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "characteristicwriteprocessor.h"
|
||||
#include <QSettings>
|
||||
|
||||
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
|
||||
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, int8_t bikeResistanceOffset,
|
||||
bluetoothdevice *bike, QObject *parent)
|
||||
: QObject(parent), bikeResistanceOffset(bikeResistanceOffset), bikeResistanceGain(bikeResistanceGain), Bike(bike) {}
|
||||
|
||||
@@ -25,6 +25,8 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
|
||||
double CRRGain = settings.value(QZSettings::CRRGain, QZSettings::default_CRRGain).toDouble();
|
||||
double CWGain = settings.value(QZSettings::CWGain, QZSettings::default_CWGain).toDouble();
|
||||
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
|
||||
double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble();
|
||||
|
||||
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
|
||||
QStringLiteral(" enabled ") + force_resistance;
|
||||
@@ -38,6 +40,11 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
percentage = (((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * 2.0) * gain) + offset;
|
||||
}
|
||||
|
||||
if(min_inclination > grade) {
|
||||
grade = min_inclination;
|
||||
qDebug() << "grade override due to min_inclination " << min_inclination;
|
||||
}
|
||||
|
||||
/*
|
||||
Surface Road Crr MTB Crr Gravel Crr (Namebrand) Zwift Gravel Crr
|
||||
Pavement .004 .01 .008 .008
|
||||
@@ -60,9 +67,13 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
|
||||
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
|
||||
// form Zwift
|
||||
if (!((bike *)Bike)->inclinationAvailableByHardware())
|
||||
Bike->setInclination(grade + CRR_offset + CW_offset);
|
||||
// from Zwift
|
||||
if (!((bike *)Bike)->inclinationAvailableByHardware()) {
|
||||
if(zwift_play_emulator)
|
||||
Bike->setInclination(grade);
|
||||
else
|
||||
Bike->setInclination(grade + CRR_offset + CW_offset);
|
||||
}
|
||||
|
||||
emit changeInclination(grade, percentage);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
197
src/devices/antbike/antbike.cpp
Normal file
197
src/devices/antbike/antbike.cpp
Normal 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; }
|
||||
81
src/devices/antbike/antbike.h
Normal file
81
src/devices/antbike/antbike.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include "devices/bike.h"
|
||||
#include "qdebugfixup.h"
|
||||
#include "homeform.h"
|
||||
#include <QSettings>
|
||||
|
||||
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
|
||||
@@ -14,6 +15,8 @@ void bike::changeResistance(resistance_t resistance) {
|
||||
double zwift_erg_resistance_down =
|
||||
settings.value(QZSettings::zwift_erg_resistance_down, QZSettings::default_zwift_erg_resistance_down).toDouble();
|
||||
|
||||
qDebug() << QStringLiteral("bike::changeResistance") << autoResistanceEnable << resistance;
|
||||
|
||||
lastRawRequestedResistanceValue = resistance;
|
||||
if (autoResistanceEnable) {
|
||||
double v = (resistance * m_difficult) + gears();
|
||||
@@ -81,12 +84,63 @@ void bike::changePower(int32_t power) {
|
||||
}
|
||||
}
|
||||
|
||||
double bike::gears() { return m_gears; }
|
||||
double bike::gears() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
|
||||
if(gears_zwift_ratio) {
|
||||
if(m_gears < 1)
|
||||
return 1.0;
|
||||
else if(m_gears > 24)
|
||||
return 24.0;
|
||||
}
|
||||
return m_gears + gears_offset;
|
||||
}
|
||||
|
||||
void bike::setGears(double gears) {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
|
||||
gears -= gears_offset;
|
||||
qDebug() << "setGears" << gears;
|
||||
|
||||
// Check for boundaries and emit failure signals
|
||||
if(gears_zwift_ratio && (gears > 24 || gears < 1)) {
|
||||
qDebug() << "new gear value ignored because of gears_zwift_ratio setting!";
|
||||
if(gears > 24) {
|
||||
emit gearFailedUp();
|
||||
} else {
|
||||
emit gearFailedDown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(gears > maxGears()) {
|
||||
qDebug() << "new gear value ignored because of maxGears" << maxGears();
|
||||
emit gearFailedUp();
|
||||
return;
|
||||
}
|
||||
|
||||
if(gears < minGears()) {
|
||||
qDebug() << "new gear value ignored because of minGears" << minGears();
|
||||
emit gearFailedDown();
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_gears > gears) {
|
||||
emit gearOkDown();
|
||||
} else {
|
||||
emit gearOkUp();
|
||||
}
|
||||
|
||||
m_gears = gears;
|
||||
settings.setValue(QZSettings::gears_current_value, m_gears);
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->updateGearsValue();
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool())
|
||||
settings.setValue(QZSettings::gears_current_value, m_gears);
|
||||
|
||||
if (lastRawRequestedResistanceValue != -1) {
|
||||
changeResistance(lastRawRequestedResistanceValue);
|
||||
}
|
||||
@@ -306,3 +360,61 @@ uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
|
||||
}
|
||||
return watt;
|
||||
}
|
||||
|
||||
double bike::gearsZwiftRatio() {
|
||||
if(m_gears <= 0)
|
||||
return 0.65;
|
||||
else if(m_gears > 24)
|
||||
return 6;
|
||||
switch((int)m_gears) {
|
||||
case 1:
|
||||
return 0.75;
|
||||
case 2:
|
||||
return 0.87;
|
||||
case 3:
|
||||
return 0.99;
|
||||
case 4:
|
||||
return 1.11;
|
||||
case 5:
|
||||
return 1.23;
|
||||
case 6:
|
||||
return 1.38;
|
||||
case 7:
|
||||
return 1.53;
|
||||
case 8:
|
||||
return 1.68;
|
||||
case 9:
|
||||
return 1.86;
|
||||
case 10:
|
||||
return 2.04;
|
||||
case 11:
|
||||
return 2.22;
|
||||
case 12:
|
||||
return 2.40;
|
||||
case 13:
|
||||
return 2.61;
|
||||
case 14:
|
||||
return 2.82;
|
||||
case 15:
|
||||
return 3.03;
|
||||
case 16:
|
||||
return 3.24;
|
||||
case 17:
|
||||
return 3.49;
|
||||
case 18:
|
||||
return 3.74;
|
||||
case 19:
|
||||
return 3.99;
|
||||
case 20:
|
||||
return 4.24;
|
||||
case 21:
|
||||
return 4.54;
|
||||
case 22:
|
||||
return 4.84;
|
||||
case 23:
|
||||
return 5.14;
|
||||
case 24:
|
||||
return 5.49;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ class bike : public bluetoothdevice {
|
||||
double currentCrankRevolutions() override;
|
||||
uint16_t lastCrankEventTime() override;
|
||||
bool connected() override;
|
||||
double defaultMaxGears() { return 9999.0; }
|
||||
virtual double maxGears() { return defaultMaxGears(); }
|
||||
virtual double minGears() { return -9999.0; }
|
||||
virtual uint16_t watts();
|
||||
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
|
||||
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
|
||||
@@ -36,8 +39,10 @@ class bike : public bluetoothdevice {
|
||||
uint8_t metrics_override_heartrate() override;
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
double gearsZwiftRatio();
|
||||
void setSpeedLimit(double speed) { m_speedLimit = speed; }
|
||||
double speedLimit() { return m_speedLimit; }
|
||||
virtual bool ifitCompatible() {return false;}
|
||||
|
||||
/**
|
||||
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
|
||||
@@ -58,26 +63,36 @@ class bike : public bluetoothdevice {
|
||||
void changeInclination(double grade, double percentage) override;
|
||||
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
|
||||
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
|
||||
void gearUp() {QSettings settings; setGears(gears() +
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
|
||||
void gearDown() {QSettings settings; setGears(gears() -
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
|
||||
void gearUp() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
setGears(gears() + (gears_zwift_ratio ? 1 :
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
|
||||
}
|
||||
void gearDown() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
setGears(gears() - (gears_zwift_ratio ? 1 :
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void bikeStarted();
|
||||
void resistanceChanged(resistance_t resistance);
|
||||
void resistanceRead(resistance_t resistance);
|
||||
void steeringAngleChanged(double angle);
|
||||
void gearOkUp(); // Signal when gear up succeeds
|
||||
void gearOkDown(); // Signal when gear down succeeds
|
||||
void gearFailedUp(); // Signal when gear up hits max
|
||||
void gearFailedDown(); // Signal when gear down hits min
|
||||
|
||||
protected:
|
||||
metric RequestedResistance;
|
||||
metric RequestedPelotonResistance;
|
||||
metric RequestedCadence;
|
||||
metric RequestedPower;
|
||||
|
||||
resistance_t requestResistance = -1;
|
||||
double requestInclination = -100;
|
||||
int16_t requestPower = -1;
|
||||
|
||||
bool ergModeSupported = false; // if a bike has this mode supported, when from the virtual bike there is a power
|
||||
// request there is no need to translate in resistance levels
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
#include "qzsettings.h"
|
||||
|
||||
#include "devices/activiotreadmill/activiotreadmill.h"
|
||||
#include "devices/antbike/antbike.h"
|
||||
#include "devices/apexbike/apexbike.h"
|
||||
#include "devices/bhfitnesselliptical/bhfitnesselliptical.h"
|
||||
#include "devices/bkoolbike/bkoolbike.h"
|
||||
@@ -31,10 +32,13 @@
|
||||
#ifndef Q_OS_IOS
|
||||
#include "devices/computrainerbike/computrainerbike.h"
|
||||
#include "devices/csaferower/csaferower.h"
|
||||
#include "devices/csafeelliptical/csafeelliptical.h"
|
||||
#endif
|
||||
#include "devices/concept2skierg/concept2skierg.h"
|
||||
#include "devices/crossrope/crossrope.h"
|
||||
#include "devices/cscbike/cscbike.h"
|
||||
#include "devices/cycleopsphantombike/cycleopsphantombike.h"
|
||||
#include "devices/deeruntreadmill/deerruntreadmill.h"
|
||||
#include "devices/domyosbike/domyosbike.h"
|
||||
#include "devices/domyoselliptical/domyoselliptical.h"
|
||||
#include "devices/domyosrower/domyosrower.h"
|
||||
@@ -65,9 +69,11 @@
|
||||
#include "devices/iconceptelliptical/iconceptelliptical.h"
|
||||
#include "devices/inspirebike/inspirebike.h"
|
||||
#include "devices/keepbike/keepbike.h"
|
||||
#include "devices/kineticinroadbike/kineticinroadbike.h"
|
||||
#include "devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h"
|
||||
#include "devices/kingsmithr2treadmill/kingsmithr2treadmill.h"
|
||||
#include "devices/lifefitnesstreadmill/lifefitnesstreadmill.h"
|
||||
#include "devices/lifespantreadmill/lifespantreadmill.h"
|
||||
#include "devices/m3ibike/m3ibike.h"
|
||||
#include "devices/mcfbike/mcfbike.h"
|
||||
#include "devices/mepanelbike/mepanelbike.h"
|
||||
@@ -76,6 +82,7 @@
|
||||
#include "devices/nautilustreadmill/nautilustreadmill.h"
|
||||
#include "devices/nordictrackelliptical/nordictrackelliptical.h"
|
||||
#include "devices/nordictrackifitadbbike/nordictrackifitadbbike.h"
|
||||
#include "devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h"
|
||||
#include "devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h"
|
||||
#include "devices/npecablebike/npecablebike.h"
|
||||
#include "devices/octaneelliptical/octaneelliptical.h"
|
||||
@@ -83,6 +90,7 @@
|
||||
#include "devices/pafersbike/pafersbike.h"
|
||||
#include "devices/paferstreadmill/paferstreadmill.h"
|
||||
#include "devices/pelotonbike/pelotonbike.h"
|
||||
#include "devices/pitpatbike/pitpatbike.h"
|
||||
#include "devices/proformbike/proformbike.h"
|
||||
#include "devices/proformelliptical/proformelliptical.h"
|
||||
#include "devices/proformellipticaltrainer/proformellipticaltrainer.h"
|
||||
@@ -107,7 +115,10 @@
|
||||
|
||||
#include "devices/spirittreadmill/spirittreadmill.h"
|
||||
#include "devices/sportsplusbike/sportsplusbike.h"
|
||||
#include "devices/sportsplusrower/sportsplusrower.h"
|
||||
#include "devices/sportstechbike/sportstechbike.h"
|
||||
#include "devices/sportstechelliptical/sportstechelliptical.h"
|
||||
#include "devices/sramAXSController/sramAXSController.h"
|
||||
#include "devices/stagesbike/stagesbike.h"
|
||||
|
||||
#include "devices/renphobike/renphobike.h"
|
||||
@@ -118,11 +129,13 @@
|
||||
#include "devices/echelonstride/echelonstride.h"
|
||||
|
||||
#include "templateinfosenderbuilder.h"
|
||||
#include "technogymbike/technogymbike.h"
|
||||
#include "devices/toorxtreadmill/toorxtreadmill.h"
|
||||
#include "devices/treadmill.h"
|
||||
#include "devices/truetreadmill/truetreadmill.h"
|
||||
#include "devices/trxappgateusbbike/trxappgateusbbike.h"
|
||||
#include "devices/trxappgateusbelliptical/trxappgateusbelliptical.h"
|
||||
#include "devices/trxappgateusbrower/trxappgateusbrower.h"
|
||||
#include "devices/trxappgateusbtreadmill/trxappgateusbtreadmill.h"
|
||||
#include "devices/ultrasportbike/ultrasportbike.h"
|
||||
#include "devices/wahookickrheadwind/wahookickrheadwind.h"
|
||||
@@ -145,7 +158,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bluetooth(const discoveryoptions &options);
|
||||
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
|
||||
bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false,
|
||||
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
|
||||
bool testResistance = false, int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
|
||||
bool startDiscovery = true);
|
||||
~bluetooth();
|
||||
bluetoothdevice *device();
|
||||
@@ -159,6 +172,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool useDiscovery = false;
|
||||
QFile *debugCommsLog = nullptr;
|
||||
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
|
||||
antbike *antBike = nullptr;
|
||||
apexbike *apexBike = nullptr;
|
||||
bkoolbike *bkoolBike = nullptr;
|
||||
bhfitnesselliptical *bhFitnessElliptical = nullptr;
|
||||
@@ -170,8 +184,11 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
#ifndef Q_OS_IOS
|
||||
computrainerbike *computrainerBike = nullptr;
|
||||
csaferower *csafeRower = nullptr;
|
||||
csafeelliptical *csafeElliptical = nullptr;
|
||||
#endif
|
||||
concept2skierg *concept2Skierg = nullptr;
|
||||
cycleopsphantombike *cycleopsphantomBike = nullptr;
|
||||
deerruntreadmill *deerrunTreadmill = nullptr;
|
||||
domyostreadmill *domyos = nullptr;
|
||||
domyosbike *domyosBike = nullptr;
|
||||
domyosrower *domyosRower = nullptr;
|
||||
@@ -186,6 +203,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
nautiluselliptical *nautilusElliptical = nullptr;
|
||||
nautilustreadmill *nautilusTreadmill = nullptr;
|
||||
trxappgateusbbike *trxappgateusbBike = nullptr;
|
||||
trxappgateusbrower *trxappgateusbRower = nullptr;
|
||||
trxappgateusbelliptical *trxappgateusbElliptical = nullptr;
|
||||
echelonconnectsport *echelonConnectSport = nullptr;
|
||||
yesoulbike *yesoulBike = nullptr;
|
||||
@@ -193,6 +211,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
nordictrackelliptical *nordictrackElliptical = nullptr;
|
||||
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
|
||||
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
|
||||
nordictrackifitadbelliptical *nordictrackifitadbElliptical = nullptr;
|
||||
octaneelliptical *octaneElliptical = nullptr;
|
||||
octanetreadmill *octaneTreadmill = nullptr;
|
||||
pelotonbike *pelotonBike = nullptr;
|
||||
@@ -212,8 +231,11 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
truetreadmill *trueTreadmill = nullptr;
|
||||
horizongr7bike *horizonGr7Bike = nullptr;
|
||||
schwinnic4bike *schwinnIC4Bike = nullptr;
|
||||
technogymbike* technogymBike = nullptr;
|
||||
sportstechbike *sportsTechBike = nullptr;
|
||||
sportstechelliptical *sportsTechElliptical = nullptr;
|
||||
sportsplusbike *sportsPlusBike = nullptr;
|
||||
sportsplusrower *sportsPlusRower = nullptr;
|
||||
inspirebike *inspireBike = nullptr;
|
||||
snodebike *snodeBike = nullptr;
|
||||
eslinkertreadmill *eslinkerTreadmill = nullptr;
|
||||
@@ -235,6 +257,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
smartrowrower *smartrowRower = nullptr;
|
||||
echelonstride *echelonStride = nullptr;
|
||||
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;
|
||||
lifespantreadmill *lifespanTreadmill = nullptr;
|
||||
keepbike *keepBike = nullptr;
|
||||
kingsmithr1protreadmill *kingsmithR1ProTreadmill = nullptr;
|
||||
kingsmithr2treadmill *kingsmithR2Treadmill = nullptr;
|
||||
@@ -242,6 +265,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
pafersbike *pafersBike = nullptr;
|
||||
paferstreadmill *pafersTreadmill = nullptr;
|
||||
tacxneo2 *tacxneo2Bike = nullptr;
|
||||
pitpatbike *pitpatBike = nullptr;
|
||||
renphobike *renphoBike = nullptr;
|
||||
shuaa5treadmill *shuaA5Treadmill = nullptr;
|
||||
heartratebelt *heartRateBelt = nullptr;
|
||||
@@ -254,6 +278,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
|
||||
ypooelliptical *ypooElliptical = nullptr;
|
||||
ziprotreadmill *ziproTreadmill = nullptr;
|
||||
kineticinroadbike *kineticInroadBike = nullptr;
|
||||
strydrunpowersensor *powerTreadmill = nullptr;
|
||||
eliterizer *eliteRizer = nullptr;
|
||||
elitesterzosmart *eliteSterzoSmart = nullptr;
|
||||
@@ -266,6 +291,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
QList<eliteariafan *> eliteAriaFan;
|
||||
QList<zwiftclickremote* > zwiftPlayDevice;
|
||||
zwiftclickremote* zwiftClickRemote = nullptr;
|
||||
sramaxscontroller* sramAXSController = nullptr;
|
||||
QString filterDevice = QLatin1String("");
|
||||
|
||||
bool testResistance = false;
|
||||
@@ -274,7 +300,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool noConsole = false;
|
||||
bool logs = true;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool forceHeartBeltOffForTimeout = false;
|
||||
|
||||
@@ -299,6 +325,8 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool eliteRizerAvaiable();
|
||||
bool eliteSterzoSmartAvaiable();
|
||||
bool fitmetriaFanfitAvaiable();
|
||||
bool zwiftDeviceAvaiable();
|
||||
bool sramDeviceAvaiable();
|
||||
bool fitmetria_fanfit_isconnected(QString name);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -337,6 +365,10 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
void speedChanged(double);
|
||||
void inclinationChanged(double, double);
|
||||
void connectedAndDiscovered();
|
||||
void gearDown();
|
||||
void gearUp();
|
||||
void gearFailedDown();
|
||||
void gearFailedUp();
|
||||
|
||||
signals:
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ bluetoothdevice::~bluetoothdevice() {
|
||||
}
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
|
||||
void bluetoothdevice::start() { requestStart = 1; }
|
||||
void bluetoothdevice::start() { requestStart = 1; lastStart = QDateTime::currentMSecsSinceEpoch(); }
|
||||
void bluetoothdevice::stop(bool pause) {
|
||||
requestStop = 1;
|
||||
if (pause)
|
||||
@@ -169,7 +169,7 @@ void bluetoothdevice::setVirtualDevice(virtualdevice *virtualDevice, VIRTUAL_DEV
|
||||
}
|
||||
|
||||
// keiser m3i has a separate management of this, so please check it
|
||||
void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
|
||||
void bluetoothdevice::update_metrics(bool watt_calc, const double watts, const bool from_accessory) {
|
||||
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
double deltaTime = (((double)_lastTimeUpdate.msecsTo(current)) / ((double)1000.0));
|
||||
@@ -181,15 +181,15 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
|
||||
bool power_as_treadmill =
|
||||
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
|
||||
|
||||
if(deviceType() == bluetoothdevice::BIKE)
|
||||
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
|
||||
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")) == false &&
|
||||
!power_as_bike && !power_as_treadmill)
|
||||
watt_calc = false;
|
||||
|
||||
if(deviceType() == bluetoothdevice::BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
|
||||
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
|
||||
|
||||
if (!_firstUpdate && !paused) {
|
||||
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {
|
||||
|
||||
|
||||
@@ -216,6 +216,18 @@ class bluetoothdevice : public QObject {
|
||||
*/
|
||||
metric wattsMetric();
|
||||
|
||||
/**
|
||||
* @brief wattsMetricforUi Show the wattage applying averaging in case the user requested this. Units: watts
|
||||
*/
|
||||
double wattsMetricforUI() {
|
||||
QSettings settings;
|
||||
bool power5s = settings.value(QZSettings::power_avg_5s, QZSettings::default_power_avg_5s).toBool();
|
||||
if (power5s)
|
||||
return wattsMetric().average5s();
|
||||
else
|
||||
return wattsMetric().value();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief changeFanSpeed Tries to change the fan speed.
|
||||
* @param speed The requested fan speed. Units: depends on device
|
||||
@@ -401,7 +413,7 @@ class bluetoothdevice : public QObject {
|
||||
*/
|
||||
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
|
||||
|
||||
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL };
|
||||
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE };
|
||||
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
|
||||
|
||||
/**
|
||||
@@ -531,6 +543,12 @@ class bluetoothdevice : public QObject {
|
||||
int8_t requestDecreaseFan = -1;
|
||||
double requestFanSpeed = -1;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
metric RequestedPower;
|
||||
int16_t requestPower = -1;
|
||||
|
||||
/**
|
||||
* @brief m_difficult The current difficulty gain. Units: device dependent
|
||||
*/
|
||||
@@ -692,7 +710,7 @@ class bluetoothdevice : public QObject {
|
||||
* @param watt_calc ??
|
||||
* @param watts ?. Unit: watts
|
||||
*/
|
||||
void update_metrics(bool watt_calc, const double watts);
|
||||
void update_metrics(bool watt_calc, const double watts, const bool from_accessory = false);
|
||||
|
||||
/**
|
||||
* @brief update_hr_from_external Updates heart rate from Garmin Companion App or Apple Watch
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -28,12 +28,6 @@ crossrope::crossrope(uint32_t pollDeviceTime, bool noConsole, bool noHeartServic
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
if (forceInitSpeed > 0)
|
||||
lastSpeed = forceInitSpeed;
|
||||
|
||||
if (forceInitInclination > 0)
|
||||
lastInclination = forceInitInclination;
|
||||
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
connect(refresh, &QTimer::timeout, this, &crossrope::update);
|
||||
@@ -82,17 +76,17 @@ void crossrope::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
|
||||
void crossrope::updateDisplay(uint16_t elapsed) {}
|
||||
|
||||
void crossrope::update() {
|
||||
if (m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
if (m_control == nullptr || m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << m_control->state() << bluetoothDevice.isValid() << gattCommunicationChannelService
|
||||
<< gattWriteCharacteristic.isValid() << initDone << requestSpeed << requestInclination;
|
||||
<< gattWriteCharacteristic.isValid() << initDone;
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit((lastSpeed > 0 ? true : false));
|
||||
btinit((false));
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() && initDone) {
|
||||
QSettings settings;
|
||||
@@ -107,11 +101,7 @@ void crossrope::update() {
|
||||
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
if (lastSpeed == 0.0) {
|
||||
lastSpeed = 0.5;
|
||||
}
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
@@ -133,6 +123,8 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
Q_UNUSED(characteristic);
|
||||
QByteArray value = newValue;
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + QString::number(value.length()) + QStringLiteral(" ") + value.toHex(' '));
|
||||
|
||||
@@ -142,11 +134,29 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
return;
|
||||
|
||||
double steps = (double)(uint16_t)((newValue.at(1) << 8) | ((uint8_t)newValue.at(2)));
|
||||
if(steps != StepCount.value()) {
|
||||
Cadence = (steps - StepCount.value()) / fabs(StepCount.valueChanged().msecsTo(QDateTime::currentDateTime())) * 60000.0;
|
||||
StepCount = steps;
|
||||
if(steps != JumpsCount.value()) {
|
||||
CadenceRaw = (steps - JumpsCount.value()) / fabs(JumpsCount.valueChanged().msecsTo(now)) * 60000.0;
|
||||
JumpsSequence = JumpsSequence.value() + 1;
|
||||
Cadence = CadenceRaw.average20s();
|
||||
JumpsCount = steps;
|
||||
Speed = Cadence.value() * 0.15; // (speed emulated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastTimeCharacteristicChanged.msecsTo(now)));
|
||||
} else if(abs(Cadence.lastChanged().secsTo(now)) > 2) {
|
||||
CadenceRaw = 0;
|
||||
Cadence = 0;
|
||||
JumpsSequence = 0;
|
||||
Speed = 0;
|
||||
}
|
||||
Speed = (newValue.at(5) << 8) | ((uint8_t)newValue.at(6));
|
||||
|
||||
if (watts(weight))
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(weight)) + 1.19) *
|
||||
weight * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -160,7 +170,7 @@ void crossrope::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
}
|
||||
}
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
emit debug(QStringLiteral("Current Step Count: ") + QString::number(StepCount.value()));
|
||||
emit debug(QStringLiteral("Current Jumps Count: ") + QString::number(JumpsCount.value()));
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
// debug("Current Distance: " + QString::number(distance));
|
||||
|
||||
@@ -306,3 +316,12 @@ bool crossrope::connected() {
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
uint16_t crossrope::watts(double weight) {
|
||||
QSettings settings;
|
||||
double power_per_kg = 1.5;
|
||||
double cadence_efficiency = 0.1;
|
||||
|
||||
// Power calculation
|
||||
double power = (power_per_kg * weight) + (cadence_efficiency * Cadence.value());
|
||||
return power;
|
||||
}
|
||||
|
||||
@@ -26,18 +26,17 @@
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
#include "treadmill.h"
|
||||
#include "jumprope.h"
|
||||
|
||||
class crossrope : public treadmill {
|
||||
class crossrope : public jumprope {
|
||||
Q_OBJECT
|
||||
public:
|
||||
crossrope(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
|
||||
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
|
||||
bool connected() override;
|
||||
bool canHandleSpeedChange() override { return false; }
|
||||
bool canHandleInclineChange() override { return false; }
|
||||
|
||||
private:
|
||||
uint16_t watts(double weight) override;
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit(bool startTape);
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
@@ -52,9 +51,6 @@ class crossrope : public treadmill {
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
@@ -64,6 +60,8 @@ class crossrope : public treadmill {
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
metric CadenceRaw;
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
@@ -64,8 +64,24 @@ csafe::csafe() {
|
||||
cmds["CSAFE_PM_GET_WORKTIME"] = populateCmd(0xa0, QList<int>(), 0x1a);
|
||||
cmds["CSAFE_PM_GET_WORKDISTANCE"] = populateCmd(0xa3, QList<int>(), 0x1a);
|
||||
|
||||
// LIFE FITNESS specific commands
|
||||
cmds["CSAFE_LF_GET_DETAIL"] = populateCmd(0xd0, QList<int>());
|
||||
|
||||
// Generic long commands
|
||||
cmds["CSAFE_SETPROGRAM_CMD"] = populateCmd(0x24, QList<int>() << 1 << 1);
|
||||
cmds["CSAFE_SETUSERINFO_CMD"] = populateCmd(0x2B, QList<int>() << 2 << 1 << 1 << 1);
|
||||
cmds["CSAFE_SETLEVEL_CMD"] = populateCmd(0x2D, QList<int>() << 1);
|
||||
|
||||
// Generic Short Commands
|
||||
cmds["CSAFE_GETSTATUS_CMD"] = populateCmd(0x80, QList<int>());
|
||||
cmds["CSAFE_GOINUSE_CMD"] = populateCmd(0x85, QList<int>());
|
||||
cmds["CSAFE_GETCALORIES_CMD"] = populateCmd(0xa3, QList<int>());
|
||||
cmds["CSAFE_GETPROGRAM_CMD"] = populateCmd(0xA4, QList<int>());
|
||||
cmds["CSAFE_GETPACE_CMD"] = populateCmd(0xa6, QList<int>());
|
||||
cmds["CSAFE_GETCADENCE_CMD"] = populateCmd(0xa7, QList<int>());
|
||||
cmds["CSAFE_GETHORIZONTAL_CMD"] = populateCmd(0xA1, QList<int>());
|
||||
cmds["CSAFE_GETSPEED_CMD"] = populateCmd(0xA5, QList<int>());
|
||||
cmds["CSAFE_GETUSERINFO_CMD"] = populateCmd(0xAB, QList<int>());
|
||||
cmds["CSAFE_GETHRCUR_CMD"] = populateCmd(0xb0, QList<int>());
|
||||
cmds["CSAFE_GETPOWER_CMD"] = populateCmd(0xb4, QList<int>());
|
||||
|
||||
@@ -87,7 +103,8 @@ csafe::csafe() {
|
||||
resp[0xA0] = qMakePair(QString("CSAFE_GETTWORK_CMD"), QList<int>() << 1 << 1 << 1);
|
||||
resp[0xA1] = qMakePair(QString("CSAFE_GETHORIZONTAL_CMD"), QList<int>() << 2 << 1);
|
||||
resp[0xA3] = qMakePair(QString("CSAFE_GETCALORIES_CMD"), QList<int>() << 2);
|
||||
resp[0xA4] = qMakePair(QString("CSAFE_GETPROGRAM_CMD"), QList<int>() << 1);
|
||||
resp[0xA4] = qMakePair(QString("CSAFE_GETPROGRAM_CMD"), QList<int>() << 1 << 1);
|
||||
resp[0xA5] = qMakePair(QString("CSAFE_GETSPEED_CMD"), QList<int>() << 2 << 1);
|
||||
resp[0xA6] = qMakePair(QString("CSAFE_GETPACE_CMD"), QList<int>() << 2 << 1);
|
||||
resp[0xA7] = qMakePair(QString("CSAFE_GETCADENCE_CMD"), QList<int>() << 2 << 1);
|
||||
resp[0xAB] = qMakePair(QString("CSAFE_GETUSERINFO_CMD"), QList<int>() << 2 << 1 << 1 << 1);
|
||||
@@ -105,6 +122,8 @@ csafe::csafe() {
|
||||
resp[0x21] = qMakePair(QString("CSAFE_SETHORIZONTAL_CMD"), QList<int>() << 0);
|
||||
resp[0x23] = qMakePair(QString("CSAFE_SETCALORIES_CMD"), QList<int>() << 0);
|
||||
resp[0x24] = qMakePair(QString("CSAFE_SETPROGRAM_CMD"), QList<int>() << 0);
|
||||
resp[0x2B] = qMakePair(QString("CSAFE_SETUSERINFO_CMD"), QList<int>() << 0);
|
||||
resp[0x2D] = qMakePair(QString("CSAFE_SETLEVEL_CMD"), QList<int>() << 0);
|
||||
resp[0x34] = qMakePair(QString("CSAFE_SETPOWER_CMD"), QList<int>() << 0);
|
||||
resp[0x70] = qMakePair(QString("CSAFE_GETCAPS_CMD"), QList<int>() << 11);
|
||||
|
||||
@@ -129,6 +148,9 @@ csafe::csafe() {
|
||||
resp[0x1A6C] =
|
||||
qMakePair(QString("CSAFE_PM_GET_HEARTBEATDATA"),
|
||||
QList<int>() << 1 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2);
|
||||
|
||||
// LIFE FITNESS specific response
|
||||
resp[0xD0] = qMakePair(QString("CSAFE_LF_GET_DETAIL"), QList<int>() << 0x1c);
|
||||
}
|
||||
|
||||
QList<QList<int>> csafe::populateCmd(int First, QList<int> Second, int Third) {
|
||||
@@ -140,7 +162,9 @@ QList<QList<int>> csafe::populateCmd(int First, QList<int> Second, int Third) {
|
||||
second.clear();
|
||||
third.clear();
|
||||
first.append(First);
|
||||
foreach (int a, Second) { second.append(a); }
|
||||
foreach (int a, Second) {
|
||||
second.append(a);
|
||||
}
|
||||
third.append(Third);
|
||||
ret.append(first);
|
||||
ret.append(second);
|
||||
@@ -155,7 +179,9 @@ QList<QList<int>> csafe::populateCmd(int First, QList<int> Second) {
|
||||
first.clear();
|
||||
second.clear();
|
||||
first.append(First);
|
||||
foreach (int a, Second) { second.append(a); }
|
||||
foreach (int a, Second) {
|
||||
second.append(a);
|
||||
}
|
||||
ret.append(first);
|
||||
ret.append(second);
|
||||
return ret;
|
||||
@@ -195,7 +221,7 @@ QString csafe::bytes2ascii(const QVector<quint8> &raw_bytes) {
|
||||
return word;
|
||||
}
|
||||
|
||||
QByteArray csafe::write(const QStringList &arguments) {
|
||||
QByteArray csafe::write(const QStringList &arguments, bool surround_msg) {
|
||||
int i = 0;
|
||||
QVector<quint8> message;
|
||||
int wrapper = 0;
|
||||
@@ -204,6 +230,10 @@ QByteArray csafe::write(const QStringList &arguments) {
|
||||
|
||||
while (i < arguments.size()) {
|
||||
QString arg = arguments[i];
|
||||
if (!cmds.contains(arg)) {
|
||||
qWarning("CSAFE Command not implemented: %s", qPrintable(arg));
|
||||
return QByteArray();
|
||||
}
|
||||
const auto &cmdprop = cmds[arg];
|
||||
QVector<quint8> command;
|
||||
|
||||
@@ -290,27 +320,31 @@ QByteArray csafe::write(const QStringList &arguments) {
|
||||
qWarning("Message is too long: %d", message.size());
|
||||
}
|
||||
|
||||
int maxmessage = qMax(message.size() + 1, maxresponse); // report IDs
|
||||
if (surround_msg) { // apply non-standard wrapping for PM3 rower
|
||||
int maxmessage = qMax(message.size() + 1, maxresponse); // report IDs
|
||||
|
||||
if (maxmessage <= 21) {
|
||||
message.prepend(0x01);
|
||||
message.append(QVector<quint8>(21 - message.size()));
|
||||
} else if (maxmessage <= 63) {
|
||||
message.prepend(0x04);
|
||||
message.append(QVector<quint8>(63 - message.size()));
|
||||
} else if ((message.size() + 1) <= 121) {
|
||||
message.prepend(0x02);
|
||||
message.append(QVector<quint8>(121 - message.size()));
|
||||
if (maxresponse > 121) {
|
||||
qWarning("Response may be too long to receive. Max possible length: %d", maxresponse);
|
||||
if (maxmessage <= 21) {
|
||||
message.prepend(0x01);
|
||||
message.append(QVector<quint8>(21 - message.size()));
|
||||
} else if (maxmessage <= 63) {
|
||||
message.prepend(0x04);
|
||||
message.append(QVector<quint8>(63 - message.size()));
|
||||
} else if ((message.size() + 1) <= 121) {
|
||||
message.prepend(0x02);
|
||||
message.append(QVector<quint8>(121 - message.size()));
|
||||
if (maxresponse > 121) {
|
||||
qWarning("Response may be too long to receive. Max possible length: %d", maxresponse);
|
||||
}
|
||||
} else {
|
||||
qWarning("Message too long. Message length: %d", message.size());
|
||||
message.clear();
|
||||
}
|
||||
} else {
|
||||
qWarning("Message too long. Message length: %d", message.size());
|
||||
message.clear();
|
||||
}
|
||||
|
||||
QByteArray ret;
|
||||
foreach (int a, message) { ret.append(a); }
|
||||
foreach (int a, message) {
|
||||
ret.append(a);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -325,9 +359,7 @@ QVector<quint8> csafe::check_message(QVector<quint8> message) {
|
||||
quint8 stuffvalue = message.takeAt(i + 1);
|
||||
message[i] = 0xF0 | stuffvalue;
|
||||
}
|
||||
|
||||
checksum ^= message[i]; // calculate checksum
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
@@ -345,15 +377,21 @@ QVector<quint8> csafe::check_message(QVector<quint8> message) {
|
||||
QVariantMap csafe::read(const QVector<quint8> &transmission) {
|
||||
QVector<quint8> message;
|
||||
bool stopfound = false;
|
||||
|
||||
int startflag = transmission[1];
|
||||
|
||||
int j = 0;
|
||||
if (startflag == Extended_Frame_Start_Flag) {
|
||||
j = 4;
|
||||
} else if (startflag == Standard_Frame_Start_Flag) {
|
||||
j = 2;
|
||||
} else {
|
||||
while (j < transmission.size()) {
|
||||
int startflag = transmission[j];
|
||||
if (startflag == Extended_Frame_Start_Flag) {
|
||||
j = j + 3;
|
||||
break;
|
||||
} else if (startflag == Standard_Frame_Start_Flag) {
|
||||
++j;
|
||||
break;
|
||||
} else {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
if (j >= transmission.size()) {
|
||||
qWarning("No Start Flag found.");
|
||||
return QVariantMap();
|
||||
}
|
||||
@@ -371,7 +409,6 @@ QVariantMap csafe::read(const QVector<quint8> &transmission) {
|
||||
qWarning("No Stop Flag found.");
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
message = check_message(message);
|
||||
int status = message.takeFirst();
|
||||
|
||||
@@ -34,7 +34,7 @@ class csafe {
|
||||
|
||||
public:
|
||||
csafe();
|
||||
QByteArray write(const QStringList &arguments);
|
||||
QByteArray write(const QStringList &arguments , bool surround_msg = false); //surround_msg is for wrapping the communication in CSAFE non-standard way for some devices like PM3
|
||||
QVector<quint8> check_message(QVector<quint8> message);
|
||||
QVariantMap read(const QVector<quint8> &transmission);
|
||||
};
|
||||
128
src/devices/csafe/csaferunner.cpp
Normal file
128
src/devices/csafe/csaferunner.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "csaferunner.h"
|
||||
|
||||
CsafeRunnerThread::CsafeRunnerThread() {}
|
||||
|
||||
CsafeRunnerThread::CsafeRunnerThread(QString deviceFileName, int sleepTime) {
|
||||
setDevice(deviceFileName);
|
||||
setSleepTime(sleepTime);
|
||||
}
|
||||
|
||||
void CsafeRunnerThread::setDevice(const QString &device) { deviceName = device; }
|
||||
|
||||
void CsafeRunnerThread::setBaudRate(uint32_t _baudRate) { baudRate = _baudRate; }
|
||||
|
||||
void CsafeRunnerThread::setSleepTime(int time) { sleepTime = time; }
|
||||
|
||||
void CsafeRunnerThread::addRefreshCommand(const QStringList &commands) {
|
||||
mutex.lock();
|
||||
refreshCommands.append(commands);
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void CsafeRunnerThread::sendCommand(const QStringList &commands) {
|
||||
mutex.lock();
|
||||
if (commandQueue.size() < MAX_QUEUE_SIZE) {
|
||||
commandQueue.enqueue(commands);
|
||||
mutex.unlock();
|
||||
} else {
|
||||
qDebug() << "CSAFE port commands QUEUE FULL. Dropping commands" << commands;
|
||||
}
|
||||
}
|
||||
|
||||
void CsafeRunnerThread::run() {
|
||||
|
||||
int rc = 0;
|
||||
|
||||
SerialHandler *serial = SerialHandler::create(deviceName, baudRate);
|
||||
serial->setEndChar(0xf2); // end of frame for CSAFE
|
||||
serial->setTimeout(1200); // CSAFE spec specifies 1s timeout
|
||||
|
||||
csafe *csafeInstance = new csafe();
|
||||
int connectioncounter = 20; // counts timeouts. If 10 timeouts in a row, then the port is closed and reopened
|
||||
int refresh_nr = -1;
|
||||
QStringList refreshCommand = {};
|
||||
|
||||
while (1) {
|
||||
|
||||
if (connectioncounter > 10 || !serial->isOpen()) {
|
||||
serial->closePort();
|
||||
rc = serial->openPort();
|
||||
if (rc != 0) {
|
||||
emit portAvailable(false);
|
||||
connectioncounter++;
|
||||
qDebug() << "Error opening serial port " << deviceName << "rc=" << rc << " sleeping for "
|
||||
<< "5s";
|
||||
QThread::msleep(5000);
|
||||
continue;
|
||||
} else {
|
||||
emit portAvailable(true);
|
||||
connectioncounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int elapsed = 0;
|
||||
while (elapsed < sleepTime || sleepTime == -1) {
|
||||
QThread::msleep(50);
|
||||
elapsed += 50;
|
||||
// TODO: does not seem to work with netsocket as intended. (no data available)
|
||||
// Needs further testing, maybe because the port is already closed and needs to remain open.
|
||||
// No issue for current implementations as they do not use unsolicited slave data / cmdAutoUpload .
|
||||
if (serial->dataAvailable() > 0 || !commandQueue.isEmpty()) {
|
||||
qDebug() << "CSAFE port data available. " << serial->dataAvailable() << " bytes"
|
||||
<< "commands in queue: " << commandQueue.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray ret;
|
||||
mutex.lock();
|
||||
if (!commandQueue.isEmpty()) {
|
||||
ret = csafeInstance->write(commandQueue.dequeue());
|
||||
qDebug() << "CSAFE port commands processed from queue. Remaining commands in queue: "
|
||||
<< commandQueue.size();
|
||||
} else {
|
||||
if (!(elapsed < sleepTime) || !refreshCommands.isEmpty()) {
|
||||
if (refreshCommands.length() > 0) {
|
||||
refresh_nr++;
|
||||
if (refresh_nr >= refreshCommands.length()) {
|
||||
refresh_nr = 0;
|
||||
}
|
||||
QStringList refreshCommand = refreshCommands[refresh_nr];
|
||||
ret = csafeInstance->write(refreshCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex.unlock();
|
||||
|
||||
if (!ret.isEmpty()) { // we have commands to send
|
||||
qDebug() << "CSAFE >> " << ret.toHex(' ');
|
||||
rc = serial->rawWrite((uint8_t *)ret.data(), ret.length());
|
||||
if (rc < 0) {
|
||||
qDebug() << "Error writing serial port " << deviceName << "rc=" << rc;
|
||||
connectioncounter++;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "CSAFE Slave unsolicited data present.";
|
||||
}
|
||||
|
||||
static uint8_t rx[120];
|
||||
rc = serial->rawRead(rx, 120, true);
|
||||
if (rc > 0) {
|
||||
qDebug() << "CSAFE << " << QByteArray::fromRawData((const char *)rx, rc).toHex(' ') << " (" << rc << ")";
|
||||
connectioncounter = 0;
|
||||
} else {
|
||||
qDebug() << "Error reading serial port " << deviceName << " rc=" << rc;
|
||||
connectioncounter++;
|
||||
continue;
|
||||
}
|
||||
|
||||
QVector<quint8> v;
|
||||
for (int i = 0; i < rc; i++)
|
||||
v.append(rx[i]);
|
||||
QVariantMap frame = csafeInstance->read(v);
|
||||
emit onCsafeFrame(frame);
|
||||
memset(rx, 0x00, sizeof(rx));
|
||||
}
|
||||
serial->closePort();
|
||||
}
|
||||
44
src/devices/csafe/csaferunner.h
Normal file
44
src/devices/csafe/csaferunner.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "csafe.h"
|
||||
#include "qzsettings.h"
|
||||
#include "serialhandler.h"
|
||||
#include <QDebug>
|
||||
#include <QMutex>
|
||||
#include <QQueue>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QVariantMap>
|
||||
#include <QVector>
|
||||
|
||||
#define MAX_QUEUE_SIZE 100
|
||||
/**
|
||||
* @brief The CsafeRunnerThread class is a thread that runs the CSAFE protocol interaction.
|
||||
* It periodically sends the refresh commands processes the responses.
|
||||
* It also allows sending additional commands to the device.
|
||||
*/
|
||||
class CsafeRunnerThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CsafeRunnerThread();
|
||||
explicit CsafeRunnerThread(QString deviceFileName, int sleepTime = 200);
|
||||
void setDevice(const QString &device);
|
||||
void setBaudRate(uint32_t baudRate = 9600);
|
||||
void setSleepTime(int time);
|
||||
void addRefreshCommand(const QStringList &commands);
|
||||
void run();
|
||||
|
||||
public slots:
|
||||
void sendCommand(const QStringList &commands);
|
||||
|
||||
signals:
|
||||
void onCsafeFrame(const QVariantMap &frame);
|
||||
void portAvailable(bool available);
|
||||
|
||||
private:
|
||||
QString deviceName;
|
||||
uint32_t baudRate = 9600;
|
||||
int sleepTime = 200;
|
||||
QList<QStringList> refreshCommands;
|
||||
QQueue<QStringList> commandQueue;
|
||||
QMutex mutex;
|
||||
};
|
||||
101
src/devices/csafe/csafeutility.cpp
Normal file
101
src/devices/csafe/csafeutility.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "csafeutility.h"
|
||||
|
||||
// Static data map initialization
|
||||
const QMap<int, QPair<QString, double>> CSafeUtility::unitData = {{1, {"mile", 1609.34}},
|
||||
{2, {"0.1 mile", 160.934}},
|
||||
{3, {"0.01 mile", 16.0934}},
|
||||
{4, {"0.001 mile", 1.60934}},
|
||||
{5, {"ft", 0.3048}},
|
||||
{6, {"inch", 0.0254}},
|
||||
{7, {"lbs.", 0.453592}},
|
||||
{8, {"0.1 lbs.", 0.0453592}},
|
||||
{10, {"10 ft", 3.048}},
|
||||
{16, {"mile/hour", 0.44704}},
|
||||
{17, {"0.1 mile/hour", 0.044704}},
|
||||
{18, {"0.01 mile/hour", 0.0044704}},
|
||||
{19, {"ft/minute", 0.00508}},
|
||||
{33, {"Km", 1000}},
|
||||
{34, {"0.1 km", 100}},
|
||||
{35, {"0.01 km", 10}},
|
||||
{36, {"Meter", 1}},
|
||||
{37, {"0.1 meter", 0.1}},
|
||||
{38, {"Cm", 0.01}},
|
||||
{39, {"Kg", 1}},
|
||||
{40, {"0.1 kg", 0.1}},
|
||||
{48, {"Km/hour", 0.277778}},
|
||||
{49, {"0.1 Km/hour", 0.0277778}},
|
||||
{50, {"0.01 Km/hour", 0.00277778}},
|
||||
{51, {"Meter/minute", 0.0166667}},
|
||||
{55, {"Minutes/mile", 1}},
|
||||
{56, {"Minutes/km", 1}},
|
||||
{57, {"Seconds/km", 1}},
|
||||
{58, {"Seconds/mile", 1}},
|
||||
{65, {"floors", 1}},
|
||||
{66, {"0.1 floors", 0.1}},
|
||||
{67, {"steps", 1}},
|
||||
{68, {"revolutions", 1}},
|
||||
{69, {"strides", 1}},
|
||||
{70, {"strokes", 1}},
|
||||
{71, {"beats", 1}},
|
||||
{72, {"calories", 1}},
|
||||
{73, {"Kp", 1}},
|
||||
{74, {"% grade", 1}},
|
||||
{75, {"0.01 % grade", 0.01}},
|
||||
{76, {"0.1 % grade", 0.1}},
|
||||
{79, {"0.1 floors/minute", 0.1}},
|
||||
{80, {"floors/minute", 1}},
|
||||
{81, {"steps/minute", 1}},
|
||||
{82, {"revs/minute", 1}},
|
||||
{83, {"strides/minute", 1}},
|
||||
{84, {"stokes/minute", 1}},
|
||||
{85, {"beats/minute", 1}},
|
||||
{86, {"calories/minute", 1}},
|
||||
{87, {"calories/hour", 1}},
|
||||
{88, {"Watts", 1}},
|
||||
{89, {"Kpm", 1}},
|
||||
{90, {"Inch-Lb", 1}},
|
||||
{91, {"Foot-Lb", 1}},
|
||||
{92, {"Newton-Meters", 1}},
|
||||
{97, {"Amperes", 1}},
|
||||
{98, {"0.001 Amperes", 0.001}},
|
||||
{99, {"Volts", 1}},
|
||||
{100, {"0.001 Volts", 0.001}}};
|
||||
|
||||
QString CSafeUtility::getUnitName(int unitCode) {
|
||||
if (unitData.contains(unitCode)) {
|
||||
return unitData[unitCode].first; // Return the description
|
||||
}
|
||||
return "Unknown unit";
|
||||
}
|
||||
|
||||
double CSafeUtility::convertToStandard(int unitCode, double value) {
|
||||
if (unitData.contains(unitCode)) {
|
||||
return value * unitData[unitCode].second; // Apply the conversion factor
|
||||
}
|
||||
return value; // Return the original value if no conversion factor is available
|
||||
}
|
||||
|
||||
QString CSafeUtility::statusByteToText(int status) {
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
return "Error";
|
||||
case 0x01:
|
||||
return "Ready";
|
||||
case 0x02:
|
||||
return "Idle";
|
||||
case 0x03:
|
||||
return "Have ID";
|
||||
case 0x05:
|
||||
return "In Use";
|
||||
case 0x06:
|
||||
return "Pause";
|
||||
case 0x07:
|
||||
return "Finish";
|
||||
case 0x08:
|
||||
return "Manual";
|
||||
case 0x09:
|
||||
return "Off line";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
44
src/devices/csafe/csafeutility.h
Normal file
44
src/devices/csafe/csafeutility.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* This emulates a serial port over a network connection.
|
||||
* e.g. as created by ser2net or hardware serial to ethernet converters
|
||||
*
|
||||
*/
|
||||
#ifndef CSAFEUTILITY_H
|
||||
#define CSAFEUTILITY_H
|
||||
|
||||
#include <cstdint> // For uint8_t
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
|
||||
/**
|
||||
* @brief This class contains some utility functions supporting the CSAFE protocol
|
||||
*/
|
||||
class CSafeUtility {
|
||||
public:
|
||||
static QString getUnitName(int unitCode);
|
||||
static double convertToStandard(int unitCode, double value);
|
||||
static QString statusByteToText(int statusByte);
|
||||
|
||||
private:
|
||||
// Static map to hold unit data
|
||||
static const QMap<int, QPair<QString, double>> unitData;
|
||||
};
|
||||
|
||||
#endif // CSAFEUTILITY_H
|
||||
28
src/devices/csafe/kalmanfilter.cpp
Normal file
28
src/devices/csafe/kalmanfilter.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "kalmanfilter.h"
|
||||
|
||||
KalmanFilter::KalmanFilter(double measure_error, double estimate_error, double process_noise_q, double initial_Value) {
|
||||
setMeasurementError(measure_error);
|
||||
setEstimateError(estimate_error);
|
||||
setProcessNoise(process_noise_q);
|
||||
estimate = initial_Value;
|
||||
}
|
||||
|
||||
KalmanFilter::~KalmanFilter() {};
|
||||
|
||||
double KalmanFilter::updateEstimate(double measurement) {
|
||||
estimate_error += process_noise_q;
|
||||
gain = estimate_error / (estimate_error + measure_error);
|
||||
estimate += gain * (measurement - estimate);
|
||||
estimate_error *= (1 - gain);
|
||||
return estimate;
|
||||
}
|
||||
|
||||
void KalmanFilter::setMeasurementError(double measure_err) { measure_error = measure_err; }
|
||||
|
||||
void KalmanFilter::setEstimateError(double estimate_err) { estimate_error = estimate_err; }
|
||||
|
||||
void KalmanFilter::setProcessNoise(double noise_q) { process_noise_q = noise_q; }
|
||||
|
||||
double KalmanFilter::getGain() { return gain; }
|
||||
|
||||
double KalmanFilter::getEstimateError() { return estimate_error; }
|
||||
52
src/devices/csafe/kalmanfilter.h
Normal file
52
src/devices/csafe/kalmanfilter.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* This emulates a serial port over a network connection.
|
||||
* e.g. as created by ser2net or hardware serial to ethernet converters
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef KALMAN_FILTER_H
|
||||
#define KALMAN_FILTER_H
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* @brief A simple Kalman filter for smoothing noisy data.
|
||||
*/
|
||||
class KalmanFilter {
|
||||
|
||||
public:
|
||||
KalmanFilter(double measure_error, double estimate_error, double process_noise_q = 0, double initial_Value = 0);
|
||||
~KalmanFilter();
|
||||
|
||||
double updateEstimate(double measurement);
|
||||
void setMeasurementError(double measure_err);
|
||||
void setEstimateError(double estimate_err);
|
||||
void setProcessNoise(double noise_q);
|
||||
double getGain();
|
||||
double getEstimateError();
|
||||
|
||||
private:
|
||||
double measure_error;
|
||||
double estimate_error;
|
||||
double process_noise_q;
|
||||
double estimate = 0;
|
||||
double gain = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
118
src/devices/csafe/netserial.cpp
Normal file
118
src/devices/csafe/netserial.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "netserial.h"
|
||||
|
||||
NetSerial::NetSerial(QString deviceFilename) : socket(new QTcpSocket()), _timeout(1000), endChar('\n') {
|
||||
setDevice(deviceFilename);
|
||||
}
|
||||
|
||||
NetSerial::~NetSerial() {
|
||||
closePort();
|
||||
delete socket;
|
||||
}
|
||||
|
||||
void NetSerial::setTimeout(int timeout) { this->_timeout = timeout; }
|
||||
|
||||
void NetSerial::setDevice(const QString &devname) {
|
||||
if (!devname.isEmpty()) {
|
||||
deviceFilename = devname;
|
||||
parseDeviceFilename(devname);
|
||||
}
|
||||
}
|
||||
|
||||
void NetSerial::setEndChar(uint8_t endChar) { this->endChar = endChar; }
|
||||
|
||||
bool NetSerial::isOpen() const { return socket->state() == QAbstractSocket::ConnectedState; }
|
||||
|
||||
int NetSerial::openPort() {
|
||||
if (serverAddress.isEmpty() || serverPort == 0) {
|
||||
qDebug() << "Invalid server address or port";
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket->connectToHost(serverAddress, serverPort);
|
||||
if (!socket->waitForConnected(_timeout)) {
|
||||
qDebug() << "Failed to connect to server:" << socket->errorString();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NetSerial::closePort() {
|
||||
if (isOpen()) {
|
||||
socket->disconnectFromHost();
|
||||
if (socket->state() != QAbstractSocket::UnconnectedState) {
|
||||
socket->waitForDisconnected(_timeout);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NetSerial::dataAvailable() {
|
||||
if (!isOpen()) {
|
||||
qDebug() << "Socket not connected.";
|
||||
return -1;
|
||||
}
|
||||
if (socket->bytesAvailable() > 0) {
|
||||
qDebug() << "Socket data is available!!!!!!!!!!!!!!.";
|
||||
}
|
||||
return socket->bytesAvailable();
|
||||
}
|
||||
|
||||
int NetSerial::rawWrite(uint8_t *bytes, int size) {
|
||||
if (!isOpen()) {
|
||||
qDebug() << "Socket not connected.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
QByteArray data(reinterpret_cast<const char *>(bytes), size);
|
||||
qint64 bytesWritten = socket->write(data);
|
||||
if (bytesWritten == -1) {
|
||||
qDebug() << "Failed to write to socket:" << socket->errorString();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!socket->waitForBytesWritten(_timeout)) {
|
||||
qDebug() << "Write operation timed out.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int>(bytesWritten);
|
||||
}
|
||||
|
||||
int NetSerial::rawRead(uint8_t bytes[], int size, bool line) {
|
||||
if (!isOpen()) {
|
||||
qDebug() << "Socket not connected.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
QByteArray buffer;
|
||||
while (buffer.size() < size) {
|
||||
if (!socket->waitForReadyRead(_timeout)) {
|
||||
qDebug() << "Read operation timed out.";
|
||||
return buffer.size() > 0 ? buffer.size() : -1;
|
||||
}
|
||||
|
||||
buffer.append(socket->read(size - buffer.size()));
|
||||
if (line && buffer.contains(static_cast<char>(endChar))) {
|
||||
int index = buffer.indexOf(static_cast<char>(endChar)) + 1;
|
||||
memcpy(bytes, buffer.data(), index);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(bytes, buffer.data(), buffer.size());
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
bool NetSerial::parseDeviceFilename(const QString &filename) {
|
||||
// Format: "server:port", e.g., "127.0.0.1:12345"
|
||||
QStringList parts = filename.split(':');
|
||||
if (parts.size() == 2) {
|
||||
serverAddress = parts[0];
|
||||
serverPort = parts[1].toUShort();
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "Invalid device filename format. Expected 'server:port'.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
61
src/devices/csafe/netserial.h
Normal file
61
src/devices/csafe/netserial.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NETSERIAL_H
|
||||
#define NETSERIAL_H
|
||||
|
||||
#include "serialhandler.h"
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QTcpSocket>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* @brief This is a simple implementation of serial port emulation over TCP
|
||||
* It emulates a serial port over a network connection.
|
||||
* e.g. as created by ser2net or hardware serial to ethernet converters
|
||||
*/
|
||||
class NetSerial : public SerialHandler {
|
||||
public:
|
||||
NetSerial(QString deviceFilename);
|
||||
~NetSerial() ;
|
||||
|
||||
int openPort() override;
|
||||
int closePort() override;
|
||||
int dataAvailable() override;
|
||||
int rawWrite(uint8_t *bytes, int size) override;
|
||||
int rawRead(uint8_t bytes[], int size, bool line = false) override;
|
||||
|
||||
bool isOpen() const override;
|
||||
void setTimeout(int timeout) override;
|
||||
void setEndChar(uint8_t endChar) override;
|
||||
void setDevice(const QString &devname) override;
|
||||
|
||||
private:
|
||||
QString deviceFilename;
|
||||
QString serverAddress;
|
||||
quint16 serverPort;
|
||||
QTcpSocket *socket;
|
||||
int _timeout = 1000; // Timeout in milliseconds
|
||||
uint8_t endChar = '\n';
|
||||
bool parseDeviceFilename(const QString &filename);
|
||||
};
|
||||
|
||||
#endif // NETSERIAL_H
|
||||
13
src/devices/csafe/serialhandler.cpp
Normal file
13
src/devices/csafe/serialhandler.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "serialhandler.h"
|
||||
#include "netserial.h"
|
||||
#include "serialport.h"
|
||||
|
||||
SerialHandler *SerialHandler::create(const QString &deviceFilename, uint32_t baudRate) {
|
||||
if (deviceFilename.contains(':')) {
|
||||
qDebug() << "Using NetSerial for device:" << deviceFilename;
|
||||
return new NetSerial(deviceFilename);
|
||||
} else {
|
||||
qDebug() << "Using Serialport for device:" << deviceFilename;
|
||||
return new Serialport(deviceFilename, baudRate);
|
||||
}
|
||||
}
|
||||
60
src/devices/csafe/serialhandler.h
Normal file
60
src/devices/csafe/serialhandler.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* This emulates a serial port over a network connection.
|
||||
* e.g. as created by ser2net or hardware serial to ethernet converters
|
||||
*
|
||||
*/
|
||||
#ifndef SERIALHANDLER_H
|
||||
#define SERIALHANDLER_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* @brief This is a parent class for serial port handlers
|
||||
* It defines the common interface for serial operations over physical serial port or network
|
||||
*/
|
||||
class SerialHandler {
|
||||
public:
|
||||
virtual ~SerialHandler() = 0; // Pure virtual destructor
|
||||
|
||||
// Factory method to create the appropriate serial handler
|
||||
static SerialHandler *create(const QString &deviceFilename, uint32_t baudRate);
|
||||
|
||||
// Abstract interface for serial operations
|
||||
virtual int openPort() = 0;
|
||||
virtual int closePort() = 0;
|
||||
virtual int rawWrite(uint8_t *bytes, int size) = 0;
|
||||
virtual int rawRead(uint8_t bytes[], int size, bool line = false) = 0;
|
||||
virtual int dataAvailable() = 0;
|
||||
virtual bool isOpen() const = 0;
|
||||
|
||||
// Common configuration methods
|
||||
virtual void setDevice(const QString &devname) = 0;
|
||||
virtual void setTimeout(int timeout) = 0;
|
||||
virtual void setEndChar(uint8_t endChar) = 0;
|
||||
|
||||
protected:
|
||||
// Protected constructor to prevent direct instantiation of this abstract class
|
||||
SerialHandler() = default;
|
||||
};
|
||||
|
||||
inline SerialHandler::~SerialHandler() {} // Definition of the pure virtual destructor
|
||||
|
||||
#endif // SERIALHANDLER_H
|
||||
381
src/devices/csafe/serialport.cpp
Normal file
381
src/devices/csafe/serialport.cpp
Normal file
@@ -0,0 +1,381 @@
|
||||
#include "serialport.h"
|
||||
|
||||
/* ----------------------------------------------------------------------
|
||||
* CONSTRUCTOR/DESTRUCTOR
|
||||
* ---------------------------------------------------------------------- */
|
||||
Serialport::Serialport(QString deviceFilename, uint32_t baudRate) {
|
||||
setDevice(deviceFilename);
|
||||
this->baudRate = baudRate;
|
||||
}
|
||||
|
||||
Serialport::~Serialport() {}
|
||||
|
||||
void Serialport::setTimeout(int timeout) { this->_timeout = timeout; }
|
||||
|
||||
void Serialport::setDevice(const QString &devname) {
|
||||
if (!devname.isEmpty()) {
|
||||
deviceFilename = devname;
|
||||
}
|
||||
}
|
||||
|
||||
void Serialport::setEndChar(uint8_t endChar) { this->endChar = endChar; }
|
||||
|
||||
bool Serialport::isOpen() const {
|
||||
#ifdef WIN32
|
||||
return (devicePort != INVALID_HANDLE_VALUE); // Checks if the Windows handle is valid
|
||||
#else
|
||||
return (devicePort != -1); // Checks if the file descriptor is valid on Linux/macOS
|
||||
#endif
|
||||
}
|
||||
|
||||
int Serialport::closePort() {
|
||||
#ifdef WIN32
|
||||
return (int)!CloseHandle(devicePort);
|
||||
#else
|
||||
tcflush(devicePort, TCIOFLUSH); // Clear out the buffer
|
||||
return close(devicePort);
|
||||
#endif
|
||||
}
|
||||
|
||||
int Serialport::openPort() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/Usbserial", "open",
|
||||
"(Landroid/content/Context;)V", QtAndroid::androidContext().object());
|
||||
#elif !defined(WIN32)
|
||||
|
||||
// LINUX AND MAC USES TERMIO / IOCTL / STDIO
|
||||
|
||||
#if defined(Q_OS_MACX)
|
||||
int ldisc = TTYDISC;
|
||||
#else
|
||||
int ldisc = N_TTY; // LINUX
|
||||
#endif
|
||||
|
||||
if ((devicePort = open(deviceFilename.toLatin1(), O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1)
|
||||
return errno;
|
||||
|
||||
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
|
||||
|
||||
if (ioctl(devicePort, TIOCSETD, &ldisc) == -1)
|
||||
return errno;
|
||||
|
||||
// get current settings for the port
|
||||
tcgetattr(devicePort, &deviceSettings);
|
||||
|
||||
// set raw mode i.e. ignbrk, brkint, parmrk, istrip, inlcr, igncr, icrnl, ixon
|
||||
// noopost, cs8, noecho, noechonl, noicanon, noisig, noiexn
|
||||
cfmakeraw(&deviceSettings);
|
||||
|
||||
// set baud rate
|
||||
switch (baudRate) {
|
||||
case 2400:
|
||||
cfsetispeed(&deviceSettings, B2400);
|
||||
cfsetospeed(&deviceSettings, B2400);
|
||||
break;
|
||||
case 9600:
|
||||
cfsetispeed(&deviceSettings, B9600);
|
||||
cfsetospeed(&deviceSettings, B9600);
|
||||
break;
|
||||
case 19200:
|
||||
cfsetispeed(&deviceSettings, B19200);
|
||||
cfsetospeed(&deviceSettings, B19200);
|
||||
break;
|
||||
case 38400:
|
||||
cfsetispeed(&deviceSettings, B38400);
|
||||
cfsetospeed(&deviceSettings, B38400);
|
||||
break;
|
||||
case 57600:
|
||||
cfsetispeed(&deviceSettings, B57600);
|
||||
cfsetospeed(&deviceSettings, B57600);
|
||||
break;
|
||||
case 115200:
|
||||
cfsetispeed(&deviceSettings, B115200);
|
||||
cfsetospeed(&deviceSettings, B115200);
|
||||
break;
|
||||
default:
|
||||
qWarning("Invalid baud rate, defaulting to 9600");
|
||||
cfsetispeed(&deviceSettings, B9600);
|
||||
cfsetospeed(&deviceSettings, B9600);
|
||||
break;
|
||||
}
|
||||
|
||||
// further attributes
|
||||
deviceSettings.c_iflag &=
|
||||
~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ICANON | ISTRIP | IXON | IXOFF | IXANY);
|
||||
deviceSettings.c_iflag |= IGNPAR;
|
||||
deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB);
|
||||
deviceSettings.c_oflag = 0;
|
||||
|
||||
#if defined(Q_OS_MACX)
|
||||
deviceSettings.c_cflag &= (~CCTS_OFLOW & ~CRTS_IFLOW); // no hardware flow control
|
||||
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
|
||||
#else
|
||||
deviceSettings.c_cflag &= (~CRTSCTS); // no hardware flow control
|
||||
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
|
||||
#endif
|
||||
deviceSettings.c_lflag = 0;
|
||||
deviceSettings.c_cc[VSTART] = 0x11;
|
||||
deviceSettings.c_cc[VSTOP] = 0x13;
|
||||
deviceSettings.c_cc[VEOF] = 0x20;
|
||||
deviceSettings.c_cc[VMIN] = 0;
|
||||
deviceSettings.c_cc[VTIME] = 0;
|
||||
|
||||
// set those attributes
|
||||
if (tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1)
|
||||
return errno;
|
||||
tcgetattr(devicePort, &deviceSettings);
|
||||
|
||||
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
|
||||
#else
|
||||
// WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE
|
||||
|
||||
COMMTIMEOUTS timeouts; // timeout settings on serial ports
|
||||
|
||||
// if deviceFilename references a port above COM9
|
||||
// then we need to open "\\.\COMX" not "COMX"
|
||||
QString portSpec;
|
||||
int portnum = deviceFilename.midRef(3).toString().toInt();
|
||||
if (portnum < 10)
|
||||
portSpec = deviceFilename;
|
||||
else
|
||||
portSpec = "\\\\.\\" + deviceFilename;
|
||||
wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough?
|
||||
MultiByteToWideChar(CP_ACP, 0, portSpec.toLatin1(), -1, (LPWSTR)deviceFilenameW, sizeof(deviceFilenameW));
|
||||
|
||||
// win32 commport API
|
||||
devicePort = CreateFile(deviceFilenameW, GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if (devicePort == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
if (GetCommState(devicePort, &deviceSettings) == false)
|
||||
return -1;
|
||||
|
||||
switch (baudRate) {
|
||||
case 2400:
|
||||
deviceSettings.BaudRate = CBR_2400;
|
||||
break;
|
||||
case 9600:
|
||||
deviceSettings.BaudRate = CBR_9600;
|
||||
break;
|
||||
case 19200:
|
||||
deviceSettings.BaudRate = CBR_19200;
|
||||
break;
|
||||
case 38400: // 38400
|
||||
deviceSettings.BaudRate = CBR_38400;
|
||||
break;
|
||||
case 57600: // 57600
|
||||
deviceSettings.BaudRate = CBR_57600;
|
||||
break;
|
||||
case 115200: // 115200
|
||||
deviceSettings.BaudRate = CBR_115200;
|
||||
break;
|
||||
default:
|
||||
qWarning("Invalid baud rate, defaulting to 9600");
|
||||
deviceSettings.BaudRate = CBR_9600;
|
||||
break;
|
||||
}
|
||||
// so we've opened the comm port lets set it up for
|
||||
deviceSettings.fParity = NOPARITY;
|
||||
deviceSettings.ByteSize = 8;
|
||||
deviceSettings.StopBits = ONESTOPBIT;
|
||||
deviceSettings.XonChar = 11;
|
||||
deviceSettings.XoffChar = 13;
|
||||
deviceSettings.EofChar = 0x0;
|
||||
deviceSettings.ErrorChar = 0x0;
|
||||
deviceSettings.EvtChar = 0x0;
|
||||
deviceSettings.fBinary = true;
|
||||
deviceSettings.fOutX = 0;
|
||||
deviceSettings.fInX = 0;
|
||||
deviceSettings.XonLim = 0;
|
||||
deviceSettings.XoffLim = 0;
|
||||
deviceSettings.fRtsControl = RTS_CONTROL_ENABLE;
|
||||
deviceSettings.fDtrControl = DTR_CONTROL_ENABLE;
|
||||
deviceSettings.fOutxCtsFlow = FALSE; // TRUE;
|
||||
|
||||
if (SetCommState(devicePort, &deviceSettings) == false) {
|
||||
CloseHandle(devicePort);
|
||||
return -1;
|
||||
}
|
||||
|
||||
timeouts.ReadIntervalTimeout = 0;
|
||||
timeouts.ReadTotalTimeoutConstant = 1000;
|
||||
timeouts.ReadTotalTimeoutMultiplier = 50;
|
||||
timeouts.WriteTotalTimeoutConstant = 2000;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
SetCommTimeouts(devicePort, &timeouts);
|
||||
|
||||
#endif
|
||||
|
||||
// success
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Serialport::dataAvailable() {
|
||||
if (!isOpen()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
jint len = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Usbserial", "readLen", "()I");
|
||||
static_cast<size_t>(len);
|
||||
|
||||
#elif defined(WIN32)
|
||||
COMSTAT cs;
|
||||
if (!ClearCommError(devicePort, NULL, &cs)) {
|
||||
return -1;
|
||||
}
|
||||
return static_cast<size_t>(cs.cbInQue);
|
||||
#else
|
||||
int count = 0;
|
||||
if (-1 == ioctl(devicePort, FIONREAD, &count)) {
|
||||
return 0;
|
||||
} else {
|
||||
return static_cast<size_t>(count);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int Serialport::rawWrite(uint8_t *bytes, int size) {
|
||||
qDebug() << "Writing data:" << QByteArray((const char *)bytes, size).toHex();
|
||||
int rc = 0;
|
||||
if (!isOpen()) {
|
||||
qDebug() << "Port not open";
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
QAndroidJniEnvironment env;
|
||||
jbyteArray d = env->NewByteArray(size);
|
||||
jbyte *b = env->GetByteArrayElements(d, 0);
|
||||
for (int i = 0; i < size; i++)
|
||||
b[i] = bytes[i];
|
||||
env->SetByteArrayRegion(d, 0, size, b);
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/Usbserial", "write", "([B)V", d);
|
||||
#elif defined(WIN32)
|
||||
DWORD cBytes;
|
||||
rc = WriteFile(devicePort, bytes, size, &cBytes, NULL);
|
||||
if (!rc)
|
||||
return -1;
|
||||
return rc;
|
||||
|
||||
#else
|
||||
int ibytes;
|
||||
ioctl(devicePort, FIONREAD, &ibytes);
|
||||
|
||||
// timeouts are less critical for writing, since vols are low
|
||||
rc = write(devicePort, bytes, size);
|
||||
|
||||
// but it is good to avoid buffer overflow since the
|
||||
// computrainer microcontroller has almost no RAM
|
||||
if (rc != -1)
|
||||
tcdrain(devicePort); // wait till its gone.
|
||||
|
||||
ioctl(devicePort, FIONREAD, &ibytes);
|
||||
#endif
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int Serialport::rawRead(uint8_t bytes[], int size, bool line) {
|
||||
int rc = 0;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
int fullLen = 0;
|
||||
cleanFrame = false;
|
||||
|
||||
// previous buffer?
|
||||
while (bufRX.count()) {
|
||||
bytes[fullLen++] = bufRX.at(0);
|
||||
bufRX.removeFirst();
|
||||
qDebug() << "byte popped from rxBuf";
|
||||
if (fullLen >= size) {
|
||||
qDebug() << size << QByteArray((const char *)bytes, size).toHex(' ');
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
QAndroidJniEnvironment env;
|
||||
while (fullLen < size) {
|
||||
QAndroidJniObject dd =
|
||||
QAndroidJniObject::callStaticObjectMethod("org/cagnulen/qdomyoszwift/Usbserial", "read", "()[B");
|
||||
jint len = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Usbserial", "readLen", "()I");
|
||||
jbyteArray d = dd.object<jbyteArray>();
|
||||
jbyte *b = env->GetByteArrayElements(d, 0);
|
||||
if (len + fullLen > size) {
|
||||
QByteArray tmpDebug;
|
||||
qDebug() << "buffer overflow! Truncate from" << len + fullLen << "requested" << size;
|
||||
/*for(int i=0; i<len; i++) {
|
||||
qDebug() << b[i];
|
||||
}*/
|
||||
|
||||
for (int i = fullLen; i < size; i++) {
|
||||
bytes[i] = b[i - fullLen];
|
||||
}
|
||||
for (int i = size; i < len + fullLen; i++) {
|
||||
jbyte bb = b[i - fullLen];
|
||||
bufRX.append(bb);
|
||||
tmpDebug.append(bb);
|
||||
}
|
||||
qDebug() << len + fullLen - size << "bytes to the rxBuf" << tmpDebug.toHex(' ');
|
||||
qDebug() << size << QByteArray((const char *)b, size).toHex(' ');
|
||||
return size;
|
||||
}
|
||||
for (int i = fullLen; i < len + fullLen; i++) {
|
||||
bytes[i] = b[i - fullLen];
|
||||
}
|
||||
qDebug() << len << QByteArray((const char *)b, len).toHex(' ');
|
||||
fullLen += len;
|
||||
}
|
||||
|
||||
qDebug() << "FULL BUFFER RX: << " << fullLen << QByteArray((const char *)bytes, size).toHex(' ');
|
||||
cleanFrame = true;
|
||||
|
||||
return fullLen;
|
||||
#elif defined(WIN32)
|
||||
Q_UNUSED(size);
|
||||
// Readfile deals with timeouts and readyread issues
|
||||
DWORD cBytes;
|
||||
rc = ReadFile(devicePort, bytes, 7, &cBytes, NULL);
|
||||
if (rc)
|
||||
return (int)cBytes;
|
||||
else
|
||||
return (-1);
|
||||
|
||||
#else
|
||||
|
||||
int timeout = 0, i = 0;
|
||||
uint8_t byte;
|
||||
|
||||
// read one byte at a time sleeping when no data ready
|
||||
// until we timeout waiting then return error
|
||||
for (i = 0; i < size; i++) {
|
||||
timeout = 0;
|
||||
rc = 0;
|
||||
while (rc == 0 && timeout < _timeout) {
|
||||
rc = read(devicePort, &byte, 1);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
else if (rc == 0) {
|
||||
QThread::msleep(50); // sleep for 1/20th of a second
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
timeout += 50;
|
||||
} else {
|
||||
bytes[i] = byte;
|
||||
if (line && endChar == byte) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timeout >= _timeout)
|
||||
return i > 0 ? i : -1;
|
||||
}
|
||||
|
||||
qDebug() << i << QString::fromLocal8Bit((const char *)bytes, i);
|
||||
return i;
|
||||
|
||||
#endif
|
||||
}
|
||||
98
src/devices/csafe/serialport.h
Normal file
98
src/devices/csafe/serialport.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com),
|
||||
2024 Marcel Verpaalen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _SERIALPORT_h
|
||||
#define _SERIALPORT_h
|
||||
|
||||
#include "serialhandler.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#include <QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QDebug>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#ifndef N_TTY // for OpenBSD, this is a hack
|
||||
#define N_TTY 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
class Serialport : public SerialHandler {
|
||||
public:
|
||||
Serialport(QString deviceFilename, uint32_t baudRate);
|
||||
~Serialport() override;
|
||||
|
||||
// Device management
|
||||
void setDevice(const QString &devname) override;
|
||||
void setTimeout(int timeout) override;
|
||||
void setEndChar(uint8_t endChar) override;
|
||||
|
||||
// Port control
|
||||
int openPort() override;
|
||||
int dataAvailable() override;
|
||||
int closePort() override;
|
||||
|
||||
// Data transfer
|
||||
int rawWrite(uint8_t *bytes, int size) override;
|
||||
int rawRead(uint8_t bytes[], int size, bool line = false) override;
|
||||
|
||||
bool isOpen() const override;
|
||||
|
||||
private:
|
||||
uint32_t baudRate = 9600;
|
||||
uint8_t endChar = 0x0D;
|
||||
int _timeout = 1200;
|
||||
QString deviceFilename;
|
||||
|
||||
// device port
|
||||
#ifdef WIN32
|
||||
HANDLE devicePort; // file descriptor for reading from com3
|
||||
DCB deviceSettings; // serial port settings baud rate et al
|
||||
#else
|
||||
int devicePort; // unix!!
|
||||
struct termios deviceSettings; // unix!!
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QList<jbyte> bufRX;
|
||||
bool cleanFrame = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // _SERIALPORT_h
|
||||
331
src/devices/csafeelliptical/csafeelliptical.cpp
Normal file
331
src/devices/csafeelliptical/csafeelliptical.cpp
Normal file
@@ -0,0 +1,331 @@
|
||||
#include "csafeelliptical.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
csafeelliptical::csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice,
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->noVirtualDevice = noVirtualDevice;
|
||||
connect(refresh, &QTimer::timeout, this, &csafeelliptical::update);
|
||||
refresh->start(200ms);
|
||||
QString deviceFilename =
|
||||
settings.value(QZSettings::csafe_elliptical_port, QZSettings::default_csafe_elliptical_port).toString();
|
||||
CsafeRunnerThread *csafeRunner = new CsafeRunnerThread(deviceFilename);
|
||||
setupCommands(csafeRunner);
|
||||
connect(csafeRunner, &CsafeRunnerThread::portAvailable, this, &csafeelliptical::portAvailable);
|
||||
connect(csafeRunner, &CsafeRunnerThread::onCsafeFrame, this, &csafeelliptical::onCsafeFrame);
|
||||
connect(this, &csafeelliptical::sendCsafeCommand, csafeRunner, &CsafeRunnerThread::sendCommand,
|
||||
Qt::QueuedConnection);
|
||||
csafeRunner->start();
|
||||
distanceReceived = 0;
|
||||
kalman = new KalmanFilter(1, .01, .75, 0); // measure error, estimate error, process noise q, initial value
|
||||
}
|
||||
|
||||
void csafeelliptical::setupCommands(CsafeRunnerThread *runner) {
|
||||
QStringList command;
|
||||
command << "CSAFE_GETPOWER_CMD";
|
||||
command << "CSAFE_GETSPEED_CMD";
|
||||
command << "CSAFE_GETCALORIES_CMD";
|
||||
command << "CSAFE_GETHRCUR_CMD";
|
||||
command << "CSAFE_GETHORIZONTAL_CMD";
|
||||
runner->addRefreshCommand(command);
|
||||
runner->addRefreshCommand(QStringList() << "CSAFE_LF_GET_DETAIL");
|
||||
runner->addRefreshCommand(QStringList() << "CSAFE_GETPROGRAM_CMD");
|
||||
}
|
||||
|
||||
void csafeelliptical::setupWorkout() {
|
||||
emit sendCsafeCommand(QStringList() << "CSAFE_GETUSERINFO_CMD");
|
||||
QStringList command = {"CSAFE_SETUSERINFO_CMD",
|
||||
QString::number(settings.value(QZSettings::weight, QZSettings::default_weight).toInt()),
|
||||
"39", QString::number(settings.value(QZSettings::age, QZSettings::default_age).toInt()),
|
||||
"1"}; // weight,weight unit,age,gender
|
||||
emit sendCsafeCommand(command);
|
||||
emit sendCsafeCommand(QStringList() << "CSAFE_SETPROGRAM_CMD" << "4" << "5");
|
||||
emit sendCsafeCommand(QStringList() << "CSAFE_GOINUSE_CMD");
|
||||
}
|
||||
|
||||
void csafeelliptical::onCsafeFrame(const QVariantMap &csafeFrame) {
|
||||
// qDebug() << "Current CSAFE frame received:" << csafeFrame;
|
||||
|
||||
if (csafeFrame["CSAFE_GETCADENCE_CMD"].isValid()) {
|
||||
onCadence(csafeFrame["CSAFE_GETCADENCE_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETSPEED_CMD"].isValid()) {
|
||||
double speed = csafeFrame["CSAFE_GETSPEED_CMD"].value<QVariantList>()[0].toDouble();
|
||||
int unit = csafeFrame["CSAFE_GETSPEED_CMD"].value<QVariantList>()[1].toInt();
|
||||
qDebug() << "Speed value:" << speed << "unit:" << CSafeUtility::getUnitName(unit) << "(" << unit << ")";
|
||||
|
||||
if (unit == 82) { // revs/minute
|
||||
onCadence(speed);
|
||||
} else {
|
||||
onSpeed(CSafeUtility::convertToStandard(unit, speed));
|
||||
}
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETPOWER_CMD"].isValid()) {
|
||||
onPower(csafeFrame["CSAFE_GETPOWER_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETHRCUR_CMD"].isValid()) {
|
||||
onHeart(csafeFrame["CSAFE_GETHRCUR_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETCALORIES_CMD"].isValid()) {
|
||||
onCalories(csafeFrame["CSAFE_GETCALORIES_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETHORIZONTAL_CMD"].isValid()) {
|
||||
double distance = csafeFrame["CSAFE_GETHORIZONTAL_CMD"].value<QVariantList>()[0].toDouble();
|
||||
int unit = csafeFrame["CSAFE_GETHORIZONTAL_CMD"].value<QVariantList>()[1].toInt();
|
||||
qDebug() << "Distance value:" << distance << "unit:" << CSafeUtility::getUnitName(unit) << "(" << unit << ")"
|
||||
<< CSafeUtility::convertToStandard(unit, distance);
|
||||
onDistance(CSafeUtility::convertToStandard(unit, distance));
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETPROGRAM_CMD"].isValid()) {
|
||||
int resistance = csafeFrame["CSAFE_GETPROGRAM_CMD"].value<QVariantList>()[1].toUInt();
|
||||
Resistance = resistance;
|
||||
qDebug() << "Program:" << csafeFrame["CSAFE_GETPROGRAM_CMD"].value<QVariantList>()[0].toUInt()
|
||||
<< "Current level received:" << resistance;
|
||||
}
|
||||
if (csafeFrame["CSAFE_GETSTATUS_CMD"].isValid()) {
|
||||
uint16_t statusvalue = csafeFrame["CSAFE_GETSTATUS_CMD"].value<QVariantList>()[0].toUInt();
|
||||
qDebug() << "Status value:" << statusvalue << " lastStatus:" << lastStatus
|
||||
<< " Machine state from status:" << (statusvalue & 0x0f);
|
||||
if (statusvalue != lastStatus) {
|
||||
lastStatus = statusvalue;
|
||||
char statusChar = static_cast<char>(statusvalue & 0x0f);
|
||||
onStatus(statusChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::onSpeed(double speed) {
|
||||
qDebug() << "Current Speed received:" << speed << " updated:" << distanceIsChanging;
|
||||
if (distanceIsChanging)
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
void csafeelliptical::onPower(double power) {
|
||||
qDebug() << "Current Power received:" << power << " updated:" << distanceIsChanging;
|
||||
if (distanceIsChanging)
|
||||
m_watt = power;
|
||||
}
|
||||
|
||||
void csafeelliptical::onCadence(double cadence) {
|
||||
qDebug() << "Current Cadence received:" << cadence << " updated:" << distanceIsChanging;
|
||||
if (distanceIsChanging)
|
||||
Cadence = cadence;
|
||||
}
|
||||
|
||||
void csafeelliptical::onHeart(double hr) {
|
||||
qDebug() << "Current Heart received:" << hr;
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
uint8_t heart = ((uint8_t)hr);
|
||||
if (heart == 0 || disable_hr_frommachinery) {
|
||||
update_hr_from_external();
|
||||
} else
|
||||
Heart = heart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::onCalories(double calories) {
|
||||
qDebug() << "Current Calories received:" << calories;
|
||||
KCal = calories;
|
||||
}
|
||||
|
||||
void csafeelliptical::onDistance(double distance) {
|
||||
qDebug() << "Current Distance received:" << distance << " value tracker:" << distanceReceived.value();
|
||||
Distance = distance / 1000.0;
|
||||
|
||||
if (distance != distanceReceived.value()) {
|
||||
|
||||
double calculated_speed = 3600 * (distance - distanceReceived.value()) /
|
||||
abs(distanceReceived.lastChanged().msecsTo(QDateTime::currentDateTime()));
|
||||
if (distanceIsChanging) { // skip the first distance or after pause otherwise you get enormous speed values
|
||||
// Speed is calculated from the distance and is bit erratic. The kalman filter is used to smooth it out
|
||||
Speed = kalman->updateEstimate(calculated_speed);
|
||||
}
|
||||
|
||||
qDebug() << "Distance received:" << distance << " Previous:" << distanceReceived.value()
|
||||
<< " time(ms):" << abs(distanceReceived.lastChanged().msecsTo(QDateTime::currentDateTime()))
|
||||
<< " Calculated Speed:" << calculated_speed << " New speed:" << Speed.value()
|
||||
<< " updated:" << distanceIsChanging;
|
||||
distanceReceived = distance;
|
||||
distanceIsChanging = true;
|
||||
} else if (abs(distanceReceived.lastChanged().secsTo(QDateTime::currentDateTime())) > 15) {
|
||||
distanceIsChanging = false;
|
||||
m_watt = 0.0;
|
||||
Cadence = 0.0;
|
||||
Speed = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::onStatus(char status) {
|
||||
QString statusString = CSafeUtility::statusByteToText(status);
|
||||
qDebug() << "Current Status code:" << status << " status: " << statusString;
|
||||
if (status == 0x06) {
|
||||
// pause
|
||||
paused = true;
|
||||
m_watt = 0.0;
|
||||
Cadence = 0.0;
|
||||
Speed = 0.0;
|
||||
distanceIsChanging = false;
|
||||
} else {
|
||||
paused = false;
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::portAvailable(bool available) {
|
||||
if (available) {
|
||||
qDebug() << "CSAFE port available";
|
||||
_connected = true;
|
||||
setupWorkout();
|
||||
} else {
|
||||
qDebug() << "CSAFE port not available";
|
||||
_connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::update() {
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
|
||||
update_metrics(true, watts());
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
if (!firstStateChanged && !this->hasVirtualDevice()
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
&& !h
|
||||
#endif
|
||||
#endif
|
||||
) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence && !virtual_device_rower) {
|
||||
qDebug() << "ios_peloton_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualbike_ios();
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
bool virtual_device_force_bike =
|
||||
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
|
||||
.toBool();
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_force_bike) {
|
||||
debug("creating virtual treadmill interface...");
|
||||
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadmill, &virtualtreadmill::debug, this, &csafeelliptical::debug);
|
||||
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
|
||||
&csafeelliptical::changeInclinationRequested);
|
||||
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
debug("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this);
|
||||
// auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this,
|
||||
&csafeelliptical::changeInclinationRequested);
|
||||
connect(virtualBike, &virtualbike::ftmsCharacteristicChanged, this,
|
||||
&csafeelliptical::ftmsCharacteristicChanged);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
|
||||
}
|
||||
} else {
|
||||
debug("not creating virtual interface... not enabled");
|
||||
}
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
|
||||
if (!firstStateChanged) {
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
if (!noVirtualDevice) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
debug("Current Heart: " + QString::number(Heart.value()));
|
||||
}
|
||||
#endif
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
|
||||
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if (requestResistance != -1 && requestResistance != currentResistance().value()) {
|
||||
if (!noWriteResistance) {
|
||||
changeResistance(requestResistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void csafeelliptical::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
QByteArray b = newValue;
|
||||
qDebug() << "Routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
|
||||
}
|
||||
|
||||
void csafeelliptical::changeInclinationRequested(double grade, double percentage) {
|
||||
if (percentage < 0)
|
||||
percentage = 0;
|
||||
changeInclination(grade, percentage);
|
||||
qDebug() << "Requested grade:" << grade << " percentage:" << percentage;
|
||||
}
|
||||
|
||||
void csafeelliptical::changeResistance(resistance_t res) {
|
||||
if (res < 0)
|
||||
res = 0;
|
||||
if (res > 25)
|
||||
res = 25;
|
||||
QStringList resistanceCommand = {"CSAFE_SETLEVEL", QString::number(res)};
|
||||
emit sendCsafeCommand(resistanceCommand);
|
||||
qDebug() << "Send resistance update to device. Requested resitance: " << res;
|
||||
emit sendCsafeCommand(QStringList() << "CSAFE_GETPROGRAM_CMD");
|
||||
}
|
||||
|
||||
bool csafeelliptical::connected() { return _connected; }
|
||||
|
||||
void csafeelliptical::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
|
||||
}
|
||||
|
||||
uint16_t csafeelliptical::watts() { return m_watt.value(); }
|
||||
142
src/devices/csafeelliptical/csafeelliptical.h
Normal file
142
src/devices/csafeelliptical/csafeelliptical.h
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
|
||||
* based on csaferower
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef CSAFEELLIPTICAL_H
|
||||
#define CSAFEELLIPTICAL_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#include <QAndroidJniObject>
|
||||
#endif
|
||||
|
||||
#include "devices/csafe/csafe.h"
|
||||
#include "devices/csafe/csaferunner.h"
|
||||
#include "devices/csafe/csafeutility.h"
|
||||
#include "devices/csafe/kalmanfilter.h"
|
||||
#include "devices/csafe/serialhandler.h"
|
||||
|
||||
#include "devices/elliptical.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
|
||||
// #include <stdint.h>
|
||||
// #include <stdio.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <sys/types.h>
|
||||
|
||||
/**
|
||||
* @brief This class is a CSAFE implementation for elliptical devices.
|
||||
* Developed for Life Fitness 95x but most likely also working for other CSAFE devices.
|
||||
*/
|
||||
|
||||
class csafeelliptical : public elliptical {
|
||||
Q_OBJECT
|
||||
public:
|
||||
csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
QTimer *refresh;
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
int lastStatus = -1;
|
||||
QSettings settings;
|
||||
|
||||
uint16_t watts() override;
|
||||
void setupCommands(CsafeRunnerThread *runner);
|
||||
void setupWorkout();
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool noVirtualDevice = false;
|
||||
|
||||
bool distanceIsChanging = false;
|
||||
metric distanceReceived;
|
||||
KalmanFilter *kalman;
|
||||
KalmanFilter *kalman1;
|
||||
KalmanFilter *kalman2;
|
||||
KalmanFilter *kalman3;
|
||||
KalmanFilter *kalman4;
|
||||
bool _connected = true;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
void sendCsafeCommand(const QStringList &commands);
|
||||
|
||||
private slots:
|
||||
void update();
|
||||
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void changeInclinationRequested(double grade, double percentage);
|
||||
void changeResistance(resistance_t res) override;
|
||||
void onPower(double power);
|
||||
void onCadence(double cadence);
|
||||
void onHeart(double hr);
|
||||
void onCalories(double calories);
|
||||
void onDistance(double distance);
|
||||
void onStatus(char status);
|
||||
void onSpeed(double speed);
|
||||
void portAvailable(bool available);
|
||||
void onCsafeFrame(const QVariantMap &frame);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
};
|
||||
|
||||
#endif // CSAFEELLIPTICAL_H
|
||||
@@ -18,20 +18,33 @@ csaferower::csaferower(bool noWriteResistance, bool noHeartService, bool noVirtu
|
||||
connect(t, &csaferowerThread::onHeart, this, &csaferower::onHeart);
|
||||
connect(t, &csaferowerThread::onCalories, this, &csaferower::onCalories);
|
||||
connect(t, &csaferowerThread::onDistance, this, &csaferower::onDistance);
|
||||
connect(t, &csaferowerThread::onPace, this, &csaferower::onPace);
|
||||
connect(t, &csaferowerThread::onStatus, this, &csaferower::onStatus);
|
||||
t->start();
|
||||
}
|
||||
|
||||
void csaferower::onPower(double power) {
|
||||
qDebug() << "Current Power received:" << power;
|
||||
m_watt = power;
|
||||
|
||||
double pace = (pow((2.8 / power), (1. / 3))) * 1000; // pace to m/km put *500 instead to have a m/500m
|
||||
Speed = (60.0 / (double)(pace)) * 30.0;
|
||||
void csaferower::onPace(double pace) {
|
||||
qDebug() << "Current Pace received:" << pace;
|
||||
if(distanceIsChanging && pace > 0)
|
||||
Speed = (60.0 / (double)(pace)) * 60.0;
|
||||
else
|
||||
Speed = 0;
|
||||
|
||||
qDebug() << "Current Speed calculated:" << Speed.value() << pace;
|
||||
}
|
||||
|
||||
void csaferower::onCadence(double cadence) { qDebug() << "Current Cadence received:" << cadence; }
|
||||
|
||||
void csaferower::onPower(double power) {
|
||||
qDebug() << "Current Power received:" << power;
|
||||
if(distanceIsChanging)
|
||||
m_watt = power;
|
||||
}
|
||||
|
||||
void csaferower::onCadence(double cadence) {
|
||||
qDebug() << "Current Cadence received:" << cadence;
|
||||
if(distanceIsChanging)
|
||||
Cadence = cadence;
|
||||
}
|
||||
|
||||
void csaferower::onHeart(double hr) {
|
||||
qDebug() << "Current Heart received:" << hr;
|
||||
@@ -62,14 +75,30 @@ void csaferower::onCalories(double calories) {
|
||||
KCal = calories;
|
||||
}
|
||||
|
||||
void csaferower::onDistance(double distance) { qDebug() << "Current Distance received:" << distance / 1000.0; }
|
||||
void csaferower::onDistance(double distance) {
|
||||
qDebug() << "Current Distance received:" << distance / 1000.0;
|
||||
|
||||
if(distance != distanceReceived.value()) {
|
||||
distanceIsChanging = true;
|
||||
distanceReceived = distance;
|
||||
} else if(abs(distanceReceived.lastChanged().secsTo(QDateTime::currentDateTime())) > 2) {
|
||||
distanceIsChanging = false;
|
||||
m_watt = 0;
|
||||
Cadence = 0;
|
||||
Speed = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void csaferower::onStatus(char status) {
|
||||
qDebug() << "Current Status received:" << status;
|
||||
}
|
||||
|
||||
csaferowerThread::csaferowerThread() {}
|
||||
|
||||
void csaferowerThread::run() {
|
||||
QSettings settings;
|
||||
/*devicePort =
|
||||
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();*/
|
||||
deviceFilename = settings.value(QZSettings::csafe_rower, QZSettings::default_csafe_rower).toString();
|
||||
|
||||
openPort();
|
||||
csafe *aa = new csafe();
|
||||
@@ -81,7 +110,9 @@ void csaferowerThread::run() {
|
||||
command << "CSAFE_GETPOWER_CMD";
|
||||
command << "CSAFE_GETCALORIES_CMD";
|
||||
command << "CSAFE_GETHRCUR_CMD";
|
||||
QByteArray ret = aa->write(command);
|
||||
command << "CSAFE_GETPACE_CMD";
|
||||
command << "CSAFE_GETSTATUS_CMD";
|
||||
QByteArray ret = aa->write(command,true);
|
||||
|
||||
qDebug() << " >> " << ret.toHex(' ');
|
||||
rawWrite((uint8_t *)ret.data(), ret.length());
|
||||
@@ -96,6 +127,9 @@ void csaferowerThread::run() {
|
||||
if (f["CSAFE_GETCADENCE_CMD"].isValid()) {
|
||||
emit onCadence(f["CSAFE_GETCADENCE_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (f["CSAFE_GETPACE_CMD"].isValid()) {
|
||||
emit onPace(f["CSAFE_GETPACE_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (f["CSAFE_GETPOWER_CMD"].isValid()) {
|
||||
emit onPower(f["CSAFE_GETPOWER_CMD"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
@@ -108,6 +142,10 @@ void csaferowerThread::run() {
|
||||
if (f["CSAFE_PM_GET_WORKDISTANCE"].isValid()) {
|
||||
emit onDistance(f["CSAFE_PM_GET_WORKDISTANCE"].value<QVariantList>()[0].toDouble());
|
||||
}
|
||||
if (f["CSAFE_GETSTATUS_CMD"].isValid()) {
|
||||
char statusChar = static_cast<char>(f["CSAFE_GETSTATUS_CMD"].value<QVariantList>()[0].toUInt() & 0x0f);
|
||||
emit onStatus(statusChar);
|
||||
}
|
||||
|
||||
memset(rx, 0x00, sizeof(rx));
|
||||
QThread::msleep(50);
|
||||
@@ -125,6 +163,9 @@ int csaferowerThread::closePort() {
|
||||
}
|
||||
|
||||
int csaferowerThread::openPort() {
|
||||
|
||||
qDebug() << "Opening serial port " << deviceFilename.toLatin1();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/CSafeRowerUSBHID", "open",
|
||||
"(Landroid/content/Context;)V", QtAndroid::androidContext().object());
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "csafe.h"
|
||||
#include "devices/csafe/csafe.h"
|
||||
#include "devices/rower.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualrower.h"
|
||||
@@ -86,6 +86,8 @@ class csaferowerThread : public QThread {
|
||||
void onHeart(double hr);
|
||||
void onCalories(double calories);
|
||||
void onDistance(double distance);
|
||||
void onPace(double pace);
|
||||
void onStatus(char status);
|
||||
|
||||
private:
|
||||
// Utility and BG Thread functions
|
||||
@@ -141,6 +143,10 @@ class csaferower : public rower {
|
||||
uint16_t oldLastCrankEventTime = 0;
|
||||
uint16_t oldCrankRevs = 0;
|
||||
|
||||
bool distanceIsChanging = false;
|
||||
metric distanceReceived;
|
||||
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
@@ -158,6 +164,8 @@ class csaferower : public rower {
|
||||
void onHeart(double hr);
|
||||
void onCalories(double calories);
|
||||
void onDistance(double distance);
|
||||
void onPace(double pace);
|
||||
void onStatus(char status);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QLowEnergyConnectionParameters>
|
||||
#endif
|
||||
#include <chrono>
|
||||
#include "homeform.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -87,7 +88,9 @@ void cscbike::update() {
|
||||
// gattWriteCharacteristic.isValid() &&
|
||||
// gattNotify1Characteristic.isValid() &&
|
||||
/*initDone*/) {
|
||||
update_metrics(true, watts());
|
||||
bool cadence_sensor_as_bike =
|
||||
settings.value(QZSettings::cadence_sensor_as_bike, QZSettings::default_cadence_sensor_as_bike).toBool();
|
||||
update_metrics(false, watts(), !cadence_sensor_as_bike);
|
||||
|
||||
if(lastGoodCadence.secsTo(QDateTime::currentDateTime()) > 5 && !charNotified) {
|
||||
readMethod = true;
|
||||
@@ -139,7 +142,7 @@ void cscbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
|
||||
void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
qDebug() << "characteristicChanged << " << characteristic.uuid() << newValue.toHex(' ') << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
// QString heartRateBeltName = //unused QString
|
||||
@@ -155,6 +158,10 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) {
|
||||
battery = newValue.at(0);
|
||||
if(battery != battery_level)
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(bluetoothDevice.name() + QStringLiteral(" Battery Level ") + QString::number(battery) + " %");
|
||||
battery_level = battery;
|
||||
qDebug() << QStringLiteral("battery: ") << battery;
|
||||
return;
|
||||
}
|
||||
@@ -208,7 +215,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
|
||||
if (CrankRevs != oldCrankRevs && deltaT) {
|
||||
double cadence = ((CrankRevs - oldCrankRevs) / deltaT) * 1024 * 60;
|
||||
if (cadence >= 0 && cadence < 256)
|
||||
if ((cadence >= 0 && cadence < 256 && CrankPresent) || (!CrankPresent && WheelPresent))
|
||||
Cadence = cadence;
|
||||
lastGoodCadence = now;
|
||||
} else if (lastGoodCadence.msecsTo(now) > 2000) {
|
||||
|
||||
@@ -57,6 +57,8 @@ class cscbike : public bike {
|
||||
uint8_t firstStateChanged = 0;
|
||||
bool charNotified = false;
|
||||
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
|
||||
1002
src/devices/cycleopsphantombike/cycleopsphantombike.cpp
Normal file
1002
src/devices/cycleopsphantombike/cycleopsphantombike.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
src/devices/cycleopsphantombike/cycleopsphantombike.h
Normal file
134
src/devices/cycleopsphantombike/cycleopsphantombike.h
Normal file
@@ -0,0 +1,134 @@
|
||||
#ifndef CYCLEOPSPHANTOMBIKE_H
|
||||
#define CYCLEOPSPHANTOMBIKE_H
|
||||
|
||||
#include <QBluetoothDeviceDiscoveryAgent>
|
||||
#include <QtBluetooth/qlowenergyadvertisingdata.h>
|
||||
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristic.h>
|
||||
#include <QtBluetooth/qlowenergycharacteristicdata.h>
|
||||
#include <QtBluetooth/qlowenergycontroller.h>
|
||||
#include <QtBluetooth/qlowenergydescriptordata.h>
|
||||
#include <QtBluetooth/qlowenergyservice.h>
|
||||
#include <QtBluetooth/qlowenergyservicedata.h>
|
||||
#include <QtCore/qbytearray.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "devices/bike.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class cycleopsphantombike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
cycleopsphantombike(bool noWriteResistance, bool noHeartService);
|
||||
void changePower(int32_t power) override;
|
||||
bool connected() override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
|
||||
private:
|
||||
enum class ControlMode : uint8_t {
|
||||
Headless = 0x00,
|
||||
ManualPower = 0x01,
|
||||
ManualSlope = 0x02,
|
||||
PowerRange = 0x03,
|
||||
WarmUp = 0x04,
|
||||
RollDown = 0x05
|
||||
};
|
||||
|
||||
enum class ControlStatus : uint8_t {
|
||||
SpeedOkay = 0x00,
|
||||
SpeedUp = 0x01,
|
||||
SpeedDown = 0x02,
|
||||
RollDownInitializing = 0x03,
|
||||
RollDownInProcess = 0x04,
|
||||
RollDownPassed = 0x05,
|
||||
RollDownFailed = 0x06
|
||||
};
|
||||
void setControlMode(ControlMode mode, int16_t parameter1 = 0, int16_t parameter2 = 0);
|
||||
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void forceInclination(double inclination);
|
||||
uint16_t watts() override;
|
||||
double bikeResistanceToPeloton(double resistance);
|
||||
void setUserConfiguration(double wheelDiameter, double gearRatio);
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
const int max_resistance = 100;
|
||||
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointId;
|
||||
QLowEnergyCharacteristic gattWriteCharCustomId;
|
||||
QLowEnergyService *gattPowerService = nullptr;
|
||||
QLowEnergyService *gattCustomService;
|
||||
// QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged2A5B = QDateTime::currentDateTime();
|
||||
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
|
||||
QDateTime lastRefreshCharacteristicChangedPower = QDateTime::currentDateTime();
|
||||
QDateTime lastGoodCadence = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
uint16_t oldLastCrankEventTime = 0;
|
||||
uint16_t oldCrankRevs = 0;
|
||||
uint16_t CrankRevsRead = 0;
|
||||
|
||||
double lastGearValue = -1;
|
||||
bool resistance_received = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
void controllerStateChanged(QLowEnergyController::ControllerState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
|
||||
void powerPacketReceived(const QByteArray &b);
|
||||
};
|
||||
|
||||
#endif // CYCLEOPSPHANTOMBIKE_H
|
||||
495
src/devices/deeruntreadmill/deerruntreadmill.cpp
Normal file
495
src/devices/deeruntreadmill/deerruntreadmill.cpp
Normal 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; }
|
||||
103
src/devices/deeruntreadmill/deerruntreadmill.h
Normal file
103
src/devices/deeruntreadmill/deerruntreadmill.h
Normal 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
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
/**
|
||||
* @brief Specifies a value that will be added to the resistance requests going to the bike, after the gain has been applied.
|
||||
*/
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
|
||||
/**
|
||||
* @brief The resistance requests going to the bike should be multiplied by this, before adding the resistance offset.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user