mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
386 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379cf8d7de | ||
|
|
2f2989f90d | ||
|
|
6fac9770be | ||
|
|
8d07d7c3f7 | ||
|
|
7aa1061b06 | ||
|
|
46bd172d59 | ||
|
|
f85f743499 | ||
|
|
74c37f5624 | ||
|
|
d4dbaf5c57 | ||
|
|
527396eafc | ||
|
|
6b4d47c79d | ||
|
|
1bd32ade9f | ||
|
|
5e1f3abd14 | ||
|
|
5bf7ecab64 | ||
|
|
dd75df0af8 | ||
|
|
72de08f9a3 | ||
|
|
13213edb4f | ||
|
|
0f149448b3 | ||
|
|
872b618ea1 | ||
|
|
78e7fe76c6 | ||
|
|
980245bbfc | ||
|
|
2fd98a0be0 | ||
|
|
700f5debe5 | ||
|
|
06b4604e59 | ||
|
|
a6fd6b71d6 | ||
|
|
ed1599ca8e | ||
|
|
da194caf7c | ||
|
|
fa45e1040f | ||
|
|
09f0357763 | ||
|
|
89808ae65b | ||
|
|
2da194f073 | ||
|
|
f82e106fc1 | ||
|
|
906431b3a6 | ||
|
|
e82a76492a | ||
|
|
8c75c01017 | ||
|
|
d712621b7b | ||
|
|
1c06260036 | ||
|
|
dfabd2b414 | ||
|
|
8199dea809 | ||
|
|
1cb20088b2 | ||
|
|
203a9e5ca5 | ||
|
|
5a70586756 | ||
|
|
478beca96d | ||
|
|
637b57158a | ||
|
|
a2009fa91f | ||
|
|
2d1364497e | ||
|
|
cc7757bfcd | ||
|
|
49c7a96c81 | ||
|
|
4ee5fa3b00 | ||
|
|
36dde79dac | ||
|
|
9071d8a000 | ||
|
|
1c815e9d47 | ||
|
|
9b16779293 | ||
|
|
c702477dc8 | ||
|
|
05d598ffcf | ||
|
|
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 |
256
.github/workflows/main.yml
vendored
256
.github/workflows/main.yml
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
@@ -195,14 +196,14 @@ jobs:
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-binary
|
||||
path: windows-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-binary-no-python
|
||||
path: windows-binary-no-python.zip
|
||||
@@ -433,7 +434,7 @@ jobs:
|
||||
run: qmake; make -j8
|
||||
|
||||
- name: Archive linux-desktop binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-desktop-binary
|
||||
path: src/qdomyos-zwift
|
||||
@@ -442,7 +443,7 @@ jobs:
|
||||
run: cd tst; GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests; cd ..
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: test_results_xml
|
||||
@@ -585,7 +586,7 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '11'
|
||||
java-version: '11.0.23+9'
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
|
||||
@@ -593,14 +594,6 @@ jobs:
|
||||
- name: download 3rd party files for qthttpserver
|
||||
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
|
||||
|
||||
- name: Build qthttpserver
|
||||
run: |
|
||||
cd src/qthttpserver
|
||||
qmake
|
||||
make -j8
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
- name: Set Android NDK 21 && build
|
||||
run: |
|
||||
# Install NDK 21 after GitHub update
|
||||
@@ -622,6 +615,14 @@ jobs:
|
||||
|
||||
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
|
||||
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
|
||||
|
||||
# QTHTTPSERVER must use the same NDK
|
||||
cd src/qthttpserver
|
||||
qmake
|
||||
make -j8
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
|
||||
sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json
|
||||
cat src/android-qdomyos-zwift-deployment-settings.json
|
||||
@@ -630,7 +631,7 @@ jobs:
|
||||
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
|
||||
|
||||
- name: Archive apk binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: fdroid-android-trial
|
||||
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
|
||||
@@ -824,8 +825,24 @@ jobs:
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
cd ..
|
||||
|
||||
- name: Clone vcpkg
|
||||
run: git clone https://github.com/microsoft/vcpkg.git
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Bootstrap vcpkg
|
||||
run: .\vcpkg\bootstrap-vcpkg.bat
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
.\vcpkg\vcpkg install protobuf protobuf-c abseil
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
@@ -839,6 +856,7 @@ jobs:
|
||||
cp ../../windows/*.py .
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
mkdir python
|
||||
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
|
||||
@@ -849,7 +867,10 @@ jobs:
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Build without python
|
||||
run: |
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
@@ -860,10 +881,11 @@ jobs:
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
@@ -883,14 +905,14 @@ jobs:
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-binary
|
||||
path: windows-msvc2019-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-binary-no-python
|
||||
path: windows-msvc2019-binary-no-python.zip
|
||||
@@ -930,6 +952,9 @@ jobs:
|
||||
repository: qt-labs/qthttpserver
|
||||
path: "src/qthttpserver"
|
||||
|
||||
- name: Install CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
@@ -973,8 +998,24 @@ jobs:
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
cd ..
|
||||
|
||||
- name: Clone vcpkg
|
||||
run: git clone https://github.com/microsoft/vcpkg.git
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Bootstrap vcpkg
|
||||
run: .\vcpkg\bootstrap-vcpkg.bat
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
.\vcpkg\vcpkg install protobuf protobuf-c abseil
|
||||
working-directory: ${{ runner.workspace }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination . -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\lib\*.* -Destination src/ -Verbose
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\include\* -Destination src/ -Recurse -Verbose
|
||||
cd src
|
||||
echo "#define AISERVER" >> aiserver.h
|
||||
cd ..
|
||||
@@ -993,6 +1034,7 @@ jobs:
|
||||
cp ../../windows/zwift-workout-ai-server.py zwift-workout.py
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
Copy-Item -Path ${{ runner.workspace }}\vcpkg\installed\x64-windows\bin\*.* -Destination . -Verbose
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
@@ -1006,11 +1048,179 @@ jobs:
|
||||
run: Compress-Archive src/debug/output windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-msvc2019-ai-server-binary
|
||||
path: windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
raspberry-pi-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Secrets
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
echo "#define LICENSE" >> secret.h
|
||||
cd ..
|
||||
|
||||
- name: Build for Raspberry Pi
|
||||
uses: docker://arm32v7/debian:bullseye
|
||||
with:
|
||||
args: >
|
||||
bash -c "
|
||||
set -ex &&
|
||||
apt-get update &&
|
||||
apt-get install -y build-essential git cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
|
||||
export QT_SELECT=qt5 &&
|
||||
export PATH=/usr/lib/qt5/bin:$PATH &&
|
||||
cd /github/workspace &&
|
||||
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
|
||||
cat qdomyos-zwift.pro &&
|
||||
qmake &&
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-binary
|
||||
path: src/qdomyos-zwift
|
||||
|
||||
raspberry-pi-build-and-image-64bit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Secrets
|
||||
run: |
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
|
||||
echo "#define LICENSE" >> secret.h
|
||||
cd ..
|
||||
|
||||
- name: Build for Raspberry Pi 64-bit
|
||||
uses: docker://arm64v8/debian:bullseye
|
||||
with:
|
||||
args: >
|
||||
bash -c "
|
||||
set -ex &&
|
||||
apt-get update &&
|
||||
apt-get install -y build-essential git cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 &&
|
||||
export QT_SELECT=qt5 &&
|
||||
export PATH=/usr/lib/qt5/bin:$PATH &&
|
||||
cd /github/workspace &&
|
||||
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
|
||||
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
|
||||
cat qdomyos-zwift.pro &&
|
||||
qmake &&
|
||||
make -j$(nproc)
|
||||
"
|
||||
|
||||
- name: Archive Raspberry Pi binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-64bit-binary
|
||||
path: src/qdomyos-zwift
|
||||
|
||||
- name: Download and expand Raspberry Pi OS image
|
||||
run: |
|
||||
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-05-03/2023-05-03-raspios-bullseye-arm64-lite.img.xz
|
||||
xz -d 2023-05-03-raspios-bullseye-arm64-lite.img.xz
|
||||
ORIGINAL_SIZE=$(stat -c %s 2023-05-03-raspios-bullseye-arm64-lite.img)
|
||||
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
|
||||
truncate -s $NEW_SIZE 2023-05-03-raspios-bullseye-arm64-lite.img
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y parted
|
||||
sudo parted 2023-05-03-raspios-bullseye-arm64-lite.img resizepart 2 100%
|
||||
|
||||
- name: Mount Raspberry Pi image
|
||||
run: |
|
||||
sudo apt-get install -y kpartx qemu-user-static
|
||||
LOOP_DEVICE=$(sudo losetup -f --show 2023-05-03-raspios-bullseye-arm64-lite.img)
|
||||
echo "Loop device is $LOOP_DEVICE"
|
||||
sudo kpartx -av $LOOP_DEVICE
|
||||
sudo mkdir -p /mnt/raspbian
|
||||
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
|
||||
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
|
||||
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
|
||||
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
|
||||
|
||||
- name: Install Qt and dependencies on Raspberry Pi image
|
||||
run: |
|
||||
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
|
||||
apt-get update
|
||||
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
|
||||
EOF
|
||||
|
||||
- name: Copy binary to Raspberry Pi image
|
||||
run: |
|
||||
sudo cp src/qdomyos-zwift /mnt/raspbian/home/pi/
|
||||
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
|
||||
|
||||
- name: Setup auto-start for qdomyos-zwift
|
||||
run: |
|
||||
echo '[Unit]
|
||||
Description=QDomyos-Zwift
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/pi/qdomyos-zwift
|
||||
User=pi
|
||||
Environment=DISPLAY=:0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target' | sudo tee /mnt/raspbian/etc/systemd/system/qdomyos-zwift.service
|
||||
sudo chroot /mnt/raspbian systemctl enable qdomyos-zwift.service
|
||||
|
||||
- name: Unmount Raspberry Pi image
|
||||
run: |
|
||||
sudo umount /mnt/raspbian
|
||||
sudo kpartx -d ${{ env.LOOP_DEVICE }}
|
||||
sudo losetup -d ${{ env.LOOP_DEVICE }}
|
||||
|
||||
- name: Compress modified Raspberry Pi image
|
||||
run: |
|
||||
xz -z 2023-05-03-raspios-bullseye-arm64-lite.img
|
||||
|
||||
- name: Upload Raspberry Pi image as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: raspberry-pi-64bit-image
|
||||
path: 2023-05-03-raspios-bullseye-arm64-lite.img.xz
|
||||
|
||||
upload_to_release:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -1018,7 +1228,7 @@ jobs:
|
||||
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build] # Specify the job dependencies
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Update nightly release
|
||||
uses: andelf/nightly-release@main
|
||||
env:
|
||||
@@ -1044,3 +1254,9 @@ jobs:
|
||||
windows-binary-no-python/*
|
||||
windows-binary/*
|
||||
fdroid-android-trial/*
|
||||
raspberry-pi-binary/*
|
||||
raspberry-pi-64bit-binary/*
|
||||
2023-05-03-raspios-bullseye-arm64-lite.img.xz
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -152,6 +152,8 @@
|
||||
871189192893CECF006A04D1 /* libqavfmediaplayer.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 871189182893CECC006A04D1 /* libqavfmediaplayer.a */; };
|
||||
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */; };
|
||||
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */; };
|
||||
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */; };
|
||||
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8715A3E82C75E6DB009BAC05 /* antbike.cpp */; };
|
||||
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A08276BBAF600141463 /* virtualrower.cpp */; };
|
||||
87182A0B276BBB1200141463 /* moc_virtualrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87182A0A276BBB1200141463 /* moc_virtualrower.cpp */; };
|
||||
8718CBA2263063BD004BF4EE /* soleelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8718CB9A263063BC004BF4EE /* soleelliptical.cpp */; };
|
||||
@@ -174,6 +176,8 @@
|
||||
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */; };
|
||||
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */; };
|
||||
8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */; };
|
||||
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */; };
|
||||
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */; };
|
||||
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
|
||||
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
|
||||
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
|
||||
@@ -352,8 +356,12 @@
|
||||
876F9B61275385D8006AE6FA /* moc_fitmetria_fanfit.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 876F9B60275385D8006AE6FA /* moc_fitmetria_fanfit.cpp */; };
|
||||
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
|
||||
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */; };
|
||||
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */; };
|
||||
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008129E876F7008E48B7 /* iconceptelliptical.cpp */; };
|
||||
8775008529E87713008E48B7 /* moc_iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */; };
|
||||
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */; };
|
||||
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B52C98629B00BB1697 /* sportstechelliptical.cpp */; };
|
||||
877A080D2893DC4300C0F0AB /* CoreVideo.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 879F74122893D705009A64C8 /* CoreVideo.framework */; };
|
||||
877A7609269D8E9F0024DD2C /* WebKit.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 877A7608269D8E9F0024DD2C /* WebKit.framework */; };
|
||||
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */; };
|
||||
@@ -916,6 +924,9 @@
|
||||
871235BD26B297660012D0F2 /* kingsmithr1protreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = kingsmithr1protreadmill.h; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h; sourceTree = "<group>"; };
|
||||
871235BE26B297660012D0F2 /* kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = kingsmithr1protreadmill.cpp; path = ../src/devices/kingsmithr1protreadmill/kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
|
||||
871235C026B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr1protreadmill.cpp; sourceTree = "<group>"; };
|
||||
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_antbike.cpp; sourceTree = "<group>"; };
|
||||
8715A3E82C75E6DB009BAC05 /* antbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = antbike.cpp; path = ../src/devices/antbike/antbike.cpp; sourceTree = "<group>"; };
|
||||
8715A3E92C75E6DB009BAC05 /* antbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = antbike.h; path = ../src/devices/antbike/antbike.h; sourceTree = "<group>"; };
|
||||
87182A07276BBAF600141463 /* virtualrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = virtualrower.h; path = ../src/virtualdevices/virtualrower.h; sourceTree = "<group>"; };
|
||||
87182A08276BBAF600141463 /* virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = virtualrower.cpp; path = ../src/virtualdevices/virtualrower.cpp; sourceTree = "<group>"; };
|
||||
87182A0A276BBB1200141463 /* moc_virtualrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_virtualrower.cpp; sourceTree = "<group>"; };
|
||||
@@ -951,6 +962,9 @@
|
||||
8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_QTelnet.cpp; sourceTree = "<group>"; };
|
||||
8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtelnetbike.cpp; sourceTree = "<group>"; };
|
||||
8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "qdomyoszwift-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
|
||||
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbelliptical.cpp; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.cpp; sourceTree = "<group>"; };
|
||||
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbelliptical.h; path = ../src/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h; sourceTree = "<group>"; };
|
||||
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/devices/faketreadmill/faketreadmill.h; sourceTree = "<group>"; };
|
||||
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/devices/faketreadmill/faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1222,9 +1236,15 @@
|
||||
8772A0E425E43AD90080718C /* trxappgateusbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = trxappgateusbbike.h; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.h; sourceTree = "<group>"; };
|
||||
8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbbike.cpp; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbbike.cpp; sourceTree = "<group>"; };
|
||||
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deerruntreadmill.cpp; sourceTree = "<group>"; };
|
||||
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = deerruntreadmill.h; path = ../src/devices/deeruntreadmill/deerruntreadmill.h; sourceTree = SOURCE_ROOT; };
|
||||
8775008129E876F7008E48B7 /* iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iconceptelliptical.cpp; path = ../src/devices/iconceptelliptical/iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
8775008229E876F7008E48B7 /* iconceptelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iconceptelliptical.h; path = ../src/devices/iconceptelliptical/iconceptelliptical.h; sourceTree = "<group>"; };
|
||||
8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_iconceptelliptical.cpp; sourceTree = "<group>"; };
|
||||
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportstechelliptical.cpp; sourceTree = "<group>"; };
|
||||
877758B42C98629B00BB1697 /* sportstechelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportstechelliptical.h; path = ../src/devices/sportstechelliptical/sportstechelliptical.h; sourceTree = "<group>"; };
|
||||
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechelliptical.cpp; path = ../src/devices/sportstechelliptical/sportstechelliptical.cpp; sourceTree = "<group>"; };
|
||||
877A7606269D8E0F0024DD2C /* libqtwebview_darwin_debug.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtwebview_darwin_debug.a; path = ../../Qt/5.15.2/ios/plugins/webview/libqtwebview_darwin_debug.a; sourceTree = "<group>"; };
|
||||
877A7608269D8E9F0024DD2C /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
|
||||
877FBA27276E684400F6C0C9 /* bowflextreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bowflextreadmill.cpp; path = ../src/devices/bowflextreadmill/bowflextreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1300,6 +1320,7 @@
|
||||
87A0770E29B641D500A368BF /* wahookickrheadwind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wahookickrheadwind.h; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.h; sourceTree = "<group>"; };
|
||||
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wahookickrheadwind.cpp; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.cpp; sourceTree = "<group>"; };
|
||||
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wahookickrheadwind.cpp; sourceTree = "<group>"; };
|
||||
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier2ad9.h; path = ../src/characteristics/characteristicnotifier2ad9.h; sourceTree = "<group>"; };
|
||||
87A0C4B7262329A600121A76 /* npecablebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = npecablebike.cpp; path = ../src/devices/npecablebike/npecablebike.cpp; sourceTree = "<group>"; };
|
||||
87A0C4B8262329A600121A76 /* cscbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cscbike.h; path = ../src/devices/cscbike/cscbike.h; sourceTree = "<group>"; };
|
||||
87A0C4B9262329A600121A76 /* cscbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cscbike.cpp; path = ../src/devices/cscbike/cscbike.cpp; sourceTree = "<group>"; };
|
||||
@@ -2058,6 +2079,19 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
|
||||
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
|
||||
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
|
||||
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */,
|
||||
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */,
|
||||
877758B42C98629B00BB1697 /* sportstechelliptical.h */,
|
||||
877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */,
|
||||
8715A3E82C75E6DB009BAC05 /* antbike.cpp */,
|
||||
8715A3E92C75E6DB009BAC05 /* antbike.h */,
|
||||
8715A3E62C75E6C9009BAC05 /* moc_antbike.cpp */,
|
||||
872973832C6F13C300D6D9A4 /* nordictrackifitadbelliptical.cpp */,
|
||||
872973842C6F13C400D6D9A4 /* nordictrackifitadbelliptical.h */,
|
||||
872973812C6F13B000D6D9A4 /* moc_nordictrackifitadbelliptical.cpp */,
|
||||
873D3C4B2C296B3700770CB9 /* jumprope.cpp */,
|
||||
873D3C4C2C296B3700770CB9 /* jumprope.h */,
|
||||
873D3C492C296B0100770CB9 /* moc_jumprope.cpp */,
|
||||
@@ -3276,6 +3310,7 @@
|
||||
873824BC27E64707004F1B46 /* moc_resolver.cpp in Compile Sources */,
|
||||
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */,
|
||||
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */,
|
||||
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */,
|
||||
20A50533946A39CBD2C89104 /* bluetoothdevice.cpp in Compile Sources */,
|
||||
87C5F0D126285E7E0067A1B5 /* moc_stagesbike.cpp in Compile Sources */,
|
||||
873824E927E647A8004F1B46 /* mdns.cpp in Compile Sources */,
|
||||
@@ -3325,6 +3360,7 @@
|
||||
8768C8C92BBC11C80099DBE1 /* adb_client.c in Compile Sources */,
|
||||
873063C0259DF2C500DA0F44 /* moc_heartratebelt.cpp in Compile Sources */,
|
||||
DD5ED224478CB82859C61B9F /* fit_buffered_record_mesg_broadcaster.cpp in Compile Sources */,
|
||||
872973852C6F13C400D6D9A4 /* nordictrackifitadbelliptical.cpp in Compile Sources */,
|
||||
87368825259C602800C71C7E /* watchAppStart.swift in Compile Sources */,
|
||||
87BCE6BF29F28F95001F70EB /* moc_ypooelliptical.cpp in Compile Sources */,
|
||||
876ED21925C3E9000065F3DC /* moc_ftmsbike.cpp in Compile Sources */,
|
||||
@@ -3347,6 +3383,7 @@
|
||||
879F16462847E55C00CE4945 /* proformellipticaltrainer.cpp in Compile Sources */,
|
||||
8730A3922B404159007E336D /* zwift_protobuf_layer.swift in Compile Sources */,
|
||||
87917A7728E768D200F8D9AC /* Client.swift in Compile Sources */,
|
||||
872973822C6F13B100D6D9A4 /* moc_nordictrackifitadbelliptical.cpp in Compile Sources */,
|
||||
873824B927E64707004F1B46 /* moc_provider.cpp in Compile Sources */,
|
||||
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */,
|
||||
DF1FD9718B75FA591A7E3D80 /* fit_decode.cpp in Compile Sources */,
|
||||
@@ -3384,6 +3421,7 @@
|
||||
87310B22266FBB78008BA0D6 /* moc_homefitnessbuddy.cpp in Compile Sources */,
|
||||
87958F1B27628D5400124B24 /* moc_elitesterzosmart.cpp in Compile Sources */,
|
||||
8768C8D82BBC12890099DBE1 /* centraldir.c in Compile Sources */,
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */,
|
||||
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
|
||||
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
|
||||
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
|
||||
@@ -3407,6 +3445,7 @@
|
||||
873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */,
|
||||
25F2400F80DAFBD41FE5CC75 /* fit_field.cpp in Compile Sources */,
|
||||
873824E227E647A8004F1B46 /* dns.cpp in Compile Sources */,
|
||||
8715A3EA2C75E6DB009BAC05 /* antbike.cpp in Compile Sources */,
|
||||
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */,
|
||||
8768C9092BBC12B80099DBE1 /* socket_local_server.c in Compile Sources */,
|
||||
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */,
|
||||
@@ -3494,6 +3533,7 @@
|
||||
87C5F0C026285E5F0067A1B5 /* mimepart.cpp in Compile Sources */,
|
||||
47E45EE0BB22C1E4332F1D1D /* trxappgateusbtreadmill.cpp in Compile Sources */,
|
||||
873824BB27E64707004F1B46 /* moc_prober_p.cpp in Compile Sources */,
|
||||
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */,
|
||||
8742C2B227C92C30007D3FA0 /* wahookickrsnapbike.cpp in Compile Sources */,
|
||||
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */,
|
||||
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */,
|
||||
@@ -3558,6 +3598,7 @@
|
||||
8783153C25E8DAFD0007817C /* sportstechbike.cpp in Compile Sources */,
|
||||
873824E527E647A8004F1B46 /* message.cpp in Compile Sources */,
|
||||
210F6A0A7E2FA7CDD3CA0084 /* qdomyoszwift_qml_plugin_import.cpp in Compile Sources */,
|
||||
8715A3E72C75E6C9009BAC05 /* moc_antbike.cpp in Compile Sources */,
|
||||
87062644259480A600D06586 /* APIFetcher.swift in Compile Sources */,
|
||||
8768C8C72BBC11C80099DBE1 /* adb_auth_host.c in Compile Sources */,
|
||||
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */,
|
||||
@@ -3603,6 +3644,7 @@
|
||||
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
|
||||
87C5F0D726285E7E0067A1B5 /* moc_mimefile.cpp in Compile Sources */,
|
||||
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */,
|
||||
877758B62C98629B00BB1697 /* sportstechelliptical.cpp in Compile Sources */,
|
||||
8762D5102601F7EA00F6F049 /* M3iNSQT.cpp in Compile Sources */,
|
||||
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */,
|
||||
8718CBA3263063BD004BF4EE /* templateinfosender.cpp in Compile Sources */,
|
||||
@@ -4026,7 +4068,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4105,7 +4147,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
"-g",
|
||||
@@ -4217,7 +4259,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4298,7 +4340,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
@@ -4444,7 +4486,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4469,7 +4511,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4540,7 +4582,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4561,7 +4603,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4632,7 +4674,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4677,7 +4719,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4746,7 +4788,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 810;
|
||||
CURRENT_PROJECT_VERSION = 920;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4787,7 +4829,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.16;
|
||||
MARKETING_VERSION = 2.18;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
|
||||
@@ -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))
|
||||
61
src/CRC16IBM.h
Normal file
61
src/CRC16IBM.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef CRC16IBM_H
|
||||
#define CRC16IBM_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
|
||||
class CRC16IBM {
|
||||
public:
|
||||
|
||||
// Function to calculate CRC-16 (XMODEM) checksum
|
||||
static quint16 calculateCRC(const QByteArray &data) {
|
||||
quint16 crc = 0xFFFF; // Initial value
|
||||
|
||||
for (char byte : data) {
|
||||
quint8 index = (crc >> 8) ^ static_cast<quint8>(byte);
|
||||
crc = (crc << 8) ^ crc16Table[index];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
private:
|
||||
// Precomputed CRC-16-IBM table
|
||||
static constexpr quint16 crc16Table[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
#endif // CRC16IBM_H
|
||||
115
src/EventHandler.h
Normal file
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
|
||||
550
src/Home.qml
550
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
|
||||
@@ -86,6 +86,32 @@ HomeForm{
|
||||
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
|
||||
}
|
||||
|
||||
property var locationServiceRequsted: false
|
||||
MessageDialog {
|
||||
text: "Permissions Required"
|
||||
informativeText: "QZ requires both Bluetooth and Location Services to be enabled.\nLocation Services are necessary on Android to allow the app to find Bluetooth devices.\nThe GPS will not be used.\n\nWould you like to enable them?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {locationServiceRequsted = true; rootItem.enableLocationServices()}
|
||||
visible: !rootItem.locationServices() && !locationServiceRequsted
|
||||
}
|
||||
MessageDialog {
|
||||
text: "Restart the app"
|
||||
informativeText: "To apply the changes, you need to restart the app.\nWould you like to do that now?"
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: Qt.callLater(Qt.quit)
|
||||
onNoClicked: this.visible = false;
|
||||
visible: locationServiceRequsted
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 200; running: true; repeat: false
|
||||
onTriggered: {
|
||||
if(rootItem.firstRun()) {
|
||||
stackView.push("Wizard.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function inner_stop() {
|
||||
stop_clicked();
|
||||
rootItem.save_screenshot();
|
||||
@@ -101,266 +127,298 @@ HomeForm{
|
||||
}
|
||||
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
|
||||
|
||||
Component.onCompleted: { console.log("completed"); }
|
||||
Component.onCompleted: {
|
||||
console.log("home.qml completed");
|
||||
}
|
||||
|
||||
GridView {
|
||||
GridView {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175 * settings.ui_zoom / 100
|
||||
cellHeight: 130 * settings.ui_zoom / 100
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
|
||||
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
|
||||
id: gridView
|
||||
objectName: "gridview"
|
||||
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
|
||||
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
|
||||
Screen.onPrimaryOrientationChanged:{
|
||||
if(OS_VERSION === "Android")
|
||||
gridView.leftMargin = (Screen.width % cellWidth) / 2;
|
||||
else
|
||||
gridView.leftMargin = (parent.width % cellWidth) / 2;
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 170 * settings.ui_zoom / 100
|
||||
height: 125 * settings.ui_zoom / 100
|
||||
|
||||
visible: visibleItem
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
|
||||
Behavior on x {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
SequentialAnimation on rotation {
|
||||
NumberAnimation { to: 2; duration: 60 }
|
||||
NumberAnimation { to: -2; duration: 120 }
|
||||
NumberAnimation { to: 0; duration: 60 }
|
||||
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
|
||||
loops: Animation.Infinite; alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "active"; when: loc.currentId === gridId && window.lockTiles
|
||||
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
|
||||
}
|
||||
|
||||
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
|
||||
|
||||
Rectangle {
|
||||
width: 168 * settings.ui_zoom / 100
|
||||
height: 123 * settings.ui_zoom / 100
|
||||
radius: 3
|
||||
border.width: 1
|
||||
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
|
||||
color: settings.theme_tile_background_color
|
||||
id: rect
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
visible: settings.theme_tile_shadow_enabled
|
||||
anchors.fill: rect
|
||||
cached: true
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: settings.theme_tile_shadow_color
|
||||
source: rect
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toggleIconTimer
|
||||
interval: 500; running: true; repeat: true
|
||||
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
bottom: id1.bottom
|
||||
}
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
source: icon
|
||||
visible: settings.theme_tile_icon_enabled && !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: valueFontColor
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: valueFontSize * settings.ui_zoom / 100
|
||||
font.bold: true
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "secondLine"
|
||||
id: secondLineText
|
||||
color: "white"
|
||||
y: myValue.bottom
|
||||
anchors {
|
||||
top: myValue.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: secondLine
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
|
||||
font.bold: false
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
id: myText
|
||||
anchors {
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
font.pointSize: labelFontSize
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55 * settings.ui_zoom / 100
|
||||
anchors.topMargin: 20 * settings.ui_zoom / 100
|
||||
visible: !largeButton
|
||||
}
|
||||
RoundButton {
|
||||
objectName: minusName
|
||||
autoRepeat: true
|
||||
text: "-"
|
||||
onClicked: minus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: plusName
|
||||
text: "+"
|
||||
onClicked: plus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: identificator
|
||||
text: largeButtonLabel
|
||||
onClicked: largeButton_clicked(objectName)
|
||||
visible: largeButton
|
||||
anchors.fill: rect
|
||||
background: Rectangle {
|
||||
color: largeButtonColor
|
||||
radius: 20
|
||||
}
|
||||
font.pointSize: 20 * settings.ui_zoom / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: Item {
|
||||
id: footerItem
|
||||
width: parent.width
|
||||
height: footerHeight
|
||||
property real footerHeight: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
|
||||
property real minHeight: parent.height / 4
|
||||
property real maxHeight: parent.height * 3 / 4
|
||||
anchors.bottom: parent.bottom
|
||||
clip: true
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
width: parent.width / 5
|
||||
height: 10
|
||||
color: "#9C27B0"
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.fill: parent
|
||||
cellWidth: 175 * settings.ui_zoom / 100
|
||||
cellHeight: 130 * settings.ui_zoom / 100
|
||||
focus: true
|
||||
model: appModel
|
||||
leftMargin: { if(OS_VERSION === "Android") (Screen.width % cellWidth) / 2; else (parent.width % cellWidth) / 2; }
|
||||
anchors.topMargin: (!window.lockTiles ? rootItem.topBarHeight + 30 : 0)
|
||||
id: gridView
|
||||
objectName: "gridview"
|
||||
onMovementEnded: { headerToolbar.visible = (contentY == 0) || window.lockTiles; }
|
||||
Screen.orientationUpdateMask: Qt.LandscapeOrientation | Qt.PortraitOrientation
|
||||
Screen.onPrimaryOrientationChanged:{
|
||||
if(OS_VERSION === "Android")
|
||||
gridView.leftMargin = (Screen.width % cellWidth) / 2;
|
||||
else
|
||||
gridView.leftMargin = (parent.width % cellWidth) / 2;
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Canvas {
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.strokeStyle = "#FFFFFF";
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, (i + 1) * parent.height / 4);
|
||||
ctx.lineTo(parent.width, (i + 1) * parent.height / 4);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// highlight: Rectangle {
|
||||
// width: 150
|
||||
// height: 150
|
||||
// color: "lightsteelblue"
|
||||
// }
|
||||
delegate: Item {
|
||||
id: id1
|
||||
width: 170 * settings.ui_zoom / 100
|
||||
height: 125 * settings.ui_zoom / 100
|
||||
MouseArea {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
|
||||
visible: visibleItem
|
||||
Component.onCompleted: console.log("completed " + objectName)
|
||||
property real startY: 0
|
||||
property real startHeight: 0
|
||||
|
||||
Behavior on x {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
onPressed: {
|
||||
startY = mouseY
|
||||
startHeight = footerItem.height
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: id1.state != "active"
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
SequentialAnimation on rotation {
|
||||
NumberAnimation { to: 2; duration: 60 }
|
||||
NumberAnimation { to: -2; duration: 120 }
|
||||
NumberAnimation { to: 0; duration: 60 }
|
||||
running: loc.currentId !== -1 && id1.state !== "active" && window.lockTiles
|
||||
loops: Animation.Infinite; alwaysRunToEnd: true
|
||||
}
|
||||
|
||||
states: State {
|
||||
name: "active"; when: loc.currentId === gridId && window.lockTiles
|
||||
PropertyChanges { target: id1; x: loc.mouseX - gridView.x - width/2; y: loc.mouseY - gridView.y - height/2; scale: 0.5; z: 10 }
|
||||
}
|
||||
|
||||
transitions: Transition { NumberAnimation { property: "scale"; duration: 200} }
|
||||
|
||||
Rectangle {
|
||||
width: 168 * settings.ui_zoom / 100
|
||||
height: 123 * settings.ui_zoom / 100
|
||||
radius: 3
|
||||
border.width: 1
|
||||
border.color: (settings.theme_tile_shadow_enabled ? settings.theme_tile_shadow_color : settings.theme_tile_background_color)
|
||||
color: settings.theme_tile_background_color
|
||||
id: rect
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
visible: settings.theme_tile_shadow_enabled
|
||||
anchors.fill: rect
|
||||
cached: true
|
||||
horizontalOffset: 3
|
||||
verticalOffset: 3
|
||||
radius: 8.0
|
||||
samples: 16
|
||||
color: settings.theme_tile_shadow_color
|
||||
source: rect
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: toggleIconTimer
|
||||
interval: 500; running: true; repeat: true
|
||||
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = settings.theme_tile_icon_enabled && !largeButton; }
|
||||
}
|
||||
|
||||
Image {
|
||||
id: myIcon
|
||||
x: 5
|
||||
anchors {
|
||||
bottom: id1.bottom
|
||||
onMouseYChanged: {
|
||||
if (pressed) {
|
||||
var newHeight = Math.max(footerItem.minHeight, Math.min(footerItem.maxHeight, startHeight + startY - mouseY))
|
||||
footerItem.footerHeight = newHeight
|
||||
}
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
source: icon
|
||||
visible: settings.theme_tile_icon_enabled && !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "value"
|
||||
id: myValue
|
||||
color: valueFontColor
|
||||
y: 0
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: value
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: valueFontSize * settings.ui_zoom / 100
|
||||
font.bold: true
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
objectName: "secondLine"
|
||||
id: secondLineText
|
||||
color: "white"
|
||||
y: myValue.bottom
|
||||
anchors {
|
||||
top: myValue.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
text: secondLine
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.pointSize: settings.theme_tile_secondline_textsize * settings.ui_zoom / 100
|
||||
font.bold: false
|
||||
visible: !largeButton
|
||||
}
|
||||
Text {
|
||||
id: myText
|
||||
anchors {
|
||||
top: myIcon.top
|
||||
}
|
||||
font.bold: true
|
||||
font.pointSize: labelFontSize
|
||||
color: "white"
|
||||
text: name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 55 * settings.ui_zoom / 100
|
||||
anchors.topMargin: 20 * settings.ui_zoom / 100
|
||||
visible: !largeButton
|
||||
}
|
||||
RoundButton {
|
||||
objectName: minusName
|
||||
autoRepeat: true
|
||||
text: "-"
|
||||
onClicked: minus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: plusName
|
||||
text: "+"
|
||||
onClicked: plus_clicked(objectName)
|
||||
visible: writable && !largeButton
|
||||
anchors.top: myValue.top
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2
|
||||
width: 48 * settings.ui_zoom / 100
|
||||
height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
RoundButton {
|
||||
autoRepeat: true
|
||||
objectName: identificator
|
||||
text: largeButtonLabel
|
||||
onClicked: largeButton_clicked(objectName)
|
||||
visible: largeButton
|
||||
anchors.fill: rect
|
||||
background: Rectangle {
|
||||
color: largeButtonColor
|
||||
radius: 20
|
||||
}
|
||||
font.pointSize: 20 * settings.ui_zoom / 100
|
||||
//width: 48 * settings.ui_zoom / 100
|
||||
//height: 48 * settings.ui_zoom / 100
|
||||
}
|
||||
|
||||
/*MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: parent.GridView.view.currentIndex = index
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
footer:
|
||||
Item {
|
||||
width: parent.width
|
||||
height: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
|
||||
anchors.top: gridView.bottom
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Rectangle {
|
||||
id: chartFooterRectangle
|
||||
visible: rootItem.chartFooterVisible
|
||||
anchors.fill: parent
|
||||
ChartFooter {
|
||||
anchors.fill: parent
|
||||
visible: rootItem.chartFooterVisible
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.fill: parent
|
||||
// Removed Timer, Play/Pause/Resume is now done via Homeform.cpp
|
||||
/*
|
||||
Timer {
|
||||
id: pauseTimer
|
||||
interval: 1000; running: true; repeat: true
|
||||
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
|
||||
videoPlaybackHalf.play() :
|
||||
videoPlaybackHalf.pause()) } }
|
||||
}
|
||||
*/
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
//videoPlaybackHalf.playbackRate = rootItem.videoRate
|
||||
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: chartFooterRectangle
|
||||
visible: rootItem.chartFooterVisible
|
||||
anchors.top: dragHandle.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
ChartFooter {
|
||||
anchors.fill: parent
|
||||
visible: rootItem.chartFooterVisible
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.top: dragHandle.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id: videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property int currentId: -1 // Original position in model
|
||||
property int newIndex // Current Position in model
|
||||
|
||||
1428
src/Wizard.qml
Normal file
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.61" android:versionCode="814" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.1" android:versionCode="908" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
@@ -79,7 +79,7 @@
|
||||
android:name=".ForegroundService"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="true"></service>
|
||||
android:exported="false"></service>
|
||||
<service
|
||||
android:name=".WearableMessageListenerService"
|
||||
android:enabled="true"
|
||||
@@ -98,6 +98,7 @@
|
||||
|
||||
<service android:name="com.cgutman.androidremotedebugger.service.ShellService"
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="connectedDevice"
|
||||
android:exported="false" >
|
||||
</service>
|
||||
|
||||
@@ -110,6 +111,12 @@
|
||||
android:value="ocr" />
|
||||
|
||||
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
|
||||
|
||||
<receiver android:name=".MediaButtonReceiver" android:exported="true" android:permission="android.permission.MODIFY_AUDIO_SETTINGS">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.VOLUME_CHANGED_ACTION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
|
||||
@@ -118,6 +125,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_MEDIA_BUTTON" />
|
||||
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
|
||||
@@ -128,6 +136,7 @@
|
||||
<uses-permission android:name="com.android.vending.BILLING"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
@@ -18,7 +18,6 @@ repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://dl.bintray.com/rvalerio/maven' }
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
@@ -28,7 +27,7 @@ def amazon = System.getenv('AMAZON')
|
||||
println(amazon)
|
||||
|
||||
dependencies {
|
||||
compile 'com.rvalerio:fgchecker:1.1.0'
|
||||
implementation "androidx.core:core:1.12.0"
|
||||
implementation "androidx.core:core-ktx:1.12.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
|
||||
|
||||
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.
@@ -36,6 +36,8 @@ import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -258,8 +260,12 @@ public class ChannelService extends Service {
|
||||
private void doBindAntRadioService() {
|
||||
if (BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
|
||||
|
||||
// Start listing for channel available intents
|
||||
registerReceiver(mChannelProviderStateChangedReceiver, new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED), Context.RECEIVER_NOT_EXPORTED);
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
mChannelProviderStateChangedReceiver,
|
||||
new IntentFilter(AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED),
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
|
||||
// Creating the intent and calling context.bindService() is handled by
|
||||
// the static bindService() method in AntService
|
||||
|
||||
@@ -9,6 +9,8 @@ import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.util.Log;
|
||||
|
||||
public class ForegroundService extends Service {
|
||||
public static final String CHANNEL_ID = "ForegroundServiceChannel";
|
||||
@@ -32,8 +34,18 @@ public class ForegroundService extends Service {
|
||||
.setSmallIcon(R.drawable.icon)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
startForeground(1, notification, serviceType);
|
||||
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(1, notification, serviceType);
|
||||
} else {
|
||||
startForeground(1, notification);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
//do heavy work on a background thread
|
||||
//stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.Toast;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -48,12 +49,24 @@ public class Garmin {
|
||||
|
||||
private static Integer HR = 0;
|
||||
private static Integer FootCad = 0;
|
||||
private static Double Speed = 0.0;
|
||||
private static Integer Power = 0;
|
||||
|
||||
public static int getHR() {
|
||||
Log.d(TAG, "getHR " + HR);
|
||||
return HR;
|
||||
}
|
||||
|
||||
public static int getPower() {
|
||||
Log.d(TAG, "getPower " + Power);
|
||||
return Power;
|
||||
}
|
||||
|
||||
public static double getSpeed() {
|
||||
Log.d(TAG, "getSpeed " + Speed);
|
||||
return Speed;
|
||||
}
|
||||
|
||||
public static int getFootCad() {
|
||||
Log.d(TAG, "getFootCad " + FootCad);
|
||||
return FootCad;
|
||||
@@ -152,7 +165,12 @@ public class Garmin {
|
||||
synchronized (receiverToWrapper) {
|
||||
receiverToWrapper.put(receiver, wrappedRecv);
|
||||
}
|
||||
return super.registerReceiver(wrappedRecv, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
return ContextCompat.registerReceiver(
|
||||
super.getBaseContext(),
|
||||
wrappedRecv,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,13 +239,21 @@ public class Garmin {
|
||||
if (status == ConnectIQ.IQMessageStatus.SUCCESS) {
|
||||
//MessageHandler.getInstance().handleMessageFromWatchUsingCIQ(message, status, context);
|
||||
Log.d(TAG, "onMessageReceived, status: " + status.toString() + message.get(0));
|
||||
String var[] = message.toArray()[0].toString().split(",");
|
||||
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 1) {
|
||||
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
try {
|
||||
String var[] = message.toArray()[0].toString().split(",");
|
||||
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 1) {
|
||||
FootCad = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
if(var.length > 2) {
|
||||
Power = Integer.parseInt(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
Speed = Double.parseDouble(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "HR " + HR);
|
||||
Log.d(TAG, "FootCad " + FootCad);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Processing error", e);
|
||||
}
|
||||
Log.d(TAG, "HR " + HR);
|
||||
Log.d(TAG, "FootCad " + FootCad);
|
||||
} else {
|
||||
Log.d(TAG, "onMessageReceived error, status: " + status.toString());
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* This class is used for talking to hid of the dongle, connecting, disconnencting and enumerating the devices.
|
||||
@@ -88,7 +89,12 @@ public class HidBridge {
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent mPermissionIntent = PendingIntent.getBroadcast(_context, 0, new Intent(ACTION_USB_PERMISSION), flags);
|
||||
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
|
||||
_context.registerReceiver(mUsbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
ContextCompat.registerReceiver(
|
||||
_context,
|
||||
mUsbReceiver,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
|
||||
_usbManager.requestPermission(_usbDevice, mPermissionIntent);
|
||||
Log("Found the device");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
45
src/android/src/MediaButtonReceiver.java
Normal file
45
src/android/src/MediaButtonReceiver.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
private static MediaButtonReceiver instance;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("MediaButtonReceiver", "Received intent: " + intent.toString());
|
||||
String intentAction = intent.getAction();
|
||||
if ("android.media.VOLUME_CHANGED_ACTION".equals(intentAction)) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
||||
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
|
||||
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
|
||||
|
||||
Log.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
|
||||
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
|
||||
}
|
||||
}
|
||||
|
||||
private native void nativeOnMediaButtonEvent(int prev, int current, int max);
|
||||
|
||||
public static void registerReceiver(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new MediaButtonReceiver();
|
||||
}
|
||||
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
|
||||
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
|
||||
Log.d("MediaButtonReceiver", "registerReceiver");
|
||||
}
|
||||
|
||||
public static void unregisterReceiver(Context context) {
|
||||
if (instance != null) {
|
||||
context.unregisterReceiver(instance);
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,8 @@ import android.graphics.Rect;
|
||||
import android.graphics.Point;
|
||||
|
||||
import androidx.core.util.Pair;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
|
||||
public class ScreenCaptureService extends Service {
|
||||
|
||||
@@ -299,8 +301,18 @@ public class ScreenCaptureService extends Service {
|
||||
if (isStartCommand(intent)) {
|
||||
// create notification
|
||||
Pair<Integer, Notification> notification = NotificationUtils.getNotification(this);
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
startForeground(notification.first, notification.second, serviceType);
|
||||
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(notification.first, notification.second, serviceType);
|
||||
} else {
|
||||
startForeground(notification.first, notification.second);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
// start projection
|
||||
int resultCode = intent.getIntExtra(RESULT_CODE, Activity.RESULT_CANCELED);
|
||||
Intent data = intent.getParcelableExtra(DATA);
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.app.Service;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.hoho.android.usbserial.driver.CdcAcmSerialDriver;
|
||||
import com.hoho.android.usbserial.driver.Ch34xSerialDriver;
|
||||
@@ -68,7 +69,12 @@ public class Usbserial {
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent("org.cagnulen.qdomyoszwift.USB_PERMISSION"), flags);
|
||||
IntentFilter filter = new IntentFilter("org.cagnulen.qdomyoszwift.USB_PERMISSION");
|
||||
context.registerReceiver(usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
usbReceiver,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
manager.requestPermission(driver.getDevice(), permissionIntent);
|
||||
for(int i=0; i<5000; i++) {
|
||||
if(granted[0] != null) break;
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
public class ShellService extends Service implements DeviceConnectionListener {
|
||||
|
||||
@@ -101,11 +102,20 @@ public class ShellService extends Service implements DeviceConnectionListener {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (foregroundId == 0) {
|
||||
try {
|
||||
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
|
||||
// If we're not already running in the foreground, use a placeholder
|
||||
// notification until a real connection is established. After connection
|
||||
// establishment, the real notification will replace this one.
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification(), serviceType);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification(), serviceType);
|
||||
} else {
|
||||
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("ForegroundService", "Failed to start foreground service", e);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't restart if we've been killed. We will have already lost our connections
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "characteristicwriteprocessor.h"
|
||||
#include <QSettings>
|
||||
|
||||
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, uint8_t bikeResistanceOffset,
|
||||
CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistanceGain, int8_t bikeResistanceOffset,
|
||||
bluetoothdevice *bike, QObject *parent)
|
||||
: QObject(parent), bikeResistanceOffset(bikeResistanceOffset), bikeResistanceGain(bikeResistanceGain), Bike(bike) {}
|
||||
|
||||
@@ -25,6 +25,7 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
|
||||
double CRRGain = settings.value(QZSettings::CRRGain, QZSettings::default_CRRGain).toDouble();
|
||||
double CWGain = settings.value(QZSettings::CWGain, QZSettings::default_CWGain).toDouble();
|
||||
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
|
||||
|
||||
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
|
||||
QStringLiteral(" enabled ") + force_resistance;
|
||||
@@ -60,9 +61,13 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
|
||||
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
|
||||
// form Zwift
|
||||
if (!((bike *)Bike)->inclinationAvailableByHardware())
|
||||
Bike->setInclination(grade + CRR_offset + CW_offset);
|
||||
// from Zwift
|
||||
if (!((bike *)Bike)->inclinationAvailableByHardware()) {
|
||||
if(zwift_play_emulator)
|
||||
Bike->setInclination(grade);
|
||||
else
|
||||
Bike->setInclination(grade + CRR_offset + CW_offset);
|
||||
}
|
||||
|
||||
emit changeInclination(grade, percentage);
|
||||
|
||||
|
||||
@@ -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,11 +84,32 @@ void bike::changePower(int32_t power) {
|
||||
}
|
||||
}
|
||||
|
||||
double bike::gears() { return m_gears; }
|
||||
double bike::gears() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
|
||||
if(gears_zwift_ratio) {
|
||||
if(m_gears < 1)
|
||||
return 1.0;
|
||||
else if(m_gears > 24)
|
||||
return 24.0;
|
||||
}
|
||||
return m_gears + gears_offset;
|
||||
}
|
||||
void bike::setGears(double gears) {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
|
||||
gears -= gears_offset;
|
||||
qDebug() << "setGears" << gears;
|
||||
if(gears_zwift_ratio && (gears > 24 || gears < 1)) {
|
||||
qDebug() << "new gear value ignored because of gears_zwift_ratio setting!";
|
||||
return;
|
||||
}
|
||||
m_gears = gears;
|
||||
if(homeform::singleton()) {
|
||||
homeform::singleton()->updateGearsValue();
|
||||
}
|
||||
settings.setValue(QZSettings::gears_current_value, m_gears);
|
||||
if (lastRawRequestedResistanceValue != -1) {
|
||||
changeResistance(lastRawRequestedResistanceValue);
|
||||
@@ -306,3 +330,61 @@ uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
|
||||
}
|
||||
return watt;
|
||||
}
|
||||
|
||||
double bike::gearsZwiftRatio() {
|
||||
if(m_gears <= 0)
|
||||
return 0.65;
|
||||
else if(m_gears > 24)
|
||||
return 6;
|
||||
switch((int)m_gears) {
|
||||
case 1:
|
||||
return 0.75;
|
||||
case 2:
|
||||
return 0.87;
|
||||
case 3:
|
||||
return 0.99;
|
||||
case 4:
|
||||
return 1.11;
|
||||
case 5:
|
||||
return 1.23;
|
||||
case 6:
|
||||
return 1.38;
|
||||
case 7:
|
||||
return 1.53;
|
||||
case 8:
|
||||
return 1.68;
|
||||
case 9:
|
||||
return 1.86;
|
||||
case 10:
|
||||
return 2.04;
|
||||
case 11:
|
||||
return 2.22;
|
||||
case 12:
|
||||
return 2.40;
|
||||
case 13:
|
||||
return 2.61;
|
||||
case 14:
|
||||
return 2.82;
|
||||
case 15:
|
||||
return 3.03;
|
||||
case 16:
|
||||
return 3.24;
|
||||
case 17:
|
||||
return 3.49;
|
||||
case 18:
|
||||
return 3.74;
|
||||
case 19:
|
||||
return 3.99;
|
||||
case 20:
|
||||
return 4.24;
|
||||
case 21:
|
||||
return 4.54;
|
||||
case 22:
|
||||
return 4.84;
|
||||
case 23:
|
||||
return 5.14;
|
||||
case 24:
|
||||
return 5.49;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,10 @@ class bike : public bluetoothdevice {
|
||||
uint8_t metrics_override_heartrate() override;
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
double gearsZwiftRatio();
|
||||
void setSpeedLimit(double speed) { m_speedLimit = speed; }
|
||||
double speedLimit() { return m_speedLimit; }
|
||||
virtual bool ifitCompatible() {return false;}
|
||||
|
||||
/**
|
||||
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
|
||||
@@ -58,10 +60,18 @@ class bike : public bluetoothdevice {
|
||||
void changeInclination(double grade, double percentage) override;
|
||||
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
|
||||
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
|
||||
void gearUp() {QSettings settings; setGears(gears() +
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
|
||||
void gearDown() {QSettings settings; setGears(gears() -
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble());}
|
||||
void gearUp() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
setGears(gears() + (gears_zwift_ratio ? 1 :
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
|
||||
}
|
||||
void gearDown() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
setGears(gears() - (gears_zwift_ratio ? 1 :
|
||||
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void bikeStarted();
|
||||
@@ -73,11 +83,9 @@ class bike : public bluetoothdevice {
|
||||
metric RequestedResistance;
|
||||
metric RequestedPelotonResistance;
|
||||
metric RequestedCadence;
|
||||
metric RequestedPower;
|
||||
|
||||
resistance_t requestResistance = -1;
|
||||
double requestInclination = -100;
|
||||
int16_t requestPower = -1;
|
||||
|
||||
bool ergModeSupported = false; // if a bike has this mode supported, when from the virtual bike there is a power
|
||||
// request there is no need to translate in resistance levels
|
||||
|
||||
@@ -18,7 +18,7 @@ bluetooth::bluetooth(const discoveryoptions &options)
|
||||
options.bikeResistanceGain, options.startDiscovery) {}
|
||||
|
||||
bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistance, bool noHeartService,
|
||||
uint32_t pollDeviceTime, bool noConsole, bool testResistance, uint8_t bikeResistanceOffset,
|
||||
uint32_t pollDeviceTime, bool noConsole, bool testResistance, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain, bool startDiscovery) {
|
||||
QSettings settings;
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true"));
|
||||
@@ -107,9 +107,12 @@ void bluetooth::finished() {
|
||||
debug(QStringLiteral("BTLE scanning finished"));
|
||||
|
||||
QSettings settings;
|
||||
bool antbike =
|
||||
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
|
||||
QString nordictrack_2950_ip =
|
||||
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
|
||||
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
|
||||
QString proform_elliptical_ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
|
||||
bool fake_bike =
|
||||
settings.value(QZSettings::applewatch_fakedevice, QZSettings::default_applewatch_fakedevice).toBool();
|
||||
bool fakedevice_elliptical =
|
||||
@@ -118,7 +121,7 @@ void bluetooth::finished() {
|
||||
bool fakedevice_treadmill =
|
||||
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
|
||||
// wifi devices on windows
|
||||
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill) {
|
||||
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill || !proform_elliptical_ip.isEmpty() || antbike) {
|
||||
// faking a bluetooth device
|
||||
qDebug() << "faking a bluetooth device for nordictrack_2950_ip";
|
||||
deviceDiscovered(QBluetoothDeviceInfo());
|
||||
@@ -164,10 +167,14 @@ void bluetooth::finished() {
|
||||
bool fitmetriaFanfitFound =
|
||||
!settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
|
||||
|
||||
bool zwiftDeviceFound =
|
||||
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
|
||||
|
||||
if ((!heartRateBeltFound && !heartRateBeltAvaiable()) || (!ftmsAccessoryFound && !ftmsAccessoryAvaiable()) ||
|
||||
(!cscFound && !cscSensorAvaiable()) || (!powerSensorFound && !powerSensorAvaiable()) ||
|
||||
(!eliteRizerFound && !eliteRizerAvaiable()) || (!eliteSterzoSmartFound && !eliteSterzoSmartAvaiable()) ||
|
||||
(!fitmetriaFanfitFound && !fitmetriaFanfitAvaiable())) {
|
||||
(!fitmetriaFanfitFound && !fitmetriaFanfitAvaiable()) ||
|
||||
(!zwiftDeviceFound && !zwiftDeviceAvaiable())) {
|
||||
|
||||
// force heartRateBelt off
|
||||
forceHeartBeltOffForTimeout = true;
|
||||
@@ -274,6 +281,20 @@ bool bluetooth::fitmetriaFanfitAvaiable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bluetooth::zwiftDeviceAvaiable() {
|
||||
|
||||
uint8_t count = 0;
|
||||
Q_FOREACH (QBluetoothDeviceInfo b, devices) {
|
||||
if (b.name().toUpper().startsWith("ZWIFT ")) {
|
||||
count++;
|
||||
if(count >= 2)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool bluetooth::powerSensorAvaiable() {
|
||||
|
||||
QSettings settings;
|
||||
@@ -371,6 +392,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
settings.value(QZSettings::ftms_accessory_name, QZSettings::default_ftms_accessory_name).toString();
|
||||
bool heartRateBeltFound = heartRateBeltName.startsWith(QStringLiteral("Disabled"));
|
||||
bool ftmsAccessoryFound = ftmsAccessoryName.startsWith(QStringLiteral("Disabled"));
|
||||
bool zwiftDeviceFound =
|
||||
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
|
||||
bool fitmetriaFanfitFound =
|
||||
!settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
|
||||
bool toorx_ftms = settings.value(QZSettings::toorx_ftms, QZSettings::default_toorx_ftms).toBool();
|
||||
@@ -383,6 +406,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
settings.value(QZSettings::enerfit_SPX_9500, QZSettings::default_enerfit_SPX_9500).toBool() ||
|
||||
settings.value(QZSettings::toorx_srx_3500, QZSettings::default_toorx_srx_3500).toBool() ||
|
||||
settings.value(QZSettings::hop_sport_hs_090h_bike, QZSettings::default_hop_sport_hs_090h_bike).toBool() ||
|
||||
settings.value(QZSettings::toorx_bike_srx_500, QZSettings::default_toorx_bike_srx_500).toBool() ||
|
||||
settings.value(QZSettings::hertz_xr_770, QZSettings::default_hertz_xr_770).toBool()) &&
|
||||
!toorx_ftms;
|
||||
bool snode_bike = settings.value(QZSettings::snode_bike, QZSettings::default_snode_bike).toBool();
|
||||
@@ -422,9 +446,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
QString proformtdf1ip = settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString();
|
||||
QString proformtreadmillip =
|
||||
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
|
||||
bool antbike_setting =
|
||||
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
|
||||
QString nordictrack_2950_ip =
|
||||
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
|
||||
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
|
||||
QString proform_elliptical_ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
|
||||
QString computrainerSerialPort =
|
||||
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
|
||||
QString csaferowerSerialPort = settings.value(QZSettings::csafe_rower, QZSettings::default_csafe_rower).toString();
|
||||
@@ -457,6 +484,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
|
||||
fitmetriaFanfitFound = fitmetriaFanfitAvaiable();
|
||||
}
|
||||
if (!zwiftDeviceFound) {
|
||||
|
||||
zwiftDeviceFound = zwiftDeviceAvaiable();
|
||||
}
|
||||
if (!ftmsAccessoryFound) {
|
||||
|
||||
ftmsAccessoryFound = ftmsAccessoryAvaiable();
|
||||
@@ -588,7 +619,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
#endif
|
||||
|
||||
bool searchDevices = (heartRateBeltFound && ftmsAccessoryFound && cscFound && powerSensorFound && eliteRizerFound &&
|
||||
eliteSterzoSmartFound && fitmetriaFanfitFound) ||
|
||||
eliteSterzoSmartFound && fitmetriaFanfitFound && zwiftDeviceFound) ||
|
||||
forceHeartBeltOffForTimeout;
|
||||
|
||||
if (searchDevices) {
|
||||
@@ -622,6 +653,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit deviceConnected(b);
|
||||
connect(fakeBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
|
||||
connect(fakeBike, &fakebike::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
connect(fakeBike, &fakebike::debug, this, &bluetooth::debug);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
|
||||
// #358
|
||||
@@ -636,6 +668,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(fakeElliptical, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
connect(fakeElliptical, &fakeelliptical::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
connect(fakeElliptical, &fakeelliptical::debug, this, &bluetooth::debug);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
|
||||
// #358
|
||||
@@ -649,6 +682,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit deviceConnected(b);
|
||||
connect(fakeRower, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
|
||||
connect(fakeRower, &fakerower::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
connect(fakeRower, &fakerower::debug, this, &bluetooth::debug);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
|
||||
// #358
|
||||
@@ -663,6 +697,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(fakeTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
connect(fakeTreadmill, &faketreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
|
||||
connect(fakeTreadmill, &faketreadmill::debug, this, &bluetooth::debug);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
// connect(this, SIGNAL(searchingStop()), fakeBike, SLOT(searchingStop())); //NOTE: Commented due to
|
||||
// #358
|
||||
@@ -731,6 +766,19 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(csafeRower);
|
||||
#endif
|
||||
} else if (antbike_setting && !antBike) {
|
||||
this->stopDiscovery();
|
||||
antBike = new antbike(noWriteResistance, noHeartService, false);
|
||||
emit deviceConnected(b);
|
||||
connect(antBike, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(antBike, &antbike::debug, this, &bluetooth::debug);
|
||||
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(antBike);
|
||||
} else if (!proformtreadmillip.isEmpty() && !proformWifiTreadmill) {
|
||||
this->stopDiscovery();
|
||||
proformWifiTreadmill = new proformwifitreadmill(noWriteResistance, noHeartService, bikeResistanceOffset,
|
||||
@@ -773,6 +821,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(nordictrackifitadbBike);
|
||||
} else if (!proform_elliptical_ip.isEmpty() && !nordictrackifitadbElliptical) {
|
||||
this->stopDiscovery();
|
||||
nordictrackifitadbElliptical = new nordictrackifitadbelliptical(noWriteResistance, noHeartService,
|
||||
bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(nordictrackifitadbElliptical, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
connect(nordictrackifitadbElliptical, &nordictrackifitadbelliptical::debug, this, &bluetooth::debug);
|
||||
// nordictrackifitadbTreadmill->deviceDiscovered(b);
|
||||
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(nordictrackifitadbElliptical);
|
||||
} else if (((csc_as_bike && b.name().startsWith(cscName)) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("JOROTO-BK-"))) &&
|
||||
!cscBike && filter) {
|
||||
@@ -839,7 +901,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(domyosRower);
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos-Bike")) &&
|
||||
} else if ((b.name().startsWith(QStringLiteral("Domyos-Bike")) && (!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool())) &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -892,6 +954,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(domyosElliptical);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1044,7 +1107,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos")) &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBr")) &&
|
||||
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKING-")) && !domyos && !domyosElliptical && b.name().compare(ftms_treadmill, Qt::CaseInsensitive) &&
|
||||
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && filter) {
|
||||
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill &&
|
||||
(!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) &&
|
||||
filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
domyos = new domyostreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
@@ -1110,9 +1175,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-SC-BLR2C")) ||
|
||||
!b.name().toUpper().compare(QStringLiteral("RE")) || // just "RE"
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-H")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-F0")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-BLC")) || // Walkingpad C2 #1672
|
||||
b.name().toUpper().startsWith(
|
||||
QStringLiteral("KS-BLR"))) && // Treadmill KingSmith WalkingPad R2 Pro KS-HCR1AA
|
||||
!(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) && // it's an FTMS one
|
||||
!kingsmithR1ProTreadmill &&
|
||||
!kingsmithR2Treadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1157,7 +1224,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("TRUE")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("ASSAULT TREADMILL ")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("WDWAY")) && b.name().length() == 8) || // WdWay179
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TREADMILL")) && !gem_module_inclination && !deviceHasService(b, QBluetoothUuid((quint16)0x1814)))) &&
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TREADMILL")) && !gem_module_inclination && !deviceHasService(b, QBluetoothUuid((quint16)0x1814)) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)))) &&
|
||||
!trueTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1250,22 +1317,28 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("TRX4500")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("MATRIXTF50")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("T01_")) || // FTMS
|
||||
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
|
||||
!b.name().contains('(') && !b.name().contains(')') && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) &&
|
||||
horizon_treadmill_force_ftms) || // FTMS, TF-769DF2
|
||||
((b.name().toUpper().startsWith(QStringLiteral("TOORX")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")))) &&
|
||||
!toorx_ftms && toorx_ftms_treadmill) ||
|
||||
!b.name().compare(ftms_treadmill, Qt::CaseInsensitive) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT685")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XT285")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("WELLFIT TM")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("XTERRA TR")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("T118_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("EW-EP-")) || // Miweba MC700 ellipital Trainer #2419
|
||||
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT-")) || // FIT-1596
|
||||
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
|
||||
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
|
||||
(b.name().toUpper().startsWith("SCHWINN 810")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // sports tech f37s treadmill #2412
|
||||
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TT8")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
@@ -1281,8 +1354,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) &&
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
|
||||
) &&
|
||||
!horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1419,6 +1492,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("MKSM")) || // MKSM3600036
|
||||
(b.name().toUpper().startsWith("YS_C1_")) || // Yesoul C1H
|
||||
(b.name().toUpper().startsWith("YS_G1_")) || // Yesoul S3
|
||||
(b.name().toUpper().startsWith("YS_G1MPLUS")) || // Yesoul G1M Plus
|
||||
(b.name().toUpper().startsWith("DS25-")) || // Bodytone DS25
|
||||
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
|
||||
(b.name().toUpper().startsWith("3G CARDIO ")) ||
|
||||
@@ -1443,16 +1517,31 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("BIKE-")) ||
|
||||
(b.name().toUpper().startsWith("SPAX-BK-")) ||
|
||||
(b.name().toUpper().startsWith("YSV1")) ||
|
||||
(b.name().toUpper().startsWith("VOLT") && b.name().length() == 4) ||
|
||||
(b.name().toUpper().startsWith("VICTORY")) ||
|
||||
(b.name().toUpper().startsWith("CECOTEC")) || // Cecotec DrumFit Indoor 10000 MagnoMotor Connected #2420
|
||||
(b.name().toUpper().startsWith("WATTBIKE")) ||
|
||||
(b.name().toUpper().startsWith("ZYCLEZBIKE")) ||
|
||||
(b.name().toUpper().startsWith("WAVEFIT-")) ||
|
||||
(b.name().toUpper().startsWith("WAVEFIT-")) ||
|
||||
(b.name().toUpper().startsWith("KETTLERBLE")) ||
|
||||
(b.name().toUpper().startsWith("JAS_C3")) ||
|
||||
(b.name().toUpper().startsWith("SCH_190U")) ||
|
||||
(b.name().toUpper().startsWith("RAVE WHITE")) ||
|
||||
(b.name().toUpper().startsWith("DOMYOS-BIKING-")) ||
|
||||
(b.name().startsWith(QStringLiteral("Domyos-Bike")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool()) ||
|
||||
(b.name().toUpper().startsWith("F") && b.name().toUpper().endsWith("ARROW")) || // FI9110 Arrow, https://www.fitnessdigital.it/bicicletta-smart-bike-ion-fitness-arrow-connect/p/10022863/ IO Fitness Arrow
|
||||
(b.name().toUpper().startsWith("ICSE") && b.name().length() == 4) ||
|
||||
(b.name().toUpper().startsWith("FLX") && b.name().length() == 10) ||
|
||||
(b.name().toUpper().startsWith("CSRB") && b.name().length() == 11) ||
|
||||
(b.name().toUpper().startsWith("DU30-")) || // BodyTone du30
|
||||
(b.name().toUpper().startsWith("BIKZU_")) ||
|
||||
(b.name().toUpper().startsWith("WLT8828")) ||
|
||||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
|
||||
(b.name().toUpper().startsWith("HARISON-X15")) ||
|
||||
(b.name().toUpper().startsWith("FEIVON V2")) ||
|
||||
(b.name().toUpper().startsWith("FELVON V2")) ||
|
||||
(b.name().toUpper().startsWith("JUSTO")) ||
|
||||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
|
||||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
|
||||
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
|
||||
@@ -1475,7 +1564,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith("KICKR ROLLR") ||
|
||||
(b.name().toUpper().startsWith("HAMMER ") && saris_trainer) ||
|
||||
(b.name().toUpper().startsWith("WAHOO KICKR"))) &&
|
||||
!wahooKickrSnapBike && filter) {
|
||||
!wahooKickrSnapBike && !ftmsBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
wahooKickrSnapBike =
|
||||
@@ -1503,8 +1592,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(horizonGr7Bike);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
|
||||
(b.name().toUpper().startsWith("TACX SATORI")) ||
|
||||
(b.name().toUpper().startsWith("RACER S")) ||
|
||||
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
|
||||
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
|
||||
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
|
||||
!stagesBike && !ftmsBike && filter) {
|
||||
@@ -1554,6 +1647,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("CR 00")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KAYAKPRO")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("WHIPR")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("H-181-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("S4 COMMS")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-WLT")) || // KS-WLT-W1
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-ROWER")) ||
|
||||
@@ -1578,7 +1672,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().toUpper().startsWith(QLatin1String("ECH-STRIDE")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-UK-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-FR-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("STRIDE-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("STRIDE")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("STRIDE6S-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-SD-SPT"))) &&
|
||||
!echelonStride && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1754,6 +1849,22 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
sportsTechBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(sportsTechBike);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("EW-EP-")) && !sportsTechElliptical && !horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
sportsTechElliptical = new sportstechelliptical(noWriteResistance, noHeartService, bikeResistanceOffset,
|
||||
bikeResistanceGain);
|
||||
// stateFileRead();
|
||||
emit deviceConnected(b);
|
||||
connect(sportsTechElliptical, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(echelonConnectSport, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(sportsTechElliptical, &sportstechelliptical::debug, this, &bluetooth::debug);
|
||||
// connect(echelonConnectSport, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
// connect(echelonConnectSport, SIGNAL(inclinationChanged(double)), this,
|
||||
// SLOT(inclinationChanged(double)));
|
||||
sportsTechElliptical->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(sportsTechElliptical);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("CARDIOFIT")) ||
|
||||
(b.name().toUpper().contains(QStringLiteral("CARE")) &&
|
||||
b.name().length() == 11)) // CARE9040177 - Carefitness CV-351
|
||||
@@ -1821,7 +1932,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
proformTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(proformTreadmill);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("ESLINKER")) && !eslinkerTreadmill && filter) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("ESLINKER"))) && !eslinkerTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
eslinkerTreadmill = new eslinkertreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
@@ -1836,6 +1948,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
eslinkerTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(eslinkerTreadmill);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT")) && !deerrunTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
deerrunTreadmill = new deerruntreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
// stateFileRead();
|
||||
emit deviceConnected(b);
|
||||
connect(deerrunTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(proformtreadmill, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(deerrunTreadmill, &deerruntreadmill::debug, this, &bluetooth::debug);
|
||||
// connect(proformtreadmill, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
|
||||
// connect(proformtreadmill, SIGNAL(inclinationChanged(double)), this,
|
||||
// SLOT(inclinationChanged(double)));
|
||||
deerrunTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(deerrunTreadmill);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("PAFERS_")) && !pafersTreadmill &&
|
||||
(pafers_treadmill || pafers_treadmill_bh_iboxster_plus) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1930,7 +2057,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
mcfBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(mcfBike);
|
||||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
|
||||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MASTERT40-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1941,7 +2070,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(toorx, &toorxtreadmill::debug, this, &bluetooth::debug);
|
||||
toorx->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(toorx);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT"))) && !iConceptBike &&
|
||||
} else if (((b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT")) && !b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD"))) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH-"))) && !iConceptBike &&
|
||||
!iconcept_elliptical && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2019,6 +2149,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT HI WAY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BIKZU_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("PASYOU-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("VIRTUFIT")) ||
|
||||
((b.name().startsWith(QStringLiteral("TOORX")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
|
||||
@@ -2053,7 +2184,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// connect(ultraSportBike, &solebike::debug, this, &bluetooth::debug);
|
||||
ultraSportBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(ultraSportBike);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("KEEP_BIKE_"))) && !keepBike && filter) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("KEEP_BIKE_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KEEP_CC_"))) && !keepBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
keepBike = new keepbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
@@ -2076,7 +2208,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
soleBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(soleBike);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("BFCP")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("HT")) && b.name().length() == 11)) &&
|
||||
(b.name().toUpper().startsWith(QStringLiteral("HT")) && (b.name().length() == 11 || b.name().length() == 12))) &&
|
||||
!skandikaWiriBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -2161,10 +2293,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !horizonTreadmill && !snode_bike && !fitplus_bike && !ftmsBike && !iconsole_elliptical) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
|
||||
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
|
||||
!b.name().contains('(') && !b.name().contains(')')) ||
|
||||
!b.name().contains('(') && !b.name().contains(')') && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("WINFITA"))) || // also FTMS
|
||||
(b.name().startsWith(QStringLiteral("BF70")))) &&
|
||||
!fitshowTreadmill && !iconsole_elliptical && filter) {
|
||||
!fitshowTreadmill && !iconsole_elliptical && !horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
fitshowTreadmill = new fitshowtreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
@@ -2319,7 +2451,8 @@ void bluetooth::connectedAndDiscovered() {
|
||||
connect(heartRateBelt, &heartratebelt::debug, this, &bluetooth::debug);
|
||||
connect(heartRateBelt, &heartratebelt::heartRate, this->device(), &bluetoothdevice::heartRate);
|
||||
heartRateBelt->deviceDiscovered(b);
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(b.name() + " (HR sensor) connected!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2406,6 +2539,8 @@ void bluetooth::connectedAndDiscovered() {
|
||||
connect(cadenceSensor, &bluetoothdevice::cadenceChanged, this->device(),
|
||||
&bluetoothdevice::cadenceSensor);
|
||||
cadenceSensor->deviceDiscovered(b);
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(b.name() + " (cadence sensor) connected!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2451,6 +2586,10 @@ void bluetooth::connectedAndDiscovered() {
|
||||
&bluetoothdevice::verticalOscillationSensor);
|
||||
powerSensorRun->deviceDiscovered(b);
|
||||
}
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(b.name() + " (power sensor) connected!");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2518,6 +2657,8 @@ void bluetooth::connectedAndDiscovered() {
|
||||
connect(zwiftClickRemote->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
|
||||
connect(zwiftClickRemote->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
|
||||
zwiftClickRemote->deviceDiscovered(b);
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Zwift Click Connected!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2525,22 +2666,27 @@ void bluetooth::connectedAndDiscovered() {
|
||||
|
||||
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().toUpper().startsWith("ZWIFT PLAY"))) && zwiftPlayDevice.size() < 2 && this->device() &&
|
||||
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
|
||||
if(b.manufacturerData(2378).size() > 0) {
|
||||
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
|
||||
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
|
||||
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
|
||||
} else {
|
||||
qDebug() << "manufacturer not found for ZWIFT CLICK";
|
||||
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
|
||||
zwiftPlayDevice.length() == 0 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
|
||||
|
||||
}
|
||||
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
|
||||
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
|
||||
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
|
||||
connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug);
|
||||
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
|
||||
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
|
||||
zwiftPlayDevice.last()->deviceDiscovered(b);
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Zwift Play/Ride Connected!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2759,6 +2905,11 @@ void bluetooth::restart() {
|
||||
delete proformWifiBike;
|
||||
proformWifiBike = nullptr;
|
||||
}
|
||||
if (antBike) {
|
||||
|
||||
delete antBike;
|
||||
antBike = nullptr;
|
||||
}
|
||||
if (proformTelnetBike) {
|
||||
|
||||
delete proformTelnetBike;
|
||||
@@ -2779,6 +2930,11 @@ void bluetooth::restart() {
|
||||
delete nordictrackifitadbBike;
|
||||
nordictrackifitadbBike = nullptr;
|
||||
}
|
||||
if (nordictrackifitadbElliptical) {
|
||||
|
||||
delete nordictrackifitadbElliptical;
|
||||
nordictrackifitadbElliptical = nullptr;
|
||||
}
|
||||
if (powerBike) {
|
||||
|
||||
delete powerBike;
|
||||
@@ -2970,6 +3126,11 @@ void bluetooth::restart() {
|
||||
delete eslinkerTreadmill;
|
||||
eslinkerTreadmill = nullptr;
|
||||
}
|
||||
if (deerrunTreadmill) {
|
||||
|
||||
delete deerrunTreadmill;
|
||||
deerrunTreadmill = nullptr;
|
||||
}
|
||||
if (crossRope) {
|
||||
|
||||
delete crossRope;
|
||||
@@ -3020,6 +3181,11 @@ void bluetooth::restart() {
|
||||
delete sportsTechBike;
|
||||
sportsTechBike = nullptr;
|
||||
}
|
||||
if (sportsTechElliptical) {
|
||||
|
||||
delete sportsTechElliptical;
|
||||
sportsTechElliptical = nullptr;
|
||||
}
|
||||
if (sportsPlusBike) {
|
||||
|
||||
delete sportsPlusBike;
|
||||
@@ -3193,10 +3359,14 @@ bluetoothdevice *bluetooth::device() {
|
||||
return proformTelnetBike;
|
||||
} else if (proformWifiTreadmill) {
|
||||
return proformWifiTreadmill;
|
||||
} else if (antBike) {
|
||||
return antBike;
|
||||
} else if (nordictrackifitadbTreadmill) {
|
||||
return nordictrackifitadbTreadmill;
|
||||
} else if (nordictrackifitadbBike) {
|
||||
return nordictrackifitadbBike;
|
||||
} else if (nordictrackifitadbElliptical) {
|
||||
return nordictrackifitadbElliptical;
|
||||
} else if (powerBike) {
|
||||
return powerBike;
|
||||
} else if (powerTreadmill) {
|
||||
@@ -3297,6 +3467,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return proformRower;
|
||||
} else if (eslinkerTreadmill) {
|
||||
return eslinkerTreadmill;
|
||||
} else if (deerrunTreadmill) {
|
||||
return deerrunTreadmill;
|
||||
} else if (crossRope) {
|
||||
return crossRope;
|
||||
} else if (bowflexTreadmill) {
|
||||
@@ -3317,6 +3489,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return schwinn170Bike;
|
||||
} else if (sportsTechBike) {
|
||||
return sportsTechBike;
|
||||
} else if (sportsTechElliptical) {
|
||||
return sportsTechElliptical;
|
||||
} else if (sportsPlusBike) {
|
||||
return sportsPlusBike;
|
||||
} else if (inspireBike) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "qzsettings.h"
|
||||
|
||||
#include "devices/activiotreadmill/activiotreadmill.h"
|
||||
#include "devices/antbike/antbike.h"
|
||||
#include "devices/apexbike/apexbike.h"
|
||||
#include "devices/bhfitnesselliptical/bhfitnesselliptical.h"
|
||||
#include "devices/bkoolbike/bkoolbike.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "devices/concept2skierg/concept2skierg.h"
|
||||
#include "devices/crossrope/crossrope.h"
|
||||
#include "devices/cscbike/cscbike.h"
|
||||
#include "devices/deeruntreadmill/deerruntreadmill.h"
|
||||
#include "devices/domyosbike/domyosbike.h"
|
||||
#include "devices/domyoselliptical/domyoselliptical.h"
|
||||
#include "devices/domyosrower/domyosrower.h"
|
||||
@@ -76,6 +78,7 @@
|
||||
#include "devices/nautilustreadmill/nautilustreadmill.h"
|
||||
#include "devices/nordictrackelliptical/nordictrackelliptical.h"
|
||||
#include "devices/nordictrackifitadbbike/nordictrackifitadbbike.h"
|
||||
#include "devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h"
|
||||
#include "devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h"
|
||||
#include "devices/npecablebike/npecablebike.h"
|
||||
#include "devices/octaneelliptical/octaneelliptical.h"
|
||||
@@ -108,6 +111,7 @@
|
||||
#include "devices/spirittreadmill/spirittreadmill.h"
|
||||
#include "devices/sportsplusbike/sportsplusbike.h"
|
||||
#include "devices/sportstechbike/sportstechbike.h"
|
||||
#include "devices/sportstechelliptical/sportstechelliptical.h"
|
||||
#include "devices/stagesbike/stagesbike.h"
|
||||
|
||||
#include "devices/renphobike/renphobike.h"
|
||||
@@ -145,7 +149,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bluetooth(const discoveryoptions &options);
|
||||
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
|
||||
bool noHeartService = false, uint32_t pollDeviceTime = 200, bool noConsole = false,
|
||||
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
|
||||
bool testResistance = false, int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
|
||||
bool startDiscovery = true);
|
||||
~bluetooth();
|
||||
bluetoothdevice *device();
|
||||
@@ -159,6 +163,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool useDiscovery = false;
|
||||
QFile *debugCommsLog = nullptr;
|
||||
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
|
||||
antbike *antBike = nullptr;
|
||||
apexbike *apexBike = nullptr;
|
||||
bkoolbike *bkoolBike = nullptr;
|
||||
bhfitnesselliptical *bhFitnessElliptical = nullptr;
|
||||
@@ -172,6 +177,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
csaferower *csafeRower = nullptr;
|
||||
#endif
|
||||
concept2skierg *concept2Skierg = nullptr;
|
||||
deerruntreadmill *deerrunTreadmill = nullptr;
|
||||
domyostreadmill *domyos = nullptr;
|
||||
domyosbike *domyosBike = nullptr;
|
||||
domyosrower *domyosRower = nullptr;
|
||||
@@ -193,6 +199,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
nordictrackelliptical *nordictrackElliptical = nullptr;
|
||||
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
|
||||
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
|
||||
nordictrackifitadbelliptical *nordictrackifitadbElliptical = nullptr;
|
||||
octaneelliptical *octaneElliptical = nullptr;
|
||||
octanetreadmill *octaneTreadmill = nullptr;
|
||||
pelotonbike *pelotonBike = nullptr;
|
||||
@@ -213,6 +220,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
horizongr7bike *horizonGr7Bike = nullptr;
|
||||
schwinnic4bike *schwinnIC4Bike = nullptr;
|
||||
sportstechbike *sportsTechBike = nullptr;
|
||||
sportstechelliptical *sportsTechElliptical = nullptr;
|
||||
sportsplusbike *sportsPlusBike = nullptr;
|
||||
inspirebike *inspireBike = nullptr;
|
||||
snodebike *snodeBike = nullptr;
|
||||
@@ -274,7 +282,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool noConsole = false;
|
||||
bool logs = true;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool forceHeartBeltOffForTimeout = false;
|
||||
|
||||
@@ -299,6 +307,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
bool eliteRizerAvaiable();
|
||||
bool eliteSterzoSmartAvaiable();
|
||||
bool fitmetriaFanfitAvaiable();
|
||||
bool zwiftDeviceAvaiable();
|
||||
bool fitmetria_fanfit_isconnected(QString name);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -531,6 +531,12 @@ class bluetoothdevice : public QObject {
|
||||
int8_t requestDecreaseFan = -1;
|
||||
double requestFanSpeed = -1;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
metric RequestedPower;
|
||||
int16_t requestPower = -1;
|
||||
|
||||
/**
|
||||
* @brief m_difficult The current difficulty gain. Units: device dependent
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -51,9 +51,6 @@ class crossrope : public jumprope {
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
|
||||
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.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, uint8_t bikeResistanceOffset,
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
@@ -685,7 +685,41 @@ resistance_t domyosbike::resistanceFromPowerRequest(uint16_t power) {
|
||||
|
||||
uint16_t domyosbike::wattsFromResistance(double resistance) {
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1)
|
||||
if (settings.value(QZSettings::domyos_bike_500_profile_v2, QZSettings::default_domyos_bike_500_profile_v2).toBool()) {
|
||||
switch ((int)resistance) {
|
||||
case 1:
|
||||
return (5.0 * Cadence.value()) / 9.5488;
|
||||
case 2:
|
||||
return (5.7 * Cadence.value()) / 9.5488;
|
||||
case 3:
|
||||
return (6.5 * Cadence.value()) / 9.5488;
|
||||
case 4:
|
||||
return (7.5 * Cadence.value()) / 9.5488;
|
||||
case 5:
|
||||
return (8.6 * Cadence.value()) / 9.5488;
|
||||
case 6:
|
||||
return (9.9 * Cadence.value()) / 9.5488;
|
||||
case 7:
|
||||
return (11.4 * Cadence.value()) / 9.5488;
|
||||
case 8:
|
||||
return (13.6 * Cadence.value()) / 9.5488;
|
||||
case 9:
|
||||
return (15.3 * Cadence.value()) / 9.5488;
|
||||
case 10:
|
||||
return (17.3 * Cadence.value()) / 9.5488;
|
||||
case 11:
|
||||
return (19.8 * Cadence.value()) / 9.5488;
|
||||
case 12:
|
||||
return (22.5 * Cadence.value()) / 9.5488;
|
||||
case 13:
|
||||
return (25.6 * Cadence.value()) / 9.5488;
|
||||
case 14:
|
||||
return (28.4 * Cadence.value()) / 9.5488;
|
||||
case 15:
|
||||
return (35.9 * Cadence.value()) / 9.5488;
|
||||
}
|
||||
return 0;
|
||||
} else if (!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1)
|
||||
.toBool() ||
|
||||
resistance < 8)
|
||||
return ((10.39 + 1.45 * (resistance - 1.0)) * (exp(0.028 * (currentCadence().value()))));
|
||||
|
||||
@@ -37,7 +37,7 @@ class domyosbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyosbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
|
||||
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
@@ -74,7 +74,7 @@ class domyosbike : public bike {
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool searchStopped = false;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
domyoselliptical::domyoselliptical(bool noWriteResistance, bool noHeartService, bool testResistance,
|
||||
uint8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
@@ -32,7 +32,7 @@ class domyoselliptical : public elliptical {
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyoselliptical(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
|
||||
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
~domyoselliptical();
|
||||
bool connected() override;
|
||||
bool inclinationAvailableByHardware() override;
|
||||
@@ -64,7 +64,7 @@ class domyoselliptical : public elliptical {
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool searchStopped = false;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
domyosrower::domyosrower(bool noWriteResistance, bool noHeartService, bool testResistance, uint8_t bikeResistanceOffset,
|
||||
domyosrower::domyosrower(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
|
||||
@@ -36,7 +36,7 @@ class domyosrower : public rower {
|
||||
Q_OBJECT
|
||||
public:
|
||||
domyosrower(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
|
||||
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
~domyosrower();
|
||||
bool connected() override;
|
||||
|
||||
@@ -72,7 +72,7 @@ class domyosrower : public rower {
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
bool ftmsRower = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool searchStopped = false;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "echelonconnectsport.h"
|
||||
#include "homeform.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
@@ -17,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
@@ -469,7 +470,13 @@ void echelonconnectsport::serviceScanDone(void) {
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
&echelonconnectsport::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
if(gattCommunicationChannelService != nullptr) {
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
} else {
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
|
||||
m_control->disconnectFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void echelonconnectsport::errorService(QLowEnergyService::ServiceError err) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
class echelonconnectsport : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
echelonconnectsport(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
echelonconnectsport(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
@@ -65,7 +65,7 @@ class echelonconnectsport : public bike {
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify2Characteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
class echelonrower : public rower {
|
||||
Q_OBJECT
|
||||
public:
|
||||
echelonrower(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
echelonrower(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
resistance_t maxResistance() override{ return max_resistance; }
|
||||
@@ -62,7 +62,7 @@ class echelonrower : public rower {
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify2Characteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -196,12 +196,22 @@ void echelonstride::update() {
|
||||
uint8_t initData3[] = {0xf0, 0xb0, 0x01, 0x01, 0xa2};
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("start"), false, true);
|
||||
|
||||
if(stride4) {
|
||||
uint8_t initData0[] = {0xf0, 0xa5, 0x00, 0x95};
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
|
||||
}
|
||||
|
||||
uint8_t initData4[] = {0xf0, 0xd0, 0x01, 0x00, 0xc1};
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("start"), false, false);
|
||||
|
||||
uint8_t initData5[] = {0xf0, 0xd0, 0x01, 0x11, 0xd2};
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("start"), false, false);
|
||||
|
||||
if(stride4) {
|
||||
uint8_t initData0[] = {0xf0, 0xd3, 0x02, 0x01, 0xf4, 0xba};
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
|
||||
}
|
||||
|
||||
lastStart = QDateTime::currentMSecsSinceEpoch();
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
@@ -285,6 +295,32 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
|
||||
} else if (((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd0) {
|
||||
writeCharacteristic((uint8_t *)newValue.constData(), newValue.length(), "reply to d0", false, false);
|
||||
return;
|
||||
} else if (((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd1 && stride4) {
|
||||
|
||||
double miles = 1;
|
||||
if (settings.value(QZSettings::sole_treadmill_miles, QZSettings::default_sole_treadmill_miles).toBool())
|
||||
miles = 1.60934;
|
||||
|
||||
// this line on iOS sometimes gives strange overflow values
|
||||
// uint16_t convertedData = (((uint16_t)newValue.at(3)) << 8) | (uint16_t)newValue.at(4);
|
||||
qDebug() << "speed1" << newValue.at(7);
|
||||
uint16_t convertedData = (uint8_t)newValue.at(7);
|
||||
qDebug() << "speed2" << convertedData;
|
||||
convertedData = convertedData << 8;
|
||||
qDebug() << "speed3" << convertedData;
|
||||
convertedData = convertedData & 0xFF00;
|
||||
qDebug() << "speed4" << convertedData;
|
||||
convertedData = convertedData + (uint8_t)newValue.at(8);
|
||||
qDebug() << "speed5" << convertedData;
|
||||
Speed = (((double)convertedData) / 100.0) * miles;
|
||||
|
||||
if (Speed.value() > 0)
|
||||
lastStart = 0;
|
||||
else
|
||||
lastStop = 0;
|
||||
|
||||
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
|
||||
return;
|
||||
}
|
||||
|
||||
/*if (newValue.length() != 21)
|
||||
@@ -344,7 +380,17 @@ void echelonstride::btinit() {
|
||||
uint8_t initData1[] = {0xf0, 0xa1, 0x00, 0x91};
|
||||
uint8_t initData2[] = {0xf0, 0xa3, 0x00, 0x93};
|
||||
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
|
||||
// stride4
|
||||
uint8_t initDataStride4_0[] = {0xf0, 0xe0, 0xfd, 0x3e, 0x65, 0x48, 0xd5, 0x8d};
|
||||
|
||||
if(stride4) {
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true); // send a frame to wait the Value: f0e0728518586198
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initDataStride4_0, sizeof(initDataStride4_0), QStringLiteral("init"), false, false);
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, false);
|
||||
} else {
|
||||
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
|
||||
}
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
@@ -352,7 +398,9 @@ void echelonstride::btinit() {
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
|
||||
if(!stride4)
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
@@ -432,6 +480,12 @@ void echelonstride::error(QLowEnergyController::Error err) {
|
||||
void echelonstride::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
if(bluetoothDevice.name().toUpper().startsWith("STRIDE4")) {
|
||||
stride4 = true;
|
||||
qDebug() << "STRIDE4 workaround enabled!";
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &echelonstride::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &echelonstride::serviceScanDone);
|
||||
|
||||
@@ -68,9 +68,6 @@ class echelonstride : public treadmill {
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
@@ -81,6 +78,8 @@ class echelonstride : public treadmill {
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool stride4 = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,40 @@ void elliptical::update_metrics(bool watt_calc, const double watts) {
|
||||
_firstUpdate = false;
|
||||
}
|
||||
|
||||
resistance_t elliptical::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
|
||||
|
||||
void elliptical::changePower(int32_t power) {
|
||||
|
||||
RequestedPower = power; // in order to paint in any case the request power on the charts
|
||||
|
||||
if (!autoResistanceEnable) {
|
||||
qDebug() << QStringLiteral("changePower ignored because auto resistance is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
requestPower = power; // used by some bikes that have ERG mode builtin
|
||||
QSettings settings;
|
||||
bool force_resistance =
|
||||
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
|
||||
.toBool();
|
||||
// bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool(); //Not used
|
||||
// anywhere in code
|
||||
double erg_filter_upper =
|
||||
settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
|
||||
double erg_filter_lower =
|
||||
settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
|
||||
double deltaDown = wattsMetric().value() - ((double)power);
|
||||
double deltaUp = ((double)power) - wattsMetric().value();
|
||||
qDebug() << QStringLiteral("filter ") + QString::number(deltaUp) + " " + QString::number(deltaDown) + " " +
|
||||
QString::number(erg_filter_upper) + " " + QString::number(erg_filter_lower);
|
||||
if (/*!ergModeSupported &&*/ force_resistance /*&& erg_mode*/ &&
|
||||
(deltaUp > erg_filter_upper || deltaDown > erg_filter_lower)) {
|
||||
resistance_t r = (resistance_t)resistanceFromPowerRequest(power);
|
||||
changeResistance(r); // resistance start from 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint16_t elliptical::watts() {
|
||||
|
||||
QSettings settings;
|
||||
|
||||
@@ -33,12 +33,14 @@ class elliptical : public bluetoothdevice {
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
virtual double minStepInclination() { return 0.5; }
|
||||
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
|
||||
|
||||
public Q_SLOTS:
|
||||
virtual void changeSpeed(double speed);
|
||||
void changeResistance(resistance_t res) override;
|
||||
void changeInclination(double grade, double inclination) override;
|
||||
virtual void changeCadence(int16_t cad);
|
||||
void changePower(int32_t power) override;
|
||||
virtual void changeRequestedPelotonResistance(int8_t resistance);
|
||||
|
||||
signals:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "eslinkertreadmill.h"
|
||||
#include "homeform.h"
|
||||
#include "keepawakehelper.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
@@ -7,9 +8,67 @@
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <chrono>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class CRC8
|
||||
{
|
||||
public:
|
||||
CRC8(quint8 polynomial = 0x07, quint8 init = 0x00, bool refIn = false, bool refOut = false, quint8 xorOut = 0x00)
|
||||
: m_polynomial(polynomial), m_init(init), m_refIn(refIn), m_refOut(refOut), m_xorOut(xorOut)
|
||||
{
|
||||
generateTable();
|
||||
}
|
||||
|
||||
quint8 calculate(const QByteArray &data)
|
||||
{
|
||||
quint8 crc = m_init;
|
||||
for (char c : data) {
|
||||
if (m_refIn)
|
||||
c = reflect8(c);
|
||||
crc = m_table[crc ^ static_cast<quint8>(c)];
|
||||
}
|
||||
if (m_refOut)
|
||||
crc = reflect8(crc);
|
||||
return crc ^ m_xorOut;
|
||||
}
|
||||
|
||||
private:
|
||||
quint8 m_polynomial;
|
||||
quint8 m_init;
|
||||
bool m_refIn;
|
||||
bool m_refOut;
|
||||
quint8 m_xorOut;
|
||||
quint8 m_table[256];
|
||||
|
||||
void generateTable()
|
||||
{
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
quint8 crc = static_cast<quint8>(i);
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
if (crc & 0x80)
|
||||
crc = (crc << 1) ^ m_polynomial;
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
m_table[i] = crc;
|
||||
}
|
||||
}
|
||||
|
||||
quint8 reflect8(quint8 value)
|
||||
{
|
||||
quint8 reflected = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
if (value & 0x01)
|
||||
reflected |= (1 << (7 - i));
|
||||
value >>= 1;
|
||||
}
|
||||
return reflected;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
eslinkertreadmill::eslinkertreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
@@ -48,7 +107,7 @@ void eslinkertreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len, con
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
@@ -71,7 +130,8 @@ void eslinkertreadmill::updateDisplay(uint16_t elapsed) {
|
||||
|
||||
writeCharacteristic(display, sizeof(display),
|
||||
QStringLiteral("updateDisplay elapsed=") + QString::number(elapsed), false, false);
|
||||
} else {
|
||||
} else if (treadmill_type == ESANGLINKER){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +176,16 @@ void eslinkertreadmill::forceSpeed(double requestSpeed) {
|
||||
|
||||
writeCharacteristic(display, sizeof(display),
|
||||
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
|
||||
} else if(treadmill_type == ESANGLINKER) {
|
||||
uint8_t display[] = {0xa9, 0x01, 0x01, 0x0b, 0x00};
|
||||
display[3] = (int)qRound(requestSpeed * 10 * 0.621371);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
display[4] = display[4] ^ display[i];
|
||||
}
|
||||
|
||||
writeCharacteristic(display, sizeof(display),
|
||||
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +229,7 @@ void eslinkertreadmill::update() {
|
||||
}
|
||||
|
||||
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE ||
|
||||
treadmill_type == TYPE::COSTAWAY) {
|
||||
treadmill_type == TYPE::COSTAWAY || treadmill_type == TYPE::ESANGLINKER) {
|
||||
|
||||
if (requestSpeed != -1) {
|
||||
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
|
||||
@@ -236,10 +306,8 @@ void eslinkertreadmill::update() {
|
||||
if (lastSpeed == 0.0) {
|
||||
lastSpeed = 0.5;
|
||||
}
|
||||
if (treadmill_type == TYPE::RHYTHM_FUN || treadmill_type == TYPE::YPOO_MINI_CHANGE) {
|
||||
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
|
||||
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("startTape"), false, true);
|
||||
}
|
||||
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
|
||||
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("startTape"), false, true);
|
||||
requestSpeed = 1.0;
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
@@ -247,7 +315,8 @@ void eslinkertreadmill::update() {
|
||||
if (requestStop != -1) {
|
||||
requestSpeed = 0;
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape", false, true);
|
||||
uint8_t startTape[] = {0xa9, 0xa3, 0x01, 0x00, 0x0b};
|
||||
writeCharacteristic(startTape, sizeof(startTape), QStringLiteral("stopTape"), false, true);
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
@@ -255,6 +324,14 @@ void eslinkertreadmill::update() {
|
||||
|
||||
void eslinkertreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
|
||||
|
||||
if(gatt == QBluetoothUuid((quint16)0x1826)) {
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::ftms_treadmill, bluetoothDevice.name());
|
||||
qDebug() << "forcing FTMS treadmill since it has FTMS";
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("FTMS treadmill found, restart the app to apply the change");
|
||||
}
|
||||
}
|
||||
|
||||
void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
@@ -270,6 +347,43 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
|
||||
emit packetReceived();
|
||||
|
||||
if(treadmill_type == TYPE::ESANGLINKER) {
|
||||
if((uint8_t)newValue.at(0) == 0xa9 && (uint8_t)newValue.at(1) == 0x08 && (uint8_t)newValue.at(2) == 0x04) { // pair request
|
||||
lastPairFrame = newValue;
|
||||
qDebug() << "lastPairFrame" << lastPairFrame;
|
||||
|
||||
uint8_t initData6[] = {0xa9, 0x08, 0x04, 0x0c, 0x06, 0x48, 0x12, 0xf5};
|
||||
|
||||
if(lastPairFrame.length() < 3) {
|
||||
qDebug() << "ERROR! Pair code!";
|
||||
return;
|
||||
}
|
||||
QByteArray crypto = cryptographicArray(lastPairFrame.at(3));
|
||||
initData6[3] = crypto.at(0);
|
||||
initData6[4] = crypto.at(1);
|
||||
initData6[5] = crypto.at(2);
|
||||
initData6[6] = crypto.at(3);
|
||||
CRC8 crc8;
|
||||
initData6[7] = crc8.calculate(QByteArray((const char*)&initData6[3], 4));
|
||||
|
||||
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
|
||||
|
||||
emit pairPacketReceived();
|
||||
} else if((uint8_t)newValue.at(0) == 0xa9 && (uint8_t)newValue.at(1) == 0x08 &&
|
||||
(uint8_t)newValue.at(2) == 0x01 && (uint8_t)newValue.at(3) == 0xff && (uint8_t)newValue.at(4) == 0x5f) { // handshake request
|
||||
qDebug() << "handshake";
|
||||
|
||||
uint8_t initData5[] = {0xa9, 0x08, 0x01, 0xad, 0x0d};
|
||||
initData5[3] = QRandomGenerator::global()->bounded(256);
|
||||
CRC8 crc8;
|
||||
initData5[4] = crc8.calculate(QByteArray((const char*)&initData5[3], 1));
|
||||
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
|
||||
|
||||
emit handshakePacketReceived();
|
||||
}
|
||||
}
|
||||
|
||||
if (treadmill_type == CADENZA_FITNESS_T45) {
|
||||
if (newValue.length() == 6 && newValue.at(0) == 8 && newValue.at(1) == 4 && newValue.at(2) == 1 &&
|
||||
newValue.at(3) == 0 && newValue.at(4) == 0 && newValue.at(5) == 1) {
|
||||
@@ -347,12 +461,11 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
}
|
||||
}
|
||||
|
||||
if ((newValue.length() != 17 && (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE)))
|
||||
return;
|
||||
else if (newValue.length() != 5 && treadmill_type == COSTAWAY)
|
||||
return;
|
||||
if ((newValue.length() != 17 && (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE))) {
|
||||
|
||||
if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
|
||||
} else if (newValue.length() != 5 && (treadmill_type == COSTAWAY || treadmill_type == TYPE::ESANGLINKER)) {
|
||||
|
||||
} else if (treadmill_type == RHYTHM_FUN || treadmill_type == YPOO_MINI_CHANGE) {
|
||||
double speed = GetSpeedFromPacket(value);
|
||||
double incline = GetInclinationFromPacket(value);
|
||||
double kcal = GetKcalFromPacket(value);
|
||||
@@ -388,9 +501,9 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
lastSpeed = speed;
|
||||
lastInclination = incline;
|
||||
}
|
||||
} else if (treadmill_type == COSTAWAY) {
|
||||
} else if (treadmill_type == COSTAWAY || (treadmill_type == TYPE::ESANGLINKER && (uint8_t)newValue.at(1) == 0xe0)) {
|
||||
const double miles = 1.60934;
|
||||
if(((uint8_t)newValue.at(3)) == 0xFF)
|
||||
if(((uint8_t)newValue.at(3)) == 0xFF && treadmill_type == COSTAWAY)
|
||||
Speed = 0;
|
||||
else
|
||||
Speed = (double)((uint8_t)newValue.at(3)) / 10.0 * miles;
|
||||
@@ -465,7 +578,56 @@ double eslinkertreadmill::GetInclinationFromPacket(const QByteArray &packet) {
|
||||
void eslinkertreadmill::btinit(bool startTape) {
|
||||
Q_UNUSED(startTape)
|
||||
|
||||
if (treadmill_type == COSTAWAY) {
|
||||
if (treadmill_type == ESANGLINKER) {
|
||||
uint8_t initData1[] = {0xa9, 0xf2, 0x01, 0x2f, 0x75};
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
|
||||
uint8_t initData2[] = {0xa9, 0x0a, 0x01, 0xc6, 0x64};
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
|
||||
|
||||
uint8_t initData3[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
|
||||
|
||||
uint8_t initData4[] = {0xa9, 0xa0, 0x03, 0x00, 0x00, 0x00, 0x0a};
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
|
||||
|
||||
waitForHandshakePacket();
|
||||
|
||||
QThread::sleep(2);
|
||||
|
||||
waitForPairPacket();
|
||||
|
||||
QThread::sleep(1);
|
||||
|
||||
uint8_t initData7[] = {0xa9, 0x1e, 0x01, 0xfe, 0x48};
|
||||
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, true);
|
||||
|
||||
uint8_t initData8[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
|
||||
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, true);
|
||||
|
||||
QThread::sleep(2);
|
||||
|
||||
uint8_t initData9[] = {0xa9, 0xa3, 0x01, 0x01, 0x0a};
|
||||
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, true);
|
||||
|
||||
uint8_t initData10[] = {0xa9, 0x8e, 0x01, 0x09, 0x2f};
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
|
||||
uint8_t initData11[] = {0xa9, 0xb2, 0x01, 0xfe, 0xe4};
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, true);
|
||||
|
||||
QThread::sleep(3);
|
||||
|
||||
uint8_t initData12[] = {0xa9, 0x8e, 0x01, 0x09, 0x2f};
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
|
||||
uint8_t initData13[] = {0xa9, 0xae, 0x01, 0xfe, 0xf8};
|
||||
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, true);
|
||||
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Init completed, you can use the treadmill now!");
|
||||
|
||||
} else if (treadmill_type == COSTAWAY) {
|
||||
uint8_t initData1[] = {0xa9, 0xf2, 0x01, 0x2f, 0x75};
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
|
||||
|
||||
@@ -633,7 +795,10 @@ void eslinkertreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
bool eslinker_ypoo = settings.value(QZSettings::eslinker_ypoo, QZSettings::default_eslinker_ypoo).toBool();
|
||||
bool eslinker_costaway =
|
||||
settings.value(QZSettings::eslinker_costaway, QZSettings::default_eslinker_costaway).toBool();
|
||||
if (eslinker_cadenza) {
|
||||
if(device.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) {
|
||||
treadmill_type = ESANGLINKER;
|
||||
qDebug() << "ESANGLINKER workaround ENABLED!";
|
||||
} else if (eslinker_cadenza) {
|
||||
treadmill_type = CADENZA_FITNESS_T45;
|
||||
} else if (eslinker_ypoo) {
|
||||
treadmill_type = YPOO_MINI_CHANGE;
|
||||
@@ -677,3 +842,76 @@ bool eslinkertreadmill::autoStartWhenSpeedIsGreaterThenZero() {
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray eslinkertreadmill::cryptographicArray(quint8 b2) {
|
||||
int i = b2 % 6;
|
||||
QRandomGenerator *random = QRandomGenerator::global();
|
||||
int nextInt = random->bounded(1, 16); // 1 to 15
|
||||
int nextInt2 = random->bounded(1, 16); // 1 to 15
|
||||
|
||||
QByteArray bArr(4, 0);
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
bArr[0] = static_cast<char>(nextInt);
|
||||
bArr[1] = static_cast<char>(nextInt2);
|
||||
bArr[2] = static_cast<char>(nextInt + nextInt2);
|
||||
bArr[3] = static_cast<char>(nextInt * nextInt2);
|
||||
break;
|
||||
case 1:
|
||||
bArr[0] = static_cast<char>(nextInt);
|
||||
bArr[1] = static_cast<char>(nextInt2);
|
||||
bArr[2] = static_cast<char>(nextInt * nextInt2);
|
||||
bArr[3] = static_cast<char>(nextInt + nextInt2);
|
||||
break;
|
||||
case 2:
|
||||
bArr[0] = static_cast<char>(nextInt + nextInt2);
|
||||
bArr[1] = static_cast<char>(nextInt);
|
||||
bArr[2] = static_cast<char>(nextInt2);
|
||||
bArr[3] = static_cast<char>(nextInt * nextInt2);
|
||||
break;
|
||||
case 3:
|
||||
bArr[0] = static_cast<char>(nextInt * nextInt2);
|
||||
bArr[1] = static_cast<char>(nextInt);
|
||||
bArr[2] = static_cast<char>(nextInt2);
|
||||
bArr[3] = static_cast<char>(nextInt + nextInt2);
|
||||
break;
|
||||
case 4:
|
||||
bArr[0] = static_cast<char>(nextInt + nextInt2);
|
||||
bArr[1] = static_cast<char>(nextInt * nextInt2);
|
||||
bArr[2] = static_cast<char>(nextInt);
|
||||
bArr[3] = static_cast<char>(nextInt2);
|
||||
break;
|
||||
case 5:
|
||||
bArr[0] = static_cast<char>(nextInt * nextInt2);
|
||||
bArr[1] = static_cast<char>(nextInt + nextInt2);
|
||||
bArr[2] = static_cast<char>(nextInt);
|
||||
bArr[3] = static_cast<char>(nextInt2);
|
||||
break;
|
||||
}
|
||||
|
||||
return bArr;
|
||||
}
|
||||
|
||||
void eslinkertreadmill::waitForPairPacket() {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
connect(this, &eslinkertreadmill::pairPacketReceived, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(3000, &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void eslinkertreadmill::waitForHandshakePacket() {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
connect(this, &eslinkertreadmill::handshakePacketReceived, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(3000, &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
double eslinkertreadmill::minStepSpeed() {
|
||||
if(treadmill_type == ESANGLINKER)
|
||||
return 0.160934; // 0.1 mi
|
||||
else
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class eslinkertreadmill : public treadmill {
|
||||
double minStepInclination() override;
|
||||
bool autoPauseWhenSpeedIsZero() override;
|
||||
bool autoStartWhenSpeedIsGreaterThenZero() override;
|
||||
double minStepSpeed() override;
|
||||
|
||||
private:
|
||||
double GetSpeedFromPacket(const QByteArray &packet);
|
||||
@@ -46,6 +47,9 @@ class eslinkertreadmill : public treadmill {
|
||||
void forceIncline(double requestIncline);
|
||||
void updateDisplay(uint16_t elapsed);
|
||||
void btinit(bool startTape);
|
||||
void waitForPairPacket();
|
||||
void waitForHandshakePacket();
|
||||
QByteArray cryptographicArray(quint8 b2);
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
@@ -60,18 +64,17 @@ class eslinkertreadmill : public treadmill {
|
||||
uint8_t requestHandshake = 0;
|
||||
bool requestVar2 = false;
|
||||
bool toggleRequestSpeed = false;
|
||||
QByteArray lastPairFrame;
|
||||
|
||||
typedef enum TYPE {
|
||||
RHYTHM_FUN = 0,
|
||||
CADENZA_FITNESS_T45 = 1, // it has the same protocol of RHYTHM_FUN but without the header and the footer
|
||||
YPOO_MINI_CHANGE = 2, // Similar to RHYTHM_FUN but has no ascension
|
||||
COSTAWAY = 3,
|
||||
ESANGLINKER = 4,
|
||||
} TYPE;
|
||||
volatile TYPE treadmill_type = RHYTHM_FUN;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
QTimer *refresh;
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
@@ -85,6 +88,8 @@ class eslinkertreadmill : public treadmill {
|
||||
void debug(QString string);
|
||||
void speedChanged(double speed);
|
||||
void packetReceived();
|
||||
void pairPacketReceived();
|
||||
void handshakePacketReceived();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
@@ -44,7 +44,11 @@ void faketreadmill::update() {
|
||||
}
|
||||
|
||||
if (requestInclination != -100) {
|
||||
Inclination = requestInclination;
|
||||
double step =
|
||||
settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline)
|
||||
.toDouble();
|
||||
double r = qRound(requestInclination / step) * step;
|
||||
Inclination = r;
|
||||
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
|
||||
requestInclination = -100;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QSettings settings;
|
||||
@@ -89,6 +89,7 @@ void fitplusbike::forceResistance(resistance_t requestResistance) {
|
||||
QSettings settings;
|
||||
bool virtufit_etappe = settings.value(QZSettings::virtufit_etappe, QZSettings::default_virtufit_etappe).toBool();
|
||||
bool sportstech_sx600 = settings.value(QZSettings::sportstech_sx600, QZSettings::default_sportstech_sx600).toBool();
|
||||
requestResistanceCompleted = false;
|
||||
if (virtufit_etappe || merach_MRK || H9110_OSAKA) {
|
||||
if (requestResistance == 1) {
|
||||
uint8_t res[] = {0x02, 0x44, 0x05, 0x01, 0xf9, 0xb9, 0x03};
|
||||
@@ -590,14 +591,14 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
resistance_t res = newValue.at(5);
|
||||
if (settings.value(QZSettings::gears_from_bike, QZSettings::default_gears_from_bike).toBool()) {
|
||||
qDebug() << QStringLiteral("gears_from_bike") << res << Resistance.value() << gears()
|
||||
<< lastRawRequestedResistanceValue << lastRequestedResistance().value() << requestResistance;
|
||||
<< lastRawRequestedResistanceValue << lastRequestedResistance().value() << requestResistance << requestResistanceCompleted;
|
||||
if (
|
||||
// if the resistance is different from the previous one
|
||||
res != qRound(Resistance.value()) &&
|
||||
// and the last target resistance is different from the current one or there is no any pending last
|
||||
// requested resistance
|
||||
((lastRequestedResistance().value() != res && lastRequestedResistance().value() != 0 && requestResistance == -1) ||
|
||||
(lastRawRequestedResistanceValue == -1 && requestResistance == -1)) &&
|
||||
((lastRequestedResistance().value() != res && lastRequestedResistance().value() != 0 && requestResistance == -1 && requestResistanceCompleted) ||
|
||||
(lastRawRequestedResistanceValue == -1 && requestResistance == -1 && requestResistanceCompleted)) &&
|
||||
// and the difference between the 2 resistances are less than 6
|
||||
qRound(Resistance.value()) > 1 && qAbs(res - qRound(Resistance.value())) < 6) {
|
||||
|
||||
@@ -608,6 +609,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
setGears(g);
|
||||
}
|
||||
}
|
||||
requestResistanceCompleted = true;
|
||||
Resistance = res;
|
||||
emit resistanceRead(Resistance.value());
|
||||
if (merach_MRK || sportstech_sx600) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
class fitplusbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
fitplusbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
bool connected() override;
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
@@ -60,7 +60,7 @@ class fitplusbike : public bike {
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotifyFTMSCharacteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
@@ -68,6 +68,7 @@ class fitplusbike : public bike {
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
resistance_t lastResistanceBeforeDisconnection = -1;
|
||||
bool requestResistanceCompleted = true;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include "homeform.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
@@ -159,8 +160,9 @@ void fitshowtreadmill::update() {
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit(true);
|
||||
QSettings settings;
|
||||
initRequest = false;
|
||||
btinit(settings.value(QZSettings::atletica_lightspeed_treadmill, QZSettings::default_atletica_lightspeed_treadmill).toBool());
|
||||
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() && initDone) {
|
||||
@@ -297,6 +299,13 @@ void fitshowtreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
qDebug() << "adding" << gatt.toString() << "as the default service";
|
||||
serviceId = gatt; // NOTE: clazy-rule-of-tow
|
||||
}
|
||||
if(gatt == QBluetoothUuid((quint16)0x1826) && !fs_connected) {
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::ftms_treadmill, bluetoothDevice.name());
|
||||
qDebug() << "forcing FTMS treadmill since it has FTMS";
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("FTMS treadmill found, restart the app to apply the change");
|
||||
}
|
||||
}
|
||||
|
||||
void fitshowtreadmill::sendSportData() {
|
||||
@@ -829,10 +838,10 @@ void fitshowtreadmill::error(QLowEnergyController::Error err) {
|
||||
void fitshowtreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
/*if (device.name().startsWith(QStringLiteral("FS-")) ||
|
||||
(device.name().startsWith(QStringLiteral("SW")) && device.name().length() == 14))*/
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT"))) {
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("FS-"))) {
|
||||
qDebug() << "FS FIX!";
|
||||
fs_connected = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT"))) {
|
||||
qDebug() << "NOBLEPRO FIX!";
|
||||
minStepInclinationValue = 0.5;
|
||||
noblepro_connected = true;
|
||||
|
||||
@@ -98,8 +98,6 @@ class fitshowtreadmill : public treadmill {
|
||||
void sendSportData();
|
||||
void removeFromBuffer();
|
||||
QBluetoothUuid serviceId;
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
int retrySend = 0;
|
||||
bool noHeartService = false;
|
||||
bool anyrun = false;
|
||||
@@ -154,6 +152,7 @@ class fitshowtreadmill : public treadmill {
|
||||
|
||||
double minStepInclinationValue = 1.0;
|
||||
bool noblepro_connected = false;
|
||||
bool fs_connected = false;
|
||||
|
||||
metric rawInclination;
|
||||
|
||||
|
||||
@@ -59,9 +59,6 @@ class focustreadmill : public treadmill {
|
||||
bool firstCharacteristicChanged = true;
|
||||
bool searchStopped = false;
|
||||
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ftmsbike.h"
|
||||
#include "homeform.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
@@ -21,8 +22,9 @@ extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
@@ -32,17 +34,54 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResi
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
initDone = false;
|
||||
connect(refresh, &QTimer::timeout, this, &ftmsbike::update);
|
||||
refresh->start(200ms);
|
||||
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
|
||||
}
|
||||
|
||||
void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
void ftmsbike::writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if(!zwiftPlayService) {
|
||||
qDebug() << QStringLiteral("zwiftPlayService is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait_for_response) {
|
||||
connect(zwiftPlayService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
} else {
|
||||
connect(zwiftPlayService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
if (zwiftPlayWriteChar.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
zwiftPlayService->writeCharacteristic(zwiftPlayWriteChar, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
zwiftPlayService->writeCharacteristic(zwiftPlayWriteChar, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if(!gattFTMSService) {
|
||||
qDebug() << QStringLiteral("gattFTMSService is null!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wait_for_response) {
|
||||
@@ -70,6 +109,8 @@ void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ftmsbike::init() {
|
||||
@@ -77,23 +118,71 @@ void ftmsbike::init() {
|
||||
return;
|
||||
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(write, sizeof(write), "requestControl", false, true);
|
||||
bool ret = writeCharacteristic(write, sizeof(write), "requestControl", false, true);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
|
||||
initDone = true;
|
||||
initRequest = false;
|
||||
if(ret) {
|
||||
initDone = true;
|
||||
initRequest = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsbike::zwiftPlayInit() {
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
|
||||
if(zwiftPlayService && gears_zwift_ratio) {
|
||||
uint8_t rideOn[] = {0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e, 0x02, 0x01};
|
||||
writeCharacteristicZwiftPlay(rideOn, sizeof(rideOn), "rideOn", false, true);
|
||||
|
||||
uint8_t init1[] = {0x41, 0x08, 0x05};
|
||||
writeCharacteristicZwiftPlay(init1, sizeof(init1), "init1", false, true);
|
||||
|
||||
uint8_t init2[] = {0x04, 0x2a, 0x04, 0x10, 0xc0, 0xbb, 0x01};
|
||||
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
|
||||
|
||||
uint8_t init3[] = {0x00, 0x08, 0x00};
|
||||
writeCharacteristicZwiftPlay(init3, sizeof(init3), "init3", false, true);
|
||||
|
||||
writeCharacteristicZwiftPlay(init1, sizeof(init1), "init1", false, true);
|
||||
|
||||
uint8_t init4[] = {0x00, 0x08, 0x88, 0x04};
|
||||
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
|
||||
|
||||
uint8_t init5[] = {0x04, 0x2a, 0x0a, 0x10, 0xc0, 0xbb, 0x01, 0x20, 0xbf, 0x06, 0x28, 0xb4, 0x42};
|
||||
writeCharacteristicZwiftPlay(init5, sizeof(init5), "init5", false, true);
|
||||
|
||||
uint8_t init6[] = {0x04, 0x22, 0x0b, 0x08, 0x00, 0x10, 0xda, 0x02, 0x18, 0xec, 0x27, 0x20, 0x90, 0x03};
|
||||
writeCharacteristicZwiftPlay(init6, sizeof(init6), "init6", false, true);
|
||||
|
||||
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
|
||||
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
|
||||
|
||||
uint8_t init7[] = {0x04, 0x22, 0x03, 0x10, 0xa9, 0x01};
|
||||
writeCharacteristicZwiftPlay(init7, sizeof(init7), "init7", false, true);
|
||||
|
||||
writeCharacteristicZwiftPlay(init2, sizeof(init2), "init2", false, true);
|
||||
writeCharacteristicZwiftPlay(init4, sizeof(init4), "init4", false, true);
|
||||
|
||||
uint8_t init8[] = {0x04, 0x22, 0x02, 0x10, 0x00};
|
||||
writeCharacteristicZwiftPlay(init8, sizeof(init8), "init8", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsbike::forcePower(int16_t requestPower) {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
|
||||
if(resistance_lvl_mode) {
|
||||
forceResistance(resistanceFromPowerRequest(requestPower));
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
|
||||
|
||||
write[1] = ((uint16_t)requestPower) & 0xFF;
|
||||
write[2] = ((uint16_t)requestPower) >> 8;
|
||||
write[1] = ((uint16_t)requestPower) & 0xFF;
|
||||
write[2] = ((uint16_t)requestPower) >> 8;
|
||||
|
||||
writeCharacteristic(write, sizeof(write), QStringLiteral("forcePower ") + QString::number(requestPower));
|
||||
writeCharacteristic(write, sizeof(write), QStringLiteral("forcePower ") + QString::number(requestPower));
|
||||
|
||||
powerForced = true;
|
||||
powerForced = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ftmsbike::wattsFromResistance(double resistance) {
|
||||
@@ -127,7 +216,7 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
|
||||
resistance_lvl_mode == false) {
|
||||
resistance_lvl_mode == false && _3G_Cardio_RB == false) {
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
|
||||
@@ -140,6 +229,8 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
|
||||
if(_3G_Cardio_RB)
|
||||
requestResistance = requestResistance * 10;
|
||||
write[1] = ((uint8_t)(requestResistance));
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
@@ -153,6 +244,7 @@ void ftmsbike::update() {
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
zwiftPlayInit();
|
||||
initRequest = false;
|
||||
} else if (bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState //&&
|
||||
@@ -176,7 +268,9 @@ void ftmsbike::update() {
|
||||
forceResistance(currentResistance().value());
|
||||
}
|
||||
|
||||
if (requestResistance != -1) {
|
||||
auto virtualBike = this->VirtualBike();
|
||||
|
||||
if (requestResistance != -1 || lastGearValue != gears()) {
|
||||
if (requestResistance > 100) {
|
||||
requestResistance = 100;
|
||||
} // TODO, use the bluetooth value
|
||||
@@ -184,19 +278,127 @@ void ftmsbike::update() {
|
||||
requestResistance = 1;
|
||||
}
|
||||
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
if (requestResistance != currentResistance().value() || lastGearValue != gears()) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
// if the FTMS is connected, the ftmsCharacteristicChanged event will do all the stuff because it's a
|
||||
// FTMS bike. This condition handles the peloton requests
|
||||
auto virtualBike = this->VirtualBike();
|
||||
// FTMS bike. This condition handles the peloton requests
|
||||
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
|
||||
(requestPower == 0 || requestPower == -1)) {
|
||||
init();
|
||||
forceResistance(requestResistance);
|
||||
forceResistance(requestResistance + (gears() * 5));
|
||||
}
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
|
||||
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
|
||||
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) {
|
||||
uint8_t gear1[] = {0x04, 0x2a, 0x03, 0x10, 0xdc, 0xec};
|
||||
uint8_t gear2[] = {0x04, 0x2a, 0x04, 0x10, 0xdc, 0xec, 0x01};
|
||||
uint32_t gear_value = 0;
|
||||
|
||||
switch((int)gears()) {
|
||||
case 1:
|
||||
gear_value = 0x3acc;
|
||||
break;
|
||||
case 2:
|
||||
gear_value = 0x43fc;
|
||||
break;
|
||||
case 3:
|
||||
gear_value = 0x4dac;
|
||||
break;
|
||||
case 4:
|
||||
gear_value = 0x56d5;
|
||||
break;
|
||||
case 5:
|
||||
gear_value = 0x608c;
|
||||
break;
|
||||
case 6:
|
||||
gear_value = 0x6be8;
|
||||
break;
|
||||
case 7:
|
||||
gear_value = 0x77c4;
|
||||
break;
|
||||
case 8:
|
||||
gear_value = 0x183a0;
|
||||
break;
|
||||
case 9:
|
||||
gear_value = 0x191a8;
|
||||
break;
|
||||
case 10:
|
||||
gear_value = 0x19fb0;
|
||||
break;
|
||||
case 11:
|
||||
gear_value = 0x1adb8;
|
||||
break;
|
||||
case 12:
|
||||
gear_value = 0x1bbc0;
|
||||
break;
|
||||
case 13:
|
||||
gear_value = 0x1cbf3;
|
||||
break;
|
||||
case 14:
|
||||
gear_value = 0x1dca8;
|
||||
break;
|
||||
case 15:
|
||||
gear_value = 0x1ecdc;
|
||||
break;
|
||||
case 16:
|
||||
gear_value = 0x1fd90;
|
||||
break;
|
||||
case 17:
|
||||
gear_value = 0x290d4;
|
||||
break;
|
||||
case 18:
|
||||
gear_value = 0x2a498;
|
||||
break;
|
||||
case 19:
|
||||
gear_value = 0x2b7dc;
|
||||
break;
|
||||
case 20:
|
||||
gear_value = 0x2cb9f;
|
||||
break;
|
||||
case 21:
|
||||
gear_value = 0x2e2d8;
|
||||
break;
|
||||
case 22:
|
||||
gear_value = 0x2fa90;
|
||||
break;
|
||||
case 23:
|
||||
gear_value = 0x391c8;
|
||||
break;
|
||||
case 24:
|
||||
gear_value = 0x3acf3;
|
||||
break;
|
||||
default:
|
||||
// Gestione del caso di default
|
||||
break;
|
||||
}
|
||||
|
||||
gear_value = gear_value * settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble();
|
||||
|
||||
if(gear_value < 0x10000) {
|
||||
gear1[4] = gear_value & 0xFF;
|
||||
gear1[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
|
||||
writeCharacteristicZwiftPlay(gear1, sizeof(gear1), "gear", false, true);
|
||||
} else {
|
||||
gear2[4] = gear_value & 0xFF;
|
||||
gear2[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
|
||||
gear2[6] = ((gear_value & 0xFF0000) >> 16) & 0xFF;
|
||||
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gear", false, true);
|
||||
}
|
||||
|
||||
uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04};
|
||||
writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true);
|
||||
}
|
||||
|
||||
lastGearValue = gears();
|
||||
|
||||
if (requestPower != -1) {
|
||||
qDebug() << QStringLiteral("writing power") << requestPower;
|
||||
init();
|
||||
@@ -215,6 +417,13 @@ void ftmsbike::update() {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
|
||||
QSettings settings;
|
||||
if (settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
writeCharacteristic(write, sizeof(write), QStringLiteral("init SS2K"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,6 +455,17 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
return;
|
||||
}
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) { // Battery Service
|
||||
if(newValue.length() > 0) {
|
||||
uint8_t b = (uint8_t)newValue.at(0);
|
||||
if(b != battery_level)
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(QStringLiteral("Battery Level ") + QString::number(b) + " %");
|
||||
battery_level = b;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
|
||||
|
||||
union flags {
|
||||
@@ -331,7 +551,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
}
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
((double)lastRefreshCharacteristicChanged2AD2.msecsTo(now)));
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
@@ -359,7 +579,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
br) /
|
||||
(2.0 * ar)) *
|
||||
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
if (!resistance_received && !DU30_bike) {
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
@@ -379,6 +599,10 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
} else if(DOMYOS) {
|
||||
// doesn't send power at all and the resistance either
|
||||
m_watt = wattFromHR(true);
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
}
|
||||
|
||||
if (Flags.avgPower && newValue.length() > index + 1) {
|
||||
@@ -390,8 +614,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
}
|
||||
|
||||
if (Flags.expEnergy && newValue.length() > index + 1) {
|
||||
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
/*KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));*/
|
||||
index += 2;
|
||||
|
||||
// energy per hour
|
||||
@@ -399,17 +623,17 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
|
||||
// energy per minute
|
||||
index += 1;
|
||||
} else {
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged2AD2.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
|
||||
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -439,6 +663,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
if (Flags.remainingTime) {
|
||||
// todo
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged2AD2 = now;
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE)) {
|
||||
union flags {
|
||||
struct {
|
||||
@@ -500,7 +726,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(now)));
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
@@ -595,7 +821,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
@@ -629,6 +855,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
if (Flags.remainingTime) {
|
||||
// todo
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged2ACE = now;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -638,8 +866,6 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!heart || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
update_hr_from_external();
|
||||
@@ -699,7 +925,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
|
||||
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool()) {
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U) {
|
||||
QBluetoothUuid ftmsService((quint16)0x1826);
|
||||
if (s->serviceUuid() != ftmsService) {
|
||||
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
|
||||
@@ -711,7 +937,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
auto characteristics_list = s->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
|
||||
auto descriptors_list = c.descriptors();
|
||||
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
|
||||
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
|
||||
@@ -757,10 +983,23 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
gattWriteCharControlPointId = c;
|
||||
gattFTMSService = s;
|
||||
}
|
||||
|
||||
QBluetoothUuid _zwiftPlayWriteCharControlPointId(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1"));
|
||||
if (c.uuid() == _zwiftPlayWriteCharControlPointId) {
|
||||
qDebug() << QStringLiteral("Zwift Play service and Control Point found");
|
||||
zwiftPlayWriteChar = c;
|
||||
zwiftPlayService = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(gattFTMSService == nullptr && DOMYOS) {
|
||||
settings.setValue(QZSettings::domyosbike_notfmts, true);
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Domyos bike presents itself like a FTMS but it's not. Restart QZ to apply the fix, thanks.");
|
||||
}
|
||||
|
||||
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
|
||||
settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool()) {
|
||||
init();
|
||||
@@ -813,26 +1052,50 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
}
|
||||
|
||||
QByteArray b = newValue;
|
||||
QSettings settings;
|
||||
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
|
||||
|
||||
if (gattWriteCharControlPointId.isValid()) {
|
||||
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
|
||||
|
||||
// handling gears
|
||||
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS) {
|
||||
qDebug() << "applying gears mod" << m_gears;
|
||||
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && ((zwiftPlayService == nullptr && gears_zwift_ratio) || !gears_zwift_ratio)) {
|
||||
lastPacketFromFTMS.clear();
|
||||
for(int i=0; i<b.length(); i++)
|
||||
lastPacketFromFTMS.append(b.at(i));
|
||||
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
|
||||
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
|
||||
if (m_gears != 0) {
|
||||
slope += (m_gears * 50);
|
||||
b[3] = slope & 0xFF;
|
||||
b[4] = slope >> 8;
|
||||
if (gears() != 0) {
|
||||
slope += (gears() * 50);
|
||||
}
|
||||
b[3] = slope & 0xFF;
|
||||
b[4] = slope >> 8;
|
||||
|
||||
qDebug() << "applying gears mod" << gears() << slope;
|
||||
/*} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) {
|
||||
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
|
||||
uint8_t gear2[] = {0x04, 0x22, 0x02, 0x10, 0x00};
|
||||
int g = (int)(((double)slope / 100.0) + settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble());
|
||||
if(g < 0) {
|
||||
g = 0;
|
||||
}
|
||||
gear2[4] = g;
|
||||
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gearInclination", false, false);*/
|
||||
} else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
|
||||
lastPacketFromFTMS.clear();
|
||||
for(int i=0; i<b.length(); i++)
|
||||
lastPacketFromFTMS.append(b.at(i));
|
||||
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
|
||||
int16_t power = (((uint8_t)b.at(1)) + (b.at(2) << 8));
|
||||
if (gears() != 0) {
|
||||
power += (gears() * 10);
|
||||
}
|
||||
b[1] = power & 0xFF;
|
||||
b[2] = power >> 8;
|
||||
qDebug() << "applying gears mod" << gears() << gearsZwiftRatio() << power;
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray(b);
|
||||
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
writeCharacteristic((uint8_t*)b.data(), b.length(), "injectWrite ", false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,6 +1161,7 @@ resistance_t ftmsbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
}
|
||||
|
||||
void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
QSettings settings;
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
{
|
||||
@@ -912,6 +1176,22 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
qDebug() << QStringLiteral("DU30 found");
|
||||
max_resistance = 32;
|
||||
DU30_bike = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("ICSE") && bluetoothDevice.name().length() == 4)) {
|
||||
qDebug() << QStringLiteral("ICSE found");
|
||||
ICSE = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
|
||||
qDebug() << QStringLiteral("DOMYOS found");
|
||||
DOMYOS = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("3G Cardio RB"))) {
|
||||
qDebug() << QStringLiteral("_3G_Cardio_RB found");
|
||||
_3G_Cardio_RB = true;
|
||||
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_190U"))) {
|
||||
qDebug() << QStringLiteral("SCH_190U found");
|
||||
SCH_190U = true;
|
||||
}
|
||||
|
||||
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
|
||||
resistance_lvl_mode = true;
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -68,15 +68,18 @@ enum FtmsResultCode {
|
||||
class ftmsbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ftmsbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
bool connected() override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
|
||||
private:
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void zwiftPlayInit();
|
||||
void startDiscover();
|
||||
uint16_t watts() override;
|
||||
void init();
|
||||
@@ -90,12 +93,18 @@ class ftmsbike : public bike {
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointId;
|
||||
QLowEnergyService *gattFTMSService = nullptr;
|
||||
|
||||
QLowEnergyCharacteristic zwiftPlayWriteChar;
|
||||
QLowEnergyService *zwiftPlayService = nullptr;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
QByteArray lastPacketFromFTMS;
|
||||
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
|
||||
QDateTime lastRefreshCharacteristicChanged2ACE = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
double lastGearValue = -1;
|
||||
int max_resistance = 100;
|
||||
|
||||
bool initDone = false;
|
||||
@@ -110,6 +119,12 @@ class ftmsbike : public bike {
|
||||
bool resistance_received = false;
|
||||
|
||||
bool DU30_bike = false;
|
||||
bool ICSE = false;
|
||||
bool DOMYOS = false;
|
||||
bool _3G_Cardio_RB = false;
|
||||
bool SCH_190U = false;
|
||||
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -78,8 +78,13 @@ void ftmsrower::update() {
|
||||
}
|
||||
|
||||
if (initRequest) {
|
||||
uint8_t write[] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
if(I_ROWER) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(write, sizeof(write), "start", false, true);
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
|
||||
}
|
||||
|
||||
initRequest = false;
|
||||
} else if (bluetoothDevice.isValid() &&
|
||||
@@ -583,6 +588,9 @@ void ftmsrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("DFIT-L-R"))) {
|
||||
DFIT_L_R = true;
|
||||
qDebug() << "DFIT_L_R found!";
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("I-ROWER"))) {
|
||||
I_ROWER = true;
|
||||
qDebug() << "I_ROWER found!";
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("PM5"))) {
|
||||
PM5 = true;
|
||||
qDebug() << "PM5 found!";
|
||||
|
||||
@@ -71,6 +71,7 @@ class ftmsrower : public rower {
|
||||
|
||||
bool WATER_ROWER = false;
|
||||
bool DFIT_L_R = false;
|
||||
bool I_ROWER = false;
|
||||
QDateTime lastStroke = QDateTime::currentDateTime();
|
||||
double lastStrokesCount = 0;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
horizongr7bike::horizongr7bike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
horizongr7bike::horizongr7bike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
class horizongr7bike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
horizongr7bike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
horizongr7bike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
bool connected() override;
|
||||
|
||||
@@ -63,7 +63,7 @@ class horizongr7bike : public bike {
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
|
||||
bool initDone = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "horizontreadmill.h"
|
||||
|
||||
#include "homeform.h"
|
||||
#include "devices/ftmsbike/ftmsbike.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
@@ -61,7 +61,11 @@ void horizontreadmill::writeCharacteristic(QLowEnergyService *service, QLowEnerg
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
service->writeCharacteristic(characteristic, *writeBuffer);
|
||||
if (characteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
service->writeCharacteristic(characteristic, *writeBuffer, QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
service->writeCharacteristic(characteristic, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log)
|
||||
qDebug() << " >> " << writeBuffer->toHex(' ') << " // " << info;
|
||||
@@ -803,6 +807,15 @@ void horizontreadmill::btinit() {
|
||||
messageID = 0x10;
|
||||
}
|
||||
|
||||
if(wellfit_treadmill) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
QThread::msleep(500);
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -815,6 +828,8 @@ void horizontreadmill::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << initRequest << firstStateChanged << bluetoothDevice.isValid();
|
||||
|
||||
if (initRequest && firstStateChanged) {
|
||||
btinit();
|
||||
initRequest = false;
|
||||
@@ -897,6 +912,7 @@ void horizontreadmill::update() {
|
||||
|
||||
if (requestInclination != currentInclination().value() && requestInclination >= minInclination &&
|
||||
requestInclination <= 15) {
|
||||
|
||||
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
|
||||
forceIncline(requestInclination);
|
||||
}
|
||||
@@ -1139,7 +1155,7 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
if(!anplus_treadmill && !trx3500_treadmill) {
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
@@ -1205,7 +1221,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
if(!anplus_treadmill && !trx3500_treadmill) {
|
||||
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
@@ -1301,7 +1317,59 @@ void horizontreadmill::forceIncline(double requestIncline) {
|
||||
|
||||
writeS[1] = conversion[r];
|
||||
writeS[2] = conversion1[r];
|
||||
} else if(ICONCEPT_FTMS_treadmill) {
|
||||
if(requestInclination > 0 && requestInclination < 1) {
|
||||
writeS[1] = 0x3C;
|
||||
writeS[2] = 0x00;
|
||||
} else if(requestInclination > 1 && requestInclination < 2) {
|
||||
writeS[1] = 0x82;
|
||||
writeS[2] = 0x00;
|
||||
} else if(requestInclination > 2 && requestInclination < 3) {
|
||||
writeS[1] = 0xC8;
|
||||
writeS[2] = 0x00;
|
||||
} else if(requestInclination > 3 && requestInclination < 4) {
|
||||
writeS[1] = 0x04;
|
||||
writeS[2] = 0x01;
|
||||
} else if(requestInclination > 4 && requestInclination < 5) {
|
||||
writeS[1] = 0x4A;
|
||||
writeS[2] = 0x01;
|
||||
} else if(requestInclination > 5 && requestInclination < 6) {
|
||||
writeS[1] = 0x90;
|
||||
writeS[2] = 0x01;
|
||||
} else if(requestInclination > 6 && requestInclination < 7) {
|
||||
writeS[1] = 0xCC;
|
||||
writeS[2] = 0x01;
|
||||
} else if(requestInclination > 7 && requestInclination < 8) {
|
||||
writeS[1] = 0x12;
|
||||
writeS[2] = 0x02;
|
||||
} else if(requestInclination > 8 && requestInclination < 9) {
|
||||
writeS[1] = 0x58;
|
||||
writeS[2] = 0x02;
|
||||
} else if(requestInclination > 9 && requestInclination < 10) {
|
||||
writeS[1] = 0x94;
|
||||
writeS[2] = 0x02;
|
||||
} else if(requestInclination > 10 && requestInclination < 11) {
|
||||
writeS[1] = 0xDA;
|
||||
writeS[2] = 0x02;
|
||||
} else if(requestInclination > 11 && requestInclination < 12) {
|
||||
writeS[1] = 0x20;
|
||||
writeS[2] = 0x03;
|
||||
} else if(requestInclination > 12 && requestInclination < 13) {
|
||||
writeS[1] = 0x5C;
|
||||
writeS[2] = 0x03;
|
||||
} else if(requestInclination > 13 && requestInclination < 14) {
|
||||
writeS[1] = 0xA2;
|
||||
writeS[2] = 0x03;
|
||||
} else if(requestInclination > 14 && requestInclination < 15) {
|
||||
writeS[1] = 0xE8;
|
||||
writeS[2] = 0x03;
|
||||
} else {
|
||||
writeS[1] = 0x00;
|
||||
writeS[2] = 0x00;
|
||||
}
|
||||
} else {
|
||||
if(HORIZON_78AT_treadmill)
|
||||
requestIncline = requestIncline / 2.0;
|
||||
writeS[1] = ((int16_t)(requestIncline * 10.0)) & 0xFF;
|
||||
writeS[2] = ((int16_t)(requestIncline * 10.0)) >> 8;
|
||||
}
|
||||
@@ -1331,6 +1399,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + characteristic.uuid().toString() + " " + QString::number(newValue.length()) +
|
||||
" " + newValue.toHex(' '));
|
||||
@@ -1372,11 +1441,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
Inclination = treadmillInclinationOverride((double)((uint8_t)lastPacketComplete.at(30)) / 10.0);
|
||||
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
|
||||
|
||||
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
|
||||
if (firstDistanceCalculated && watts(weight))
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
|
||||
((((0.048 * ((double)watts(weight)) +
|
||||
1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
weight * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
@@ -1398,11 +1467,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
Inclination = treadmillInclinationOverride((double)((uint8_t)newValue.at(63)) / 10.0);
|
||||
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
|
||||
|
||||
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
|
||||
if (firstDistanceCalculated && watts(weight))
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
|
||||
((((0.048 * ((double)watts(weight)) +
|
||||
1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
weight * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
@@ -1422,11 +1491,11 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
|
||||
// Inclination = (double)((uint8_t)newValue.at(3)) / 10.0;
|
||||
// emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
|
||||
if (firstDistanceCalculated && watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
|
||||
if (firstDistanceCalculated && watts(weight))
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
|
||||
((((0.048 * ((double)watts(weight)) +
|
||||
1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
weight * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
@@ -1448,6 +1517,164 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
Speed = 0;
|
||||
horizonPaused = true;
|
||||
qDebug() << "stop from the treadmill";
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
|
||||
union flags {
|
||||
struct {
|
||||
uint16_t moreData : 1;
|
||||
uint16_t avgSpeed : 1;
|
||||
uint16_t instantCadence : 1;
|
||||
uint16_t avgCadence : 1;
|
||||
uint16_t totDistance : 1;
|
||||
uint16_t resistanceLvl : 1;
|
||||
uint16_t instantPower : 1;
|
||||
uint16_t avgPower : 1;
|
||||
uint16_t expEnergy : 1;
|
||||
uint16_t heartRate : 1;
|
||||
uint16_t metabolic : 1;
|
||||
uint16_t elapsedTime : 1;
|
||||
uint16_t remainingTime : 1;
|
||||
uint16_t spare : 3;
|
||||
};
|
||||
|
||||
uint16_t word_flags;
|
||||
};
|
||||
|
||||
flags Flags;
|
||||
int index = 0;
|
||||
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
|
||||
index += 2;
|
||||
|
||||
if (!Flags.moreData) {
|
||||
Speed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index)))) /
|
||||
100.0;
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
|
||||
if (Flags.avgSpeed) {
|
||||
double avgSpeed;
|
||||
avgSpeed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index)))) /
|
||||
100.0;
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed));
|
||||
}
|
||||
|
||||
if (Flags.instantCadence) {
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index)))) /
|
||||
2.0;
|
||||
}
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
}
|
||||
|
||||
if (Flags.avgCadence) {
|
||||
double avgCadence;
|
||||
avgCadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index)))) /
|
||||
2.0;
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence));
|
||||
}
|
||||
|
||||
if (Flags.totDistance) {
|
||||
|
||||
/*
|
||||
* the distance sent from the most trainers is a total distance, so it's useless for QZ
|
||||
*
|
||||
Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
|
||||
(uint32_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint32_t)((uint8_t)newValue.at(index)))) /
|
||||
1000.0;*/
|
||||
index += 3;
|
||||
}
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
if (Flags.resistanceLvl) {
|
||||
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
}
|
||||
|
||||
if (Flags.instantPower) {
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")))
|
||||
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
}
|
||||
|
||||
if (Flags.avgPower && newValue.length() > index + 1) {
|
||||
double avgPower;
|
||||
avgPower = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
|
||||
}
|
||||
|
||||
if (Flags.expEnergy && newValue.length() > index + 1) {
|
||||
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
|
||||
// energy per hour
|
||||
index += 2;
|
||||
|
||||
// energy per minute
|
||||
index += 1;
|
||||
} else {
|
||||
if (watts(weight))
|
||||
KCal += ((((0.048 * ((double)watts(weight)) + 1.19) *
|
||||
weight * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)(((uint8_t)newValue.at(index))));
|
||||
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
} else {
|
||||
Flags.heartRate = false;
|
||||
}
|
||||
heart = Flags.heartRate;
|
||||
}
|
||||
|
||||
if (Flags.metabolic) {
|
||||
// todo
|
||||
}
|
||||
|
||||
if (Flags.elapsedTime) {
|
||||
// todo
|
||||
}
|
||||
|
||||
if (Flags.remainingTime) {
|
||||
// todo
|
||||
}
|
||||
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACD)) {
|
||||
lastPacket = newValue;
|
||||
|
||||
@@ -1514,7 +1741,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
if (Flags.inclination) {
|
||||
if(!tunturi_t60_treadmill)
|
||||
if(!tunturi_t60_treadmill && !ICONCEPT_FTMS_treadmill)
|
||||
Inclination = treadmillInclinationOverride((double)(
|
||||
(int16_t)(
|
||||
((int16_t)(int8_t)newValue.at(index + 1) << 8) |
|
||||
@@ -1522,6 +1749,43 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
)
|
||||
) /
|
||||
10.0);
|
||||
else if(ICONCEPT_FTMS_treadmill) {
|
||||
uint8_t val1 = (uint8_t)newValue.at(index);
|
||||
uint8_t val2 = (uint8_t)newValue.at(index + 1);
|
||||
if(val1 == 0x3C && val2 == 0x00) {
|
||||
Inclination = 1;
|
||||
} else if(val1 == 0x82 && val2 == 0x00) {
|
||||
Inclination = 2;
|
||||
} else if(val1 == 0xC8 && val2 == 0x00) {
|
||||
Inclination = 3;
|
||||
} else if(val1 == 0x04 && val2 == 0x01) {
|
||||
Inclination = 4;
|
||||
} else if(val1 == 0x4A && val2 == 0x01) {
|
||||
Inclination = 5;
|
||||
} else if(val1 == 0x90 && val2 == 0x01) {
|
||||
Inclination = 6;
|
||||
} else if(val1 == 0xCC && val2 == 0x01) {
|
||||
Inclination = 7;
|
||||
} else if(val1 == 0x12 && val2 == 0x02) {
|
||||
Inclination = 8;
|
||||
} else if(val1 == 0x58 && val2 == 0x02) {
|
||||
Inclination = 9;
|
||||
} else if(val1 == 0x94 && val2 == 0x02) {
|
||||
Inclination = 10;
|
||||
} else if(val1 == 0xDA && val2 == 0x02) {
|
||||
Inclination = 11;
|
||||
} else if(val1 == 0x20 && val2 == 0x03) {
|
||||
Inclination = 12;
|
||||
} else if(val1 == 0x5C && val2 == 0x03) {
|
||||
Inclination = 13;
|
||||
} else if(val1 == 0xA2 && val2 == 0x03) {
|
||||
Inclination = 14;
|
||||
} else if(val1 == 0xE8 && val2 == 0x03) {
|
||||
Inclination = 15;
|
||||
} else {
|
||||
Inclination = 0;
|
||||
}
|
||||
}
|
||||
index += 4; // the ramo value is useless
|
||||
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
|
||||
}
|
||||
@@ -1838,10 +2102,21 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
|
||||
QBluetoothUuid _gattTreadmillDataId((quint16)0x2ACD);
|
||||
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
|
||||
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
|
||||
QBluetoothUuid _DomyosServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
|
||||
|
||||
if(s->serviceUuid() == _DomyosServiceId && DOMYOS) {
|
||||
settings.setValue(QZSettings::domyostreadmill_notfmts, true);
|
||||
settings.sync();
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("Domyos Treadmill presents itself like a FTMS but it's not. Restart QZ to apply the fix, thanks.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
|
||||
qDebug() << QStringLiteral("not all services discovered");
|
||||
return;
|
||||
@@ -1868,9 +2143,9 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
auto characteristics_list = s->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
|
||||
|
||||
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharControlPointId) {
|
||||
if (c.uuid() == _gattWriteCharControlPointId) {
|
||||
qDebug() << QStringLiteral("FTMS service and Control Point found");
|
||||
gattWriteCharControlPointId = c;
|
||||
gattFTMSService = s;
|
||||
@@ -1881,7 +2156,10 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
// some treadmills doesn't have the control point and also are Cross Trainer devices so i need
|
||||
// anyway to get the FTMS Service at least
|
||||
gattFTMSService = s;
|
||||
}
|
||||
}/* else if (c.uuid() == _gattInclinationSupported) {
|
||||
s->readCharacteristic(c);
|
||||
qDebug() << s->serviceUuid() << c.uuid() << "reading!";
|
||||
}*/
|
||||
|
||||
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService &&
|
||||
!settings
|
||||
@@ -1925,6 +2203,24 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
}
|
||||
|
||||
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("notification subscribed!");
|
||||
} else if ((c.properties() & QLowEnergyCharacteristic::Indicate) == QLowEnergyCharacteristic::Indicate &&
|
||||
// if it's a FTMS treadmill and has FTMS and/or RSC service too
|
||||
((((gattFTMSService && s->serviceUuid() == gattFTMSService->serviceUuid()))
|
||||
&& !gattCustomService))) {
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x02);
|
||||
descriptor.append((char)0x00);
|
||||
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
|
||||
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
notificationSubscribed++;
|
||||
} else {
|
||||
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
|
||||
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
|
||||
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
|
||||
<< QStringLiteral(" is not valid");
|
||||
}
|
||||
|
||||
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("indication subscribed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1996,6 +2292,17 @@ void horizontreadmill::characteristicWritten(const QLowEnergyCharacteristic &cha
|
||||
|
||||
void horizontreadmill::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' ');
|
||||
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
|
||||
if(characteristic.uuid() == _gattInclinationSupported && newValue.length() > 2) {
|
||||
minInclination = ((double)(
|
||||
(int16_t)(
|
||||
((int16_t)(int8_t)newValue.at(1) << 8) |
|
||||
(uint8_t)newValue.at(0)
|
||||
)
|
||||
) /
|
||||
10.0);
|
||||
qDebug() << "new minInclination is " << minInclination;
|
||||
}
|
||||
}
|
||||
|
||||
void horizontreadmill::serviceScanDone(void) {
|
||||
@@ -2004,25 +2311,13 @@ void horizontreadmill::serviceScanDone(void) {
|
||||
initRequest = false;
|
||||
firstStateChanged = 0;
|
||||
auto services_list = m_control->services();
|
||||
QBluetoothUuid ftmsService((quint16)0x1826);
|
||||
QBluetoothUuid CustomService((quint16)0xFFF0);
|
||||
|
||||
for (const QBluetoothUuid &s : qAsConst(services_list)) {
|
||||
#ifdef Q_OS_WIN
|
||||
if (s == ftmsService || s == CustomService)
|
||||
#endif
|
||||
{
|
||||
qDebug() << s << "discovering...";
|
||||
gattCommunicationChannelService.append(m_control->createServiceObject(s));
|
||||
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
|
||||
&horizontreadmill::stateChanged);
|
||||
gattCommunicationChannelService.constLast()->discoverDetails();
|
||||
}
|
||||
#ifdef Q_OS_WIN
|
||||
else {
|
||||
qDebug() << s << "NOT discovering!";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2052,12 +2347,18 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TM"))) {
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TMP"))) {
|
||||
mobvoi_tmp_treadmill = true;
|
||||
qDebug() << QStringLiteral("MOBVOI TMP workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TM"))) {
|
||||
mobvoi_treadmill = true;
|
||||
qDebug() << QStringLiteral("MOBVOI TM workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL"))) {
|
||||
kettler_treadmill = true;
|
||||
qDebug() << QStringLiteral("KETTLER TREADMILL workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("WELLFIT TM"))) {
|
||||
wellfit_treadmill = true;
|
||||
qDebug() << QStringLiteral("WELLFIT TREADMILL workaround ON!");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) {
|
||||
anplus_treadmill = true;
|
||||
qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!");
|
||||
@@ -2082,6 +2383,18 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("TREADMILL"))) { // Technogym Run
|
||||
technogymrun = true;
|
||||
qDebug() << QStringLiteral("Technogym Run TREADMILL workaround ON!");
|
||||
} else if(device.name().toUpper().startsWith(QStringLiteral("SW"))) {
|
||||
qDebug() << QStringLiteral("SW TREADMILL workaround ON!");
|
||||
disableAutoPause = true;
|
||||
} else if(device.name().toUpper().startsWith("HORIZON_7.8AT")) {
|
||||
HORIZON_78AT_treadmill = true;
|
||||
qDebug() << QStringLiteral("HORIZON_7.8AT workaround ON!");
|
||||
} else if(device.name().toUpper().startsWith("T01_")) {
|
||||
ICONCEPT_FTMS_treadmill = true;
|
||||
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
|
||||
} else if ((device.name().toUpper().startsWith("DOMYOS"))) {
|
||||
qDebug() << QStringLiteral("DOMYOS found");
|
||||
DOMYOS = true;
|
||||
}
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
|
||||
@@ -2090,6 +2403,11 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
#endif
|
||||
} else if(device.name().toUpper().startsWith(QStringLiteral("TRX4500"))) {
|
||||
qDebug() << QStringLiteral("TRX4500 TREADMILL workaround ON!");
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
#endif
|
||||
} else {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
@@ -2180,6 +2498,8 @@ int horizontreadmill::GenerateCRC_CCITT(uint8_t *PUPtr8, int PU16_Count, int crc
|
||||
}
|
||||
|
||||
bool horizontreadmill::autoPauseWhenSpeedIsZero() {
|
||||
if(disableAutoPause == true)
|
||||
return false;
|
||||
if (lastStart == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStart + 10000))
|
||||
return true;
|
||||
else
|
||||
@@ -2799,7 +3119,7 @@ void horizontreadmill::testProfileCRC() {
|
||||
double horizontreadmill::minStepInclination() {
|
||||
QSettings settings;
|
||||
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill)
|
||||
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill)
|
||||
return 1.0;
|
||||
else
|
||||
return 0.5;
|
||||
|
||||
@@ -71,8 +71,6 @@ class horizontreadmill : public treadmill {
|
||||
uint8_t firstStateChanged = 0;
|
||||
double lastSpeed = 0.0;
|
||||
double lastInclination = 0;
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
bool horizonPaused = false;
|
||||
double lastHorizonForceSpeed = 0;
|
||||
double minInclination = 0.0;
|
||||
@@ -88,7 +86,9 @@ class horizontreadmill : public treadmill {
|
||||
int32_t messageID = 0;
|
||||
|
||||
bool mobvoi_treadmill = false;
|
||||
bool mobvoi_tmp_treadmill = false;
|
||||
bool kettler_treadmill = false;
|
||||
bool wellfit_treadmill = false;
|
||||
bool sole_tt8_treadmill = false;
|
||||
bool anplus_treadmill = false;
|
||||
bool tunturi_t60_treadmill = false;
|
||||
@@ -97,6 +97,10 @@ class horizontreadmill : public treadmill {
|
||||
bool sole_f89_treadmill = false;
|
||||
bool schwinn_810_treadmill = false;
|
||||
bool technogymrun = false;
|
||||
bool disableAutoPause = false;
|
||||
bool HORIZON_78AT_treadmill = false;
|
||||
bool ICONCEPT_FTMS_treadmill = false;
|
||||
bool DOMYOS = false;
|
||||
|
||||
void testProfileCRC();
|
||||
void updateProfileCRC();
|
||||
|
||||
@@ -22,7 +22,13 @@ iconceptbike::iconceptbike() {
|
||||
void iconceptbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("BH DUALKIT"))) {
|
||||
//if (device.name().toUpper().startsWith(QStringLiteral("BH DUALKIT")))
|
||||
{
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("BH-"))) {
|
||||
i_Nexor = true;
|
||||
qDebug() << "BH i-Nexor workaround enabled";
|
||||
}
|
||||
|
||||
bluetoothDevice = device;
|
||||
|
||||
// Create a discovery agent and connect to its signals
|
||||
@@ -79,6 +85,8 @@ void iconceptbike::serviceDiscovered(const QBluetoothServiceInfo &service) {
|
||||
connect(socket, &QBluetoothSocket::disconnected, this, &iconceptbike::disconnected);
|
||||
connect(socket, QOverload<QBluetoothSocket::SocketError>::of(&QBluetoothSocket::error), this,
|
||||
&iconceptbike::onSocketErrorOccurred);
|
||||
} else {
|
||||
qDebug () << QStringLiteral("service ignored!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,50 +138,109 @@ void iconceptbike::update() {
|
||||
void iconceptbike::rfCommConnected() {
|
||||
emit debug(QStringLiteral("connected ") + socket->peerName());
|
||||
|
||||
const uint8_t init1[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
|
||||
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
|
||||
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
|
||||
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
|
||||
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
|
||||
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
|
||||
const uint8_t init2[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
|
||||
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
|
||||
const uint8_t init3[] = {0x55, 0x17, 0x01, 0x01, 0x55, 0xb5, 0x01, 0xff};
|
||||
const uint8_t init4[] = {0x55, 0x01, 0x06, 0x1e, 0x00, 0x3c, 0x00, 0xaa, 0x00};
|
||||
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
|
||||
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
|
||||
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
|
||||
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
|
||||
const uint8_t init9[] = {0x55, 0x11, 0x01, 0x08};
|
||||
if(i_Nexor) {
|
||||
const uint8_t init01[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
|
||||
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
|
||||
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
|
||||
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
|
||||
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
|
||||
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
|
||||
const uint8_t init02[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
|
||||
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
|
||||
socket->write((char *)init01, sizeof(init01));
|
||||
qDebug() << QStringLiteral(" init01 write");
|
||||
socket->write((char *)init02, sizeof(init02));
|
||||
qDebug() << QStringLiteral(" init02 write");
|
||||
QThread::msleep(2000);
|
||||
|
||||
socket->write((char *)init1, sizeof(init1));
|
||||
qDebug() << QStringLiteral(" init1 write");
|
||||
socket->write((char *)init2, sizeof(init2));
|
||||
qDebug() << QStringLiteral(" init2 write");
|
||||
QThread::msleep(2000);
|
||||
socket->write((char *)init3, sizeof(init3));
|
||||
qDebug() << QStringLiteral(" init3 write");
|
||||
QThread::msleep(2000);
|
||||
socket->write((char *)init3, sizeof(init3));
|
||||
qDebug() << QStringLiteral(" init3 write");
|
||||
QThread::msleep(500);
|
||||
socket->write((char *)init4, sizeof(init4));
|
||||
qDebug() << QStringLiteral(" init4 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init5, sizeof(init5));
|
||||
qDebug() << QStringLiteral(" init5 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init6, sizeof(init6));
|
||||
qDebug() << QStringLiteral(" init6 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init7, sizeof(init7));
|
||||
qDebug() << QStringLiteral(" init7 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init8, sizeof(init8));
|
||||
qDebug() << QStringLiteral(" init8 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init9, sizeof(init9));
|
||||
qDebug() << QStringLiteral(" init9 write");
|
||||
const uint8_t init1[] = {0x55, 0x0a, 0x01, 0x02, 0x53};
|
||||
socket->write((char *)init1, sizeof(init1));
|
||||
qDebug() << QStringLiteral(" init1 write");
|
||||
|
||||
const uint8_t init2[] = {0x55, 0x17, 0x01, 0x01, 0x4f};
|
||||
socket->write((char *)init2, sizeof(init2));
|
||||
qDebug() << QStringLiteral(" init2 write");
|
||||
QThread::msleep(600);
|
||||
|
||||
const uint8_t init3[] = {0x55, 0x01, 0x06, 0x21, 0x01, 0x50, 0x00, 0xb4, 0x00};
|
||||
socket->write((char *)init3, sizeof(init3));
|
||||
qDebug() << QStringLiteral(" init3 write");
|
||||
QThread::msleep(400);
|
||||
|
||||
const uint8_t init4[] = {0x55, 0x17, 0x01, 0x01};
|
||||
socket->write((char *)init4, sizeof(init4));
|
||||
qDebug() << QStringLiteral(" init4 write");
|
||||
QThread::msleep(200);
|
||||
|
||||
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
|
||||
socket->write((char *)init5, sizeof(init5));
|
||||
qDebug() << QStringLiteral(" init5 write");
|
||||
QThread::msleep(600);
|
||||
|
||||
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
|
||||
socket->write((char *)init6, sizeof(init6));
|
||||
qDebug() << QStringLiteral(" init6 write");
|
||||
QThread::msleep(200);
|
||||
|
||||
socket->write((char *)init4, sizeof(init4));
|
||||
qDebug() << QStringLiteral(" init4 write");
|
||||
QThread::msleep(400);
|
||||
|
||||
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
|
||||
socket->write((char *)init7, sizeof(init7));
|
||||
qDebug() << QStringLiteral(" init7 write");
|
||||
QThread::msleep(600);
|
||||
|
||||
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
|
||||
socket->write((char *)init8, sizeof(init8));
|
||||
qDebug() << QStringLiteral(" init8 write");
|
||||
QThread::msleep(600);
|
||||
} else {
|
||||
const uint8_t init1[] = {0x55, 0x0c, 0x01, 0xff, 0x55, 0xbb, 0x01, 0xff, 0x55, 0x24, 0x01, 0xff, 0x55, 0x25, 0x01,
|
||||
0xff, 0x55, 0x26, 0x01, 0xff, 0x55, 0x27, 0x01, 0xff, 0x55, 0x02, 0x01, 0xff, 0x55, 0x03,
|
||||
0x01, 0xff, 0x55, 0x04, 0x01, 0xff, 0x55, 0x06, 0x01, 0xff, 0x55, 0x1f, 0x01, 0xff, 0x55,
|
||||
0xa0, 0x01, 0xff, 0x55, 0xb0, 0x01, 0xff, 0x55, 0xb2, 0x01, 0xff, 0x55, 0xb3, 0x01, 0xff,
|
||||
0x55, 0xb4, 0x01, 0xff, 0x55, 0xb5, 0x01, 0xff, 0x55, 0xb6, 0x01, 0xff, 0x55, 0xb7, 0x01,
|
||||
0xff, 0x55, 0xb8, 0x01, 0xff, 0x55, 0xb9, 0x01, 0xff, 0x55, 0xba, 0x01, 0xff};
|
||||
const uint8_t init2[] = {0x55, 0x0b, 0x01, 0xff, 0x55, 0x18, 0x01, 0xff, 0x55, 0x19,
|
||||
0x01, 0xff, 0x55, 0x1a, 0x01, 0xff, 0x55, 0x1b, 0x01, 0xff};
|
||||
const uint8_t init3[] = {0x55, 0x17, 0x01, 0x01, 0x55, 0xb5, 0x01, 0xff};
|
||||
const uint8_t init4[] = {0x55, 0x01, 0x06, 0x1e, 0x00, 0x3c, 0x00, 0xaa, 0x00};
|
||||
const uint8_t init5[] = {0x55, 0x15, 0x01, 0x00};
|
||||
const uint8_t init6[] = {0x55, 0x11, 0x01, 0x01};
|
||||
const uint8_t init7[] = {0x55, 0x0a, 0x01, 0x01};
|
||||
const uint8_t init8[] = {0x55, 0x07, 0x01, 0xff};
|
||||
const uint8_t init9[] = {0x55, 0x11, 0x01, 0x08};
|
||||
|
||||
socket->write((char *)init1, sizeof(init1));
|
||||
qDebug() << QStringLiteral(" init1 write");
|
||||
socket->write((char *)init2, sizeof(init2));
|
||||
qDebug() << QStringLiteral(" init2 write");
|
||||
QThread::msleep(2000);
|
||||
socket->write((char *)init3, sizeof(init3));
|
||||
qDebug() << QStringLiteral(" init3 write");
|
||||
QThread::msleep(2000);
|
||||
socket->write((char *)init3, sizeof(init3));
|
||||
qDebug() << QStringLiteral(" init3 write");
|
||||
QThread::msleep(500);
|
||||
socket->write((char *)init4, sizeof(init4));
|
||||
qDebug() << QStringLiteral(" init4 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init5, sizeof(init5));
|
||||
qDebug() << QStringLiteral(" init5 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init6, sizeof(init6));
|
||||
qDebug() << QStringLiteral(" init6 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init7, sizeof(init7));
|
||||
qDebug() << QStringLiteral(" init7 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init8, sizeof(init8));
|
||||
qDebug() << QStringLiteral(" init8 write");
|
||||
QThread::msleep(600);
|
||||
socket->write((char *)init9, sizeof(init9));
|
||||
qDebug() << QStringLiteral(" init9 write");
|
||||
}
|
||||
|
||||
initDone = true;
|
||||
// requestStart = 1;
|
||||
@@ -195,7 +262,9 @@ void iconceptbike::readSocket() {
|
||||
bool bh_spada_2_watt =
|
||||
settings.value(QZSettings::bh_spada_2_watt, QZSettings::default_bh_spada_2_watt).toBool();
|
||||
elapsed = GetElapsedTimeFromPacket(line);
|
||||
Distance = GetDistanceFromPacket(line);
|
||||
//Distance = GetDistanceFromPacket(line);
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
Distance += ((Speed.value() / 3600000.0) * ((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
KCal = GetCaloriesFromPacket(line);
|
||||
if (bh_spada_2_watt) {
|
||||
m_watt = GetWattFromPacket(line);
|
||||
@@ -213,7 +282,7 @@ void iconceptbike::readSocket() {
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
} else {
|
||||
Speed = GetSpeedFromPacket(line);
|
||||
@@ -226,7 +295,7 @@ void iconceptbike::readSocket() {
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
|
||||
|
||||
@@ -53,6 +53,7 @@ class iconceptbike : public bike {
|
||||
QTimer *refresh;
|
||||
bool initDone = false;
|
||||
uint8_t firstStateChanged = 0;
|
||||
bool i_Nexor = false;
|
||||
|
||||
uint16_t GetElapsedTimeFromPacket(const QByteArray &packet);
|
||||
uint16_t GetDistanceFromPacket(const QByteArray &packet);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
class iconceptelliptical : public elliptical {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit iconceptelliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
explicit iconceptelliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
|
||||
public slots:
|
||||
@@ -52,7 +52,7 @@ class iconceptelliptical : public elliptical {
|
||||
private:
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
|
||||
QBluetoothServiceDiscoveryAgent *discoveryAgent;
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
keepbike::keepbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
keepbike::keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
@@ -355,7 +355,9 @@ void keepbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
Q_ASSERT(gattWriteCharacteristic.isValid());
|
||||
if(!gattWriteCharacteristic.isValid()) {
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
}
|
||||
Q_ASSERT(gattNotifyCharacteristic.isValid());
|
||||
|
||||
// establish hook into notifications
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
class keepbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
keepbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
bool connected() override;
|
||||
@@ -62,7 +62,7 @@ class keepbike : public bike {
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -83,8 +83,6 @@ class kingsmithr1protreadmill : public treadmill {
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
bool requestUnlock = false;
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
double lastTargetSpeed = -1;
|
||||
bool targetSpeedMatchesSpeed = false;
|
||||
double lastTargetSpeedMatchesSpeed = -1;
|
||||
|
||||
@@ -27,6 +27,12 @@ kingsmithr2treadmill::kingsmithr2treadmill(uint32_t pollDeviceTime, bool noConso
|
||||
if (forceInitInclination > 0) {
|
||||
lastInclination = forceInitInclination;
|
||||
}
|
||||
if (lastControlMode != UNKNOWN_CONTROL_MODE) {
|
||||
lastControlMode = UNKNOWN_CONTROL_MODE;
|
||||
}
|
||||
if (lastRunState != UNKNOWN_RUN_STATE) {
|
||||
lastRunState = UNKNOWN_RUN_STATE;
|
||||
}
|
||||
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
@@ -205,16 +211,25 @@ void kingsmithr2treadmill::update() {
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
if (lastControlMode != MANUAL) {
|
||||
writeCharacteristic(QStringLiteral("props ControlMode 1"), QStringLiteral("turn on treadmill to manual mode"),
|
||||
false, true);
|
||||
}
|
||||
if (lastRunState != START) {
|
||||
writeCharacteristic(QStringLiteral("props runState 1"), QStringLiteral("starting"), false, true);
|
||||
}
|
||||
if (lastSpeed == 0.0) {
|
||||
|
||||
lastSpeed = 0.5;
|
||||
}
|
||||
// btinit(true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
if (lastRunState != STOP) {
|
||||
writeCharacteristic(QStringLiteral("props runState 0"), QStringLiteral("stopping"), false, true);
|
||||
}
|
||||
// don't go to standby mode automatically
|
||||
requestStop = -1;
|
||||
}
|
||||
if (requestFanSpeed != -1) {
|
||||
@@ -297,6 +312,11 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
QStringList _props = data.split(QStringLiteral(" "), QString::SkipEmptyParts);
|
||||
for (int i = 1; i < _props.size(); i += 2) {
|
||||
QString key = _props.at(i);
|
||||
// Error key only can have error code
|
||||
// props Error "ErrorCode" -5000
|
||||
if (!key.compare(QStringLiteral("Error"))) {
|
||||
break;
|
||||
}
|
||||
// skip string params
|
||||
if (!key.compare(QStringLiteral("mcu_version")) || !key.compare(QStringLiteral("goal"))) {
|
||||
continue;
|
||||
@@ -312,6 +332,8 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
|
||||
double speed = props.value("CurrentSpeed", 0);
|
||||
Cadence = props.value("spm", 0);
|
||||
KINGSMITH_R2_CONTROL_MODE controlMode = (KINGSMITH_R2_CONTROL_MODE)(int)props.value("ControlMode", (double)UNKNOWN_CONTROL_MODE);
|
||||
KINGSMITH_R2_RUN_STATE runState = (KINGSMITH_R2_RUN_STATE)(int)props.value("runState", (double)UNKNOWN_RUN_STATE);
|
||||
|
||||
// TODO:
|
||||
// - RunningDistance (int; meter) : update each 10miters / 0.01 mile
|
||||
@@ -320,6 +342,9 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
// - RunningTotalTime (int; sec)
|
||||
// - spm (int) : steps per minute
|
||||
|
||||
// TODO: check 'ControlMode' and 'runState' of treadmill side
|
||||
// then update current running status of application.
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
@@ -376,6 +401,16 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
// lastInclination = incline;
|
||||
}
|
||||
|
||||
if (lastControlMode != controlMode) {
|
||||
lastControlMode = controlMode;
|
||||
if (controlMode != UNKNOWN_CONTROL_MODE) {
|
||||
emit debug(QStringLiteral("kingsmith r2 is ready"));
|
||||
initDone = true;
|
||||
}
|
||||
}
|
||||
if (lastRunState != runState) {
|
||||
lastRunState = runState;
|
||||
}
|
||||
firstCharacteristicChanged = false;
|
||||
}
|
||||
|
||||
@@ -403,7 +438,6 @@ void kingsmithr2treadmill::btinit(bool startTape) {
|
||||
// QStringLiteral("servers getProp 1 3 7 8 9 16 17 18 19 21 22 23 24 31"), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic(QStringLiteral("servers getProp 1 2 7 12 23 24 31"), QStringLiteral("init"), false, true);
|
||||
|
||||
// TODO need reset BurnCalories & RunningDistance
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -415,7 +449,7 @@ void kingsmithr2treadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
if (KS_NACH_X21C) {
|
||||
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED7-0000-1000-8000-00805f9b34fb"));
|
||||
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED8-0000-1000-8000-00805f9b34fb"));
|
||||
} else if (KS_NGCH_G1C || KS_NACH_MXG) {
|
||||
} else if (KS_NGCH_G1C || KS_NACH_MXG || KS_NACH_X21C_2) {
|
||||
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED7-0000-1000-8000-00805f9b34fb"));
|
||||
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0001FED8-0000-1000-8000-00805f9b34fb"));
|
||||
}
|
||||
@@ -475,6 +509,13 @@ void kingsmithr2treadmill::serviceScanDone(void) {
|
||||
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
if(gattCommunicationChannelService == nullptr && KS_NACH_X21C) {
|
||||
KS_NACH_X21C_2 = true;
|
||||
KS_NACH_X21C = false;
|
||||
qDebug() << "KS_NACH_X21C default service id not found";
|
||||
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
}
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
&kingsmithr2treadmill::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
|
||||
@@ -82,6 +82,11 @@ class kingsmithr2treadmill : public treadmill {
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
enum KINGSMITH_R2_CONTROL_MODE { AUTOMODE = 0, MANUAL, STANDBY, UNKNOWN_CONTROL_MODE };
|
||||
enum KINGSMITH_R2_RUN_STATE { STOP = 0, START, UNKNOWN_RUN_STATE };
|
||||
KINGSMITH_R2_CONTROL_MODE lastControlMode = UNKNOWN_CONTROL_MODE;
|
||||
KINGSMITH_R2_RUN_STATE lastRunState = UNKNOWN_RUN_STATE;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
@@ -92,6 +97,7 @@ class kingsmithr2treadmill : public treadmill {
|
||||
bool initRequest = false;
|
||||
|
||||
bool KS_NACH_X21C = false;
|
||||
bool KS_NACH_X21C_2 = false;
|
||||
bool KS_NGCH_G1C = false;
|
||||
bool KS_NACH_MXG = false;
|
||||
|
||||
|
||||
@@ -77,8 +77,6 @@ class lifefitnesstreadmill : public treadmill {
|
||||
uint8_t firstStateChanged = 0;
|
||||
double lastSpeed = 0.0;
|
||||
double lastInclination = 0;
|
||||
int64_t lastStart = 0;
|
||||
int64_t lastStop = 0;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
class mcfbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
mcfbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
@@ -60,7 +60,7 @@ class mcfbike : public bike {
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
class mepanelbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
mepanelbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
@@ -55,7 +55,7 @@ class mepanelbike : public bike {
|
||||
QLowEnergyCharacteristic gattWriteCharacteristic;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
uint8_t counterPoll = 1;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
nautilusbike::nautilusbike(bool noWriteResistance, bool noHeartService, bool testResistance,
|
||||
uint8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
@@ -32,7 +32,7 @@ class nautilusbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
nautilusbike(bool noWriteResistance = false, bool noHeartService = false, bool testResistance = false,
|
||||
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
|
||||
~nautilusbike();
|
||||
bool connected() override;
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
@@ -64,7 +64,7 @@ class nautilusbike : public bike {
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool testResistance = false;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
int8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
bool searchStopped = false;
|
||||
uint8_t sec1Update = 0;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user