mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
361 Commits
opencv_and
...
cagnulein-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53b245cba0 | ||
|
|
2c71206327 | ||
|
|
e3be4ffa0e | ||
|
|
868d5cc3a8 | ||
|
|
94eb866740 | ||
|
|
f192cf6c75 | ||
|
|
c7efde82b6 | ||
|
|
f32dcb4e1e | ||
|
|
6157bfdcc8 | ||
|
|
9ff91faf00 | ||
|
|
3b88bc2f1f | ||
|
|
4f7cfbbfc1 | ||
|
|
56c112ed4c | ||
|
|
f6b8984575 | ||
|
|
52579e3efc | ||
|
|
fef5d8f4a7 | ||
|
|
6c93d85932 | ||
|
|
b7763154a3 | ||
|
|
fe393bd70d | ||
|
|
3330cc516e | ||
|
|
49e288ec01 | ||
|
|
f1e14d1652 | ||
|
|
2b541c220b | ||
|
|
372d57368c | ||
|
|
21f977ad44 | ||
|
|
a152a0edb9 | ||
|
|
effb3cdfe5 | ||
|
|
e0144445a3 | ||
|
|
8042989552 | ||
|
|
d5ba21da2e | ||
|
|
9d970fbca7 | ||
|
|
600d679e21 | ||
|
|
e860224908 | ||
|
|
081d778405 | ||
|
|
5f18b4daf1 | ||
|
|
7b0065d49a | ||
|
|
65c45d5f3a | ||
|
|
12d3f469de | ||
|
|
707fe68301 | ||
|
|
69511f8c38 | ||
|
|
37ea50fca9 | ||
|
|
c91a24cf23 | ||
|
|
ea48d30f73 | ||
|
|
117c3749ea | ||
|
|
6376e45a13 | ||
|
|
e1068bc1e5 | ||
|
|
c501466372 | ||
|
|
d8dea4f69d | ||
|
|
d44fcd72f7 | ||
|
|
2a19918889 | ||
|
|
420e263a9c | ||
|
|
8a71567a46 | ||
|
|
5a5d9fb4b4 | ||
|
|
f5484768b8 | ||
|
|
5beb890d85 | ||
|
|
edf8a33b87 | ||
|
|
03ddac15da | ||
|
|
e41c60fbeb | ||
|
|
2a435b6b65 | ||
|
|
da31761c35 | ||
|
|
6908bb5d43 | ||
|
|
cf35fccca7 | ||
|
|
d4a8322160 | ||
|
|
dc5f835445 | ||
|
|
85c7a70053 | ||
|
|
b615bd008e | ||
|
|
2ea7337a0f | ||
|
|
34f34312ce | ||
|
|
d4818cc96e | ||
|
|
d801bc7984 | ||
|
|
0efe227318 | ||
|
|
40549f16e8 | ||
|
|
7295ebbf62 | ||
|
|
95db80a249 | ||
|
|
534de944d1 | ||
|
|
d4c9f2b1dd | ||
|
|
ffb2a6e072 | ||
|
|
2dc7ad7fd9 | ||
|
|
35d2eb23aa | ||
|
|
4ab4e17f03 | ||
|
|
acd80b1105 | ||
|
|
01ab6841fc | ||
|
|
e547924350 | ||
|
|
30b0bd51ca | ||
|
|
a3a2bca715 | ||
|
|
bf14b9a8e0 | ||
|
|
a53e7a6e44 | ||
|
|
5eefb07e9e | ||
|
|
55b0689d1d | ||
|
|
beb0a8a80c | ||
|
|
65f57d111a | ||
|
|
7de08d223f | ||
|
|
2b69f2dcd0 | ||
|
|
27da18fed9 | ||
|
|
22fd82cdff | ||
|
|
d9202218dd | ||
|
|
baf9e24640 | ||
|
|
9c0ab9b9f6 | ||
|
|
49e0506815 | ||
|
|
80c528f370 | ||
|
|
54fa926596 | ||
|
|
9f80a7b679 | ||
|
|
7639d19f5e | ||
|
|
9870626695 | ||
|
|
5be30f8f6c | ||
|
|
751f9c4f84 | ||
|
|
dd61b8e2ca | ||
|
|
7dfc994888 | ||
|
|
c662b84bc2 | ||
|
|
7b0ec35303 | ||
|
|
5d235eaab7 | ||
|
|
4e55e2b085 | ||
|
|
e221e4bf7e | ||
|
|
67f0be7c06 | ||
|
|
415025e43d | ||
|
|
ba1854659a | ||
|
|
f622a3e3f7 | ||
|
|
aad888cdab | ||
|
|
b3d45319ae | ||
|
|
0ecff51b14 | ||
|
|
face819d09 | ||
|
|
f8b9c65dab | ||
|
|
0b0081a506 | ||
|
|
94c63f5b77 | ||
|
|
87725cdd1d | ||
|
|
0ddeb747bf | ||
|
|
63a7476d14 | ||
|
|
28701625ac | ||
|
|
e0bc9071c5 | ||
|
|
5bd16f8200 | ||
|
|
d2b844ffb9 | ||
|
|
4018d2947c | ||
|
|
f030c319ac | ||
|
|
ccd4a9896e | ||
|
|
246bf944b2 | ||
|
|
0b7ded9020 | ||
|
|
8bf64c64be | ||
|
|
dbd0227722 | ||
|
|
7e04ab4f92 | ||
|
|
9b7e1d873c | ||
|
|
60ee86476c | ||
|
|
430378b441 | ||
|
|
b9537ff042 | ||
|
|
c0bba3943b | ||
|
|
f729260cbd | ||
|
|
bff00001ab | ||
|
|
91e5a1f14e | ||
|
|
b928be6a33 | ||
|
|
2e0dde6aa4 | ||
|
|
7b8c8ea2b8 | ||
|
|
afd6e5ce26 | ||
|
|
2b8b005762 | ||
|
|
1e639cacd1 | ||
|
|
16611c7e49 | ||
|
|
757febf35a | ||
|
|
2c46293612 | ||
|
|
9a1f9ee980 | ||
|
|
96c80d5bb6 | ||
|
|
98fbd64b89 | ||
|
|
23131f8fb8 | ||
|
|
ec966d6036 | ||
|
|
11f0d6786b | ||
|
|
0f2619233c | ||
|
|
a2ba4cefb5 | ||
|
|
a83e5951aa | ||
|
|
9fe09e7b4b | ||
|
|
0ea2269a67 | ||
|
|
fdba749812 | ||
|
|
ac257b13ed | ||
|
|
0890bf8096 | ||
|
|
ea75a04ad5 | ||
|
|
c3a4238618 | ||
|
|
8017bf64cd | ||
|
|
04633c6296 | ||
|
|
513b59c106 | ||
|
|
04c3cc329c | ||
|
|
e99d7279f4 | ||
|
|
716f917e2a | ||
|
|
0147498c69 | ||
|
|
22b3cef9d2 | ||
|
|
81e4e95272 | ||
|
|
12cc5d0221 | ||
|
|
5b1f85a8cb | ||
|
|
9a9d5fa95e | ||
|
|
8f335894e7 | ||
|
|
3292af09ec | ||
|
|
747acae290 | ||
|
|
04c13f2842 | ||
|
|
b77ed2af6a | ||
|
|
5f186204d6 | ||
|
|
6e9a1ef58a | ||
|
|
180a246902 | ||
|
|
09ab5b142c | ||
|
|
50249a5236 | ||
|
|
4da2ac2fe1 | ||
|
|
ba9253209c | ||
|
|
e8031f5a19 | ||
|
|
b649e5e0f5 | ||
|
|
da9c570029 | ||
|
|
f79eae87a9 | ||
|
|
44beb23212 | ||
|
|
ed35ece439 | ||
|
|
37b513904c | ||
|
|
639397d594 | ||
|
|
de51899ca3 | ||
|
|
ff40d68223 | ||
|
|
1385ea8b88 | ||
|
|
13c88474c6 | ||
|
|
e55577d835 | ||
|
|
6409eab90c | ||
|
|
a314dfe325 | ||
|
|
9b41e2494d | ||
|
|
8672240d72 | ||
|
|
8e83967036 | ||
|
|
8d8b1c141a | ||
|
|
64d40b5d79 | ||
|
|
91811b6b0e | ||
|
|
3e4c898d9f | ||
|
|
67398fce4b | ||
|
|
42c2c52eef | ||
|
|
94cfede7ac | ||
|
|
e8e6068aa2 | ||
|
|
056a903cf2 | ||
|
|
911e66041f | ||
|
|
dbc3c2abf2 | ||
|
|
6f37799e56 | ||
|
|
00bfd0707c | ||
|
|
6bc003db9b | ||
|
|
7b1044a634 | ||
|
|
3686b77405 | ||
|
|
ba3bb3b724 | ||
|
|
495168c204 | ||
|
|
8a9230942b | ||
|
|
100eaccc99 | ||
|
|
24c0df5075 | ||
|
|
9116a46b27 | ||
|
|
64059f2db5 | ||
|
|
2e386ef2a1 | ||
|
|
1ecee4cd3e | ||
|
|
fd523000c0 | ||
|
|
47c272d3d0 | ||
|
|
4eec504016 | ||
|
|
da08e3335e | ||
|
|
0100da67cf | ||
|
|
12e03510f6 | ||
|
|
c8a17ad70e | ||
|
|
9748558f1b | ||
|
|
c807291389 | ||
|
|
29c4e323b0 | ||
|
|
4b1408401f | ||
|
|
39e2ef50cb | ||
|
|
880ac67fbc | ||
|
|
b21695bc14 | ||
|
|
eab058f401 | ||
|
|
58f62303be | ||
|
|
22f935c827 | ||
|
|
dce2ba7067 | ||
|
|
51665c76b4 | ||
|
|
a76cf602d6 | ||
|
|
a3f6bb8d0c | ||
|
|
3f83e4a999 | ||
|
|
14546d4ed3 | ||
|
|
1afff6a79d | ||
|
|
e95bf77c9d | ||
|
|
76ebceccbc | ||
|
|
f54b900c18 | ||
|
|
42e7749856 | ||
|
|
13fb8ae05a | ||
|
|
ee27386d11 | ||
|
|
5073ed766f | ||
|
|
c571a98775 | ||
|
|
1e66fb10b9 | ||
|
|
c9bcfb201e | ||
|
|
d701d01f13 | ||
|
|
f3ae244587 | ||
|
|
755e98d49a | ||
|
|
006c365fee | ||
|
|
2d9110c251 | ||
|
|
30e945f609 | ||
|
|
d3289fa5d1 | ||
|
|
1dd8846c2e | ||
|
|
9a3dc093a2 | ||
|
|
5674c75f4f | ||
|
|
f97e11b1d2 | ||
|
|
68a5bf7f44 | ||
|
|
d644aa4523 | ||
|
|
d04a1a9f01 | ||
|
|
e907170ba2 | ||
|
|
8a55ac1088 | ||
|
|
f83c7565cc | ||
|
|
7de500503e | ||
|
|
61b15254e1 | ||
|
|
2503835a59 | ||
|
|
00ceee2ee0 | ||
|
|
c93cf781fb | ||
|
|
a17da417c2 | ||
|
|
306ce699d4 | ||
|
|
fbbd40a0f8 | ||
|
|
5398b09643 | ||
|
|
94046b18fe | ||
|
|
4e9a03d931 | ||
|
|
2392251992 | ||
|
|
973ff3a586 | ||
|
|
87e5a1905d | ||
|
|
f39ca765d8 | ||
|
|
b33ce3233d | ||
|
|
4209f6f653 | ||
|
|
7d36b4082b | ||
|
|
a3015858f9 | ||
|
|
12515752f6 | ||
|
|
9334c6b472 | ||
|
|
884bea8352 | ||
|
|
8b4c579539 | ||
|
|
c9f3a3a092 | ||
|
|
547bc8a5c9 | ||
|
|
9c600dbc00 | ||
|
|
f29d9fd1e6 | ||
|
|
8d1e98e81f | ||
|
|
94ac9d1bdd | ||
|
|
9e74918b65 | ||
|
|
3be43c14e1 | ||
|
|
e1b951e664 | ||
|
|
53126b16d3 | ||
|
|
0fb8e12d31 | ||
|
|
679300c930 | ||
|
|
4008d588d7 | ||
|
|
fda528babc | ||
|
|
0e9bac6168 | ||
|
|
f8c1330862 | ||
|
|
8e81df67d0 | ||
|
|
1a3c13eac9 | ||
|
|
15b16c7e5c | ||
|
|
b5ceb0a0f1 | ||
|
|
00939c56dd | ||
|
|
5f1ebc439e | ||
|
|
81db615692 | ||
|
|
65d12f00f4 | ||
|
|
adcb87b3c4 | ||
|
|
90900b786a | ||
|
|
b495e833bd | ||
|
|
fb01341dd1 | ||
|
|
867143e43e | ||
|
|
b85d7d72f5 | ||
|
|
32cb3b9e37 | ||
|
|
3ff8938b41 | ||
|
|
5156cb38f0 | ||
|
|
dd4a5dbc54 | ||
|
|
cf37b8705e | ||
|
|
bc8302c761 | ||
|
|
76ecf5c66e | ||
|
|
3693c2d1b2 | ||
|
|
3905bd8ba7 | ||
|
|
92cac7485e | ||
|
|
458c44758e | ||
|
|
be12859343 | ||
|
|
d201919b55 | ||
|
|
138a42c2e6 | ||
|
|
bbe69f3f60 | ||
|
|
b95b3a5018 | ||
|
|
fb0cbb74a5 | ||
|
|
deed6019ab |
546
.github/workflows/main.yml
vendored
546
.github/workflows/main.yml
vendored
@@ -14,8 +14,8 @@ on:
|
||||
branches: [ master, github-workflow-playground ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: "0 */12 * * *"
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
@@ -70,15 +70,20 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.10.11
|
||||
python-version: 3.7
|
||||
- name: download python and paddleocr
|
||||
run: |
|
||||
python -VV
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade setuptools
|
||||
python -m pip install "protobuf<=3.20.2,>=3.1.0"
|
||||
python -m pip install paddlepaddle==2.5.1
|
||||
python -m pip install paddleocr
|
||||
python -m pip install imutils
|
||||
python -m pip install "Pillow<10.0.0"
|
||||
python -m pip install opencv-python
|
||||
python -m pip install numpy
|
||||
python -m pip install pywin32
|
||||
python -m pip install paddlepaddle-gpu==2.4.2.post117 -f https://www.paddlepaddle.org.cn/whl/windows/mkl/avx/stable.html
|
||||
python -m pip install https://files.pythonhosted.org/packages/03/ac/13fbe0ebf110d57a89f055a292d4fe430fee3fb22c56f8c077e63e0c5a4e/paddlepaddle-2.4.2-cp310-cp310-win_amd64.whl
|
||||
python -m pip install paddleocr>=2.0.1
|
||||
if: matrix.config.python
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
@@ -117,15 +122,20 @@ jobs:
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
- name: Secrets
|
||||
if: github.ref == 'refs/heads/main'
|
||||
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
|
||||
cd ..
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
qmake
|
||||
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
|
||||
cd ..
|
||||
make -j8
|
||||
cd src/debug
|
||||
mkdir output
|
||||
@@ -133,15 +143,17 @@ jobs:
|
||||
cp qdomyos-zwift.exe output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll" .
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../windows/*.py .
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
mkdir adb
|
||||
mkdir python
|
||||
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.10.11\x64 -Destination python -Recurse
|
||||
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
cd appx
|
||||
@@ -151,12 +163,6 @@ jobs:
|
||||
- name: Build without python
|
||||
run: |
|
||||
qmake
|
||||
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
|
||||
cd ..
|
||||
make -j8
|
||||
cd src/debug
|
||||
mkdir output
|
||||
@@ -164,11 +170,12 @@ jobs:
|
||||
cp qdomyos-zwift.exe output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/ProgramData/Chocolatey/lib/mingw/tools/install/mingw64/bin/libstdc++-6.dll" .
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
@@ -176,23 +183,79 @@ jobs:
|
||||
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
|
||||
if: matrix.config.python == false
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/windows/5.15.2/binary/mingw64/*.* ${{ github.workspace }}/src/debug/output/
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output release.zip
|
||||
run: Compress-Archive src/debug/output windows-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output windows-binary-no-python.zip
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-binary
|
||||
path: release.zip
|
||||
path: windows-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-binary-no-python
|
||||
path: release.zip
|
||||
path: windows-binary-no-python.zip
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
# - name: Exit if not on master branch
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
# run: exit 1
|
||||
|
||||
# - uses: actions/checkout@v3
|
||||
# with:
|
||||
# fetch-depth: 0 # Required due to the way Git works, without it this action won't be able to find any or the correct tags
|
||||
|
||||
# - name: Get previous tag
|
||||
# id: previoustag
|
||||
# uses: 'WyriHaximus/github-action-get-previous-tag@v1'
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# - name: Create Release
|
||||
# if: ${{ ! matrix.config.python }}
|
||||
# id: create_release
|
||||
# uses: actions/create-release@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# tag_name: ${{ steps.previoustag.outputs.tag }}
|
||||
# release_name: Release ${{ steps.previoustag.outputs.tag }}
|
||||
# draft: false
|
||||
# prerelease: false
|
||||
|
||||
# - name: upload windows artifact
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# if: ${{ ! matrix.config.python }}
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ github.token }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: release.zip
|
||||
# asset_name: windows-binary-no-python.zip
|
||||
# asset_content_type: application/zip
|
||||
|
||||
# - name: upload windows artifact
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# if: ${{ matrix.config.python }}
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ github.token }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: release.zip
|
||||
# asset_name: windows-binary.zip
|
||||
# asset_content_type: application/zip
|
||||
|
||||
# window-steam-build:
|
||||
# runs-on: windows-latest
|
||||
#
|
||||
@@ -243,6 +306,7 @@ jobs:
|
||||
# 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 STEAM_STORE" >> secret.h
|
||||
# cd ..
|
||||
# make -j8
|
||||
@@ -277,6 +341,19 @@ jobs:
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- name: release
|
||||
uses: actions/create-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
id: create_release
|
||||
with:
|
||||
draft: false
|
||||
prerelease: false
|
||||
release_name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref }}
|
||||
body_path: CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
# - name: Cache Qt Linux Desktop
|
||||
# id: cache-qt-linux-desktop
|
||||
# uses: actions/cache@v1
|
||||
@@ -453,30 +530,12 @@ jobs:
|
||||
sudo apt-get install -y xvfb
|
||||
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
|
||||
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: bluetiger9/SmtpClient-for-Qt
|
||||
path: "src/smtpclient/"
|
||||
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: cagnulein/qmdnsengine
|
||||
path: "src/qmdnsengine/"
|
||||
ref: "zwift"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: google/googletest
|
||||
path: "tst/googletest/"
|
||||
ref: "release-1.12.1"
|
||||
# This token is provided by Actions, you do not need to create your own token
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
submodules: recursive # or 'true' if you want to check out only immediate submodules
|
||||
|
||||
- name: Install packages required to run QZ inside workflow
|
||||
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
|
||||
@@ -513,7 +572,7 @@ jobs:
|
||||
- name: Install Qt Android
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
version: '5.15.0'
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android'
|
||||
@@ -527,6 +586,20 @@ jobs:
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '11'
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
|
||||
|
||||
- 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: |
|
||||
@@ -538,17 +611,49 @@ jobs:
|
||||
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
|
||||
export ANDROID_NDK="${ANDROID_SDK_ROOT}/ndk-bundle"
|
||||
export ANDROID_NDK_ROOT="${ANDROID_NDK}"
|
||||
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 ..
|
||||
|
||||
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
|
||||
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
|
||||
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
|
||||
|
||||
- name: Build APK (not usable for production due to unpatched QT library)
|
||||
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
|
||||
with:
|
||||
name: fdroid-android-trial
|
||||
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
|
||||
|
||||
# - name: Exit if not on master branch
|
||||
# if: github.ref == 'refs/heads/main'
|
||||
# run: exit 1
|
||||
|
||||
# - name: upload windows artifact
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ github.token }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk
|
||||
# asset_name: fdroid-android-trial.zip
|
||||
# asset_content_type: application/zip
|
||||
|
||||
ios-build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
@@ -600,5 +705,342 @@ jobs:
|
||||
run: cp qt-patches/ios/5.15.2/binary/*.* ${{ github.workspace }}/output/ios/Qt/5.15.2/ios/lib/
|
||||
|
||||
- name: Build
|
||||
run: qmake CONFIG+=debug && make -j4
|
||||
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
|
||||
cd ..
|
||||
qmake CONFIG+=debug && make -j4
|
||||
|
||||
# causes iOS build on Mac to fail
|
||||
# - name: Commit moc files
|
||||
# uses: EndBug/add-and-commit@v9
|
||||
# with:
|
||||
# message: 'moc files added'
|
||||
# add: 'src/moc_*.cpp --force'
|
||||
# if: github.ref == 'refs/heads/master'
|
||||
|
||||
window-msvc2019-build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- {python: true}
|
||||
- {python: false}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: bluetiger9/SmtpClient-for-Qt
|
||||
path: "src/smtpclient/"
|
||||
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: cagnulein/qmdnsengine
|
||||
path: "src/qmdnsengine/"
|
||||
ref: "zwift"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: google/googletest
|
||||
path: "tst/googletest/"
|
||||
ref: "release-1.12.1"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout qHttpServer
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: qt-labs/qthttpserver
|
||||
path: "src/qthttpserver"
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.7
|
||||
- name: download python and paddleocr
|
||||
run: |
|
||||
python -VV
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade setuptools
|
||||
python -m pip install "protobuf<=3.20.2,>=3.1.0"
|
||||
python -m pip install paddlepaddle==2.5.1
|
||||
python -m pip install paddleocr
|
||||
python -m pip install imutils
|
||||
python -m pip install "Pillow<10.0.0"
|
||||
python -m pip install opencv-python
|
||||
python -m pip install numpy
|
||||
python -m pip install pywin32
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
host: 'windows'
|
||||
modules: 'qtnetworkauth qtcharts'
|
||||
target: "desktop"
|
||||
arch: win64_msvc2019_64
|
||||
dir: "${{github.workspace}}/qt/"
|
||||
install-deps: "true"
|
||||
cache: 'true'
|
||||
cache-key-prefix: 'install-qt-action-windows'
|
||||
|
||||
- name: Install MSVC compiler
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
# 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo
|
||||
toolset: 14.2
|
||||
arch: x64
|
||||
|
||||
- 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
|
||||
nmake
|
||||
nmake install
|
||||
cd ../..
|
||||
|
||||
- name: Secrets
|
||||
if: github.ref == 'refs/heads/main'
|
||||
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
|
||||
cd ..
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../windows/*.py .
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
mkdir adb
|
||||
mkdir python
|
||||
Copy-Item -Path C:\hostedtoolcache\windows\Python\3.7.9\x64 -Destination python -Recurse
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
cd appx
|
||||
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Build without python
|
||||
run: |
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp "C:/mingw64/bin/libwinpthread-1.dll" .
|
||||
cp "C:/mingw64/bin/libgcc_s_seh-1.dll" .
|
||||
cp "C:/mingw64/bin/libstdc++-6.dll" .
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
cd appx
|
||||
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
|
||||
if: matrix.config.python == false
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/windows/5.15.2/binary/msvc2019/*.* ${{ github.workspace }}/src/debug/output/
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output windows-msvc2019-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output windows-msvc2019-binary-no-python.zip
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-msvc2019-binary
|
||||
path: windows-msvc2019-binary.zip
|
||||
if: matrix.config.python
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-msvc2019-binary-no-python
|
||||
path: windows-msvc2019-binary-no-python.zip
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
window-msvc2019-aiserver-build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: bluetiger9/SmtpClient-for-Qt
|
||||
path: "src/smtpclient/"
|
||||
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: cagnulein/qmdnsengine
|
||||
path: "src/qmdnsengine/"
|
||||
ref: "zwift"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout submodule repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: google/googletest
|
||||
path: "tst/googletest/"
|
||||
ref: "release-1.12.1"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout qHttpServer
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: qt-labs/qthttpserver
|
||||
path: "src/qthttpserver"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
host: 'windows'
|
||||
modules: 'qtnetworkauth qtcharts'
|
||||
target: "desktop"
|
||||
arch: win64_msvc2019_64
|
||||
dir: "${{github.workspace}}/qt/"
|
||||
install-deps: "true"
|
||||
cache: 'true'
|
||||
cache-key-prefix: 'install-qt-action-windows'
|
||||
|
||||
- name: Install MSVC compiler
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
# 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo
|
||||
toolset: 14.2
|
||||
arch: x64
|
||||
|
||||
- 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
|
||||
nmake
|
||||
nmake install
|
||||
cd ../..
|
||||
|
||||
- name: Secrets
|
||||
if: github.ref == 'refs/heads/main'
|
||||
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
|
||||
cd ..
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cd src
|
||||
echo "#define AISERVER" >> aiserver.h
|
||||
cd ..
|
||||
qmake
|
||||
nmake
|
||||
cd src/debug
|
||||
mkdir output
|
||||
mkdir appx
|
||||
cp qdomyos-zwift.exe output/
|
||||
cd output
|
||||
windeployqt --qmldir ../../ qdomyos-zwift.exe
|
||||
cp ../../../icons/iOS/iTunesArtwork@2x.png .
|
||||
cp ../../AppxManifest.xml .
|
||||
cp ../../windows/zwift-incline-ai-server.py zwift-incline.py
|
||||
cp ../../windows/zwift-incline-climb-portal-ai-server.py zwift-incline-climb-portal.py
|
||||
cp ../../windows/zwift-workout-ai-server.py zwift-workout.py
|
||||
cp ../../windows/*.bat .
|
||||
cp ../../../windows_openssl/*.* .
|
||||
mkdir adb
|
||||
cp ../../adb/* adb/
|
||||
cd ..
|
||||
cd appx
|
||||
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/windows/5.15.2/binary/msvc2019/*.* ${{ github.workspace }}/src/debug/output/
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
- name: Archive windows binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: windows-msvc2019-ai-server-binary
|
||||
path: windows-msvc2019-ai-server-binary.zip
|
||||
|
||||
upload_to_release:
|
||||
permissions: write-all
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'schedule'
|
||||
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
|
||||
- name: Update nightly release
|
||||
uses: andelf/nightly-release@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: nightly
|
||||
prerelease: false
|
||||
name: 'QZ nightly build $$'
|
||||
body: |
|
||||
This is a nightly build of QZ.
|
||||
|
||||
You can use this if you want to try new features without waiting for releases.
|
||||
From time to time, in development builds, old difficult-to-reproduce bugs are
|
||||
fixed, but it is also true that in the development process with the introduction
|
||||
of new complex code, the stability of the program may suffer compared to
|
||||
official releases, so **use it with caution**!
|
||||
|
||||
__Please help us improve QZ by reporting any issues you encounter!__ :wink:
|
||||
files: |
|
||||
windows-msvc2019-binary-no-python/*
|
||||
windows-msvc2019-binary/*
|
||||
windows-msvc2019-ai-server-binary/*
|
||||
windows-binary-no-python/*
|
||||
windows-binary/*
|
||||
fdroid-android-trial/*
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -49,3 +49,4 @@ src/inner_templates/googlemaps/cesium-key.js
|
||||
*.autosave
|
||||
.vscode/settings.json
|
||||
/tst/Devices/.vs
|
||||
src/inner_templates/googlemaps/cesium-key.js
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -13,3 +13,6 @@
|
||||
path = tst/googletest
|
||||
url = https://github.com/google/googletest.git
|
||||
branch = tags/release-1.12.1
|
||||
[submodule "src/qthttpserver"]
|
||||
path = src/qthttpserver
|
||||
url = https://github.com/qt-labs/qthttpserver
|
||||
|
||||
112
README.md
112
README.md
@@ -7,35 +7,99 @@ Zwift bridge for Treadmills and Bike!
|
||||
[<img src="docs/img/app_store.png">](https://apps.apple.com/app/id1543684531?fbclid=IwAR10H6y3mEgwkTlGJON3e8voYOh2wt3kLFOpFzoIXaYZ_N0y0pDvKxHMUaM)
|
||||
<a href="https://www.buymeacoffee.com/cagnulein" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
|
||||

|
||||
|
||||
[](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
|
||||
|
||||

|
||||

|
||||
|
||||
UI on Linux
|
||||
|
||||

|
||||
|
||||
UI on MacOS
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot1.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot2.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot3.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot4.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
<td>
|
||||
<img src="icons/AppScreen/iOS%20Phones%20-%206.5_/screenshot5.jpeg" style="height: 400px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### Features
|
||||
|
||||
1. Domyos compatible
|
||||
2. Toorx TRX Route Key compatible
|
||||
3. Echelon Connect Sport compatible
|
||||
4. Zwift compatible
|
||||
5. Create, load and save train programs
|
||||
6. Measure distance, elevation gain and watts
|
||||
7. Gpx import (with difficulty slider)
|
||||
8. Realtime Charts
|
||||
# UI Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Tiles Customization|X|X|X|X|Order and visibility of each tile|
|
||||
|Profiles|X|X|X|X|Different user or different fitness device profiles|
|
||||
|UI Zoom Customization|X|X|X|X||
|
||||
|
||||
# Peloton Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Bike metrics on the peloton app|X||X|||
|
||||
|Power zone with auto resistance|X|||||
|
||||
|Peloton real-time resistance conversion|X||X||with the possibility to customize it|
|
||||
|Peloton real-time auto-resistance|X||X||with the possibility to customize it|
|
||||
|Peloton auto speed and auto inclination||X|X||with the possibility to customize it|
|
||||
|
||||
# Heart Rate Features
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Heart Rate support|X|X|X|X|Apple Watch, ANT+ devices and Bluetooth devices|
|
||||
|Heart Rate Zones Customizations|X|X|X|X||
|
||||
|Ability to calculate Wattage from HR and Cadence|X||||for the bikes that doesn't have a power sensor|
|
||||
|
||||
# 3rd Apps Compatibility
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Zwift Compatibility|X|X|X|X||
|
||||
|Zwift Auto resistance|X||X|||
|
||||
|Zwift Auto inclination and speed||X|X||https://www.youtube.com/watch?v=KTQ2n7yeDbo|
|
||||
|Wahoo RGT Compatibility|X|X|X|X||
|
||||
|VzFit Compatibility|X|X|X|X||
|
||||
|Rouvy Compatibility|X|X|X|X||
|
||||
|IFIT app Compatibility|X|||||
|
||||
|Echelon app Compatibility|X|||||
|
||||
|Wahoo Dircon Compatibility|X|X|X|X|in order to send data to Zwift or RGT with Wifi only!|
|
||||
|One device only support for Zwift and Wahoo RGT|X|X|X|X|using Wahoo Dircon https://www.youtube.com/watch?v=gYYUXNWFAok|
|
||||
|
||||
# Training Program
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Builtin video support (Kinomap like)|X|X|X|X|Files could be local or on the cloud!|
|
||||
|GPX auto following|X|X|X|X||
|
||||
|2D/3D maps for GPX|X|X|X|X||
|
||||
|ZWO (Zwift workout file) compatibility|X|X|X|X||
|
||||
|XML Workout file compatibility|X|X|X|X||
|
||||
|Auto follow workout based on your heart rate|X|X|X|X||
|
||||
|Random workout|X|X|X|X||
|
||||
|
||||
|
||||
# Statistics
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|E-Mail report|X|X|X|X|at the end of the workout|
|
||||
|Strava integration|X|X|X|X|press stop at the end of the workout to auto upload it|
|
||||
|
||||
# Misc
|
||||
|
||||
|Feature|Bike|Treadmill|Elliptical|Rower|Notes|
|
||||
|:---|:---:|:---:|:---:|:---:|---:|
|
||||
|Resistance shifting with bluetooth remote|X||X|||
|
||||
|TTS support|X|X|X|X||
|
||||
|
||||

|
||||
|
||||
### Installation
|
||||
|
||||
You can install on multiple platforms.
|
||||
You can install it on multiple platforms.
|
||||
Read the [installation procedure](docs/10_Installation.md)
|
||||
|
||||
|
||||
@@ -45,7 +109,7 @@ You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IO
|
||||
|
||||
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
|
||||
|
||||
### No gui version
|
||||
### No GUI version
|
||||
|
||||
run as
|
||||
|
||||
@@ -57,7 +121,7 @@ https://github.com/ProH4Ck/treadmill-bridge
|
||||
|
||||
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
|
||||
|
||||
Icons used in this documentation comes from [flaticon.com](https://www.flaticon.com)
|
||||
Icons used in this documentation come from [flaticon.com](https://www.flaticon.com)
|
||||
|
||||
### Blog
|
||||
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "swiftDebug.h"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
@@ -170,6 +170,10 @@
|
||||
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
|
||||
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
|
||||
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */; };
|
||||
8727C7D02B3BF1B8005429EB /* proformtelnetbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */; };
|
||||
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 */; };
|
||||
872A20DA28C5EC380037774D /* faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20D928C5EC380037774D /* faketreadmill.cpp */; };
|
||||
872A20DC28C5F5CE0037774D /* moc_faketreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */; };
|
||||
872BAB4E261750EE006A59AB /* libQt5Charts.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 872BAB4D261750EE006A59AB /* libQt5Charts.a */; };
|
||||
@@ -254,11 +258,16 @@
|
||||
87433F2127D8B722003D1672 /* simplecrypt.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87433F2027D8B722003D1672 /* simplecrypt.cpp */; };
|
||||
87440FBD2640291700E4DC0B /* fitplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87440FBC2640291700E4DC0B /* fitplusbike.cpp */; };
|
||||
87440FBF2640292900E4DC0B /* moc_fitplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87440FBE2640292900E4DC0B /* moc_fitplusbike.cpp */; };
|
||||
8745B2762AFCB4A300991A39 /* android in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 8745B2752AFCB4A300991A39 /* android */; };
|
||||
8745B2782AFCB87B00991A39 /* libadb.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 8745B2742AFCB3B300991A39 /* libadb.a */; };
|
||||
87473A9627ECA9EE00C203F5 /* proformrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87473A9527ECA9EE00C203F5 /* proformrower.cpp */; };
|
||||
87473A9827ECAA0500C203F5 /* moc_proformrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87473A9727ECAA0500C203F5 /* moc_proformrower.cpp */; };
|
||||
874D272029AFA11F0007C079 /* apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D271E29AFA11F0007C079 /* apexbike.cpp */; };
|
||||
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 874D272129AFA13B0007C079 /* moc_apexbike.cpp */; };
|
||||
8752B4CD27F43D9200E2EC6C /* qz.storekit in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 8752B4CC27F43D9200E2EC6C /* qz.storekit */; };
|
||||
8752C0E32B15D84100C3D1A5 /* moc_eliteariafan.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */; };
|
||||
8752C0E82B15D85600C3D1A5 /* eliteariafan.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */; };
|
||||
8752C0E92B15D85600C3D1A5 /* ios_eliteariafan.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */; };
|
||||
87540FAD2848FD70005E0D44 /* libqtexttospeech_speechios.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */; };
|
||||
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
|
||||
87586A4125B8340E00A243C4 /* proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4025B8340E00A243C4 /* proformbike.cpp */; };
|
||||
@@ -362,6 +371,7 @@
|
||||
87A0D7542A3A4547005147F2 /* moc_fakerower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A0D7532A3A4547005147F2 /* moc_fakerower.cpp */; };
|
||||
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F052660D5C0002D7C96 /* ftmsrower.cpp */; };
|
||||
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */; };
|
||||
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A2E0212B2B053E00E6168F /* swiftDebug.mm */; };
|
||||
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC1F2656429400D302E3 /* rower.cpp */; };
|
||||
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
|
||||
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
|
||||
@@ -881,6 +891,13 @@
|
||||
8727A47527849EA600019B5D /* paferstreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = paferstreadmill.h; path = ../src/paferstreadmill.h; sourceTree = "<group>"; };
|
||||
8727A47627849EA600019B5D /* paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = paferstreadmill.cpp; path = ../src/paferstreadmill.cpp; sourceTree = "<group>"; };
|
||||
8727A47827849EB200019B5D /* moc_paferstreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_paferstreadmill.cpp; sourceTree = "<group>"; };
|
||||
8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformtelnetbike.cpp; path = ../src/proformtelnetbike.cpp; sourceTree = "<group>"; };
|
||||
8727C7CD2B3BF1B8005429EB /* proformtelnetbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformtelnetbike.h; path = ../src/proformtelnetbike.h; sourceTree = "<group>"; };
|
||||
8727C7CE2B3BF1B8005429EB /* QTelnet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QTelnet.h; path = ../src/QTelnet.h; sourceTree = "<group>"; };
|
||||
8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = QTelnet.cpp; path = ../src/QTelnet.cpp; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
872A20D828C5EC380037774D /* faketreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = faketreadmill.h; path = ../src/faketreadmill.h; sourceTree = "<group>"; };
|
||||
872A20D928C5EC380037774D /* faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = faketreadmill.cpp; path = ../src/faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
872A20DB28C5F5CE0037774D /* moc_faketreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_faketreadmill.cpp; sourceTree = "<group>"; };
|
||||
@@ -1006,6 +1023,9 @@
|
||||
87440FBB2640291700E4DC0B /* fitplusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fitplusbike.h; path = ../src/fitplusbike.h; sourceTree = "<group>"; };
|
||||
87440FBC2640291700E4DC0B /* fitplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fitplusbike.cpp; path = ../src/fitplusbike.cpp; sourceTree = "<group>"; };
|
||||
87440FBE2640292900E4DC0B /* moc_fitplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitplusbike.cpp; sourceTree = "<group>"; };
|
||||
8745B2742AFCB3B300991A39 /* libadb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libadb.a; path = ../src/ios/libadb.a; sourceTree = "<group>"; };
|
||||
8745B2752AFCB4A300991A39 /* android */ = {isa = PBXFileReference; lastKnownFileType = folder; name = android; path = ../src/ios/android; sourceTree = "<group>"; };
|
||||
8745B2772AFCB52800991A39 /* AdbClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AdbClient.h; path = ../src/ios/AdbClient.h; sourceTree = "<group>"; };
|
||||
87473A9427ECA9EE00C203F5 /* proformrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformrower.h; path = ../src/proformrower.h; sourceTree = "<group>"; };
|
||||
87473A9527ECA9EE00C203F5 /* proformrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformrower.cpp; path = ../src/proformrower.cpp; sourceTree = "<group>"; };
|
||||
87473A9727ECAA0500C203F5 /* moc_proformrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformrower.cpp; sourceTree = "<group>"; };
|
||||
@@ -1013,6 +1033,11 @@
|
||||
874D271F29AFA11F0007C079 /* apexbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = apexbike.h; path = ../src/apexbike.h; sourceTree = "<group>"; };
|
||||
874D272129AFA13B0007C079 /* moc_apexbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_apexbike.cpp; sourceTree = "<group>"; };
|
||||
8752B4CC27F43D9200E2EC6C /* qz.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = qz.storekit; sourceTree = "<group>"; };
|
||||
8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_eliteariafan.cpp; sourceTree = "<group>"; };
|
||||
8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = eliteariafan.cpp; path = ../src/eliteariafan.cpp; sourceTree = "<group>"; };
|
||||
8752C0E52B15D85600C3D1A5 /* eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = eliteariafan.h; path = ../src/eliteariafan.h; sourceTree = "<group>"; };
|
||||
8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_eliteariafan.mm; path = ../src/ios/ios_eliteariafan.mm; sourceTree = "<group>"; };
|
||||
8752C0E72B15D85600C3D1A5 /* ios_eliteariafan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ios_eliteariafan.h; path = ../src/ios/ios_eliteariafan.h; sourceTree = "<group>"; };
|
||||
87540FAC2848FD70005E0D44 /* libqtexttospeech_speechios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libqtexttospeech_speechios.a; path = ../../Qt/5.15.2/ios/plugins/texttospeech/libqtexttospeech_speechios.a; sourceTree = "<group>"; };
|
||||
8754D24B27F786F0003D7054 /* virtualrower.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = virtualrower.swift; path = ../src/ios/virtualrower.swift; sourceTree = "<group>"; };
|
||||
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/proformbike.h; sourceTree = "<group>"; };
|
||||
@@ -1168,6 +1193,8 @@
|
||||
87A18F052660D5C0002D7C96 /* ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ftmsrower.cpp; path = ../src/ftmsrower.cpp; sourceTree = "<group>"; };
|
||||
87A18F062660D5C1002D7C96 /* ftmsrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ftmsrower.h; path = ../src/ftmsrower.h; sourceTree = "<group>"; };
|
||||
87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ftmsrower.cpp; sourceTree = "<group>"; };
|
||||
87A2E0202B2B024200E6168F /* swiftDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = swiftDebug.h; path = ../src/ios/swiftDebug.h; sourceTree = "<group>"; };
|
||||
87A2E0212B2B053E00E6168F /* swiftDebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swiftDebug.mm; path = ../src/ios/swiftDebug.mm; sourceTree = "<group>"; };
|
||||
87A3BC1E2656429300D302E3 /* echelonrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = echelonrower.h; path = ../src/echelonrower.h; sourceTree = "<group>"; };
|
||||
87A3BC1F2656429400D302E3 /* rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = rower.cpp; path = ../src/rower.cpp; sourceTree = "<group>"; };
|
||||
87A3BC202656429400D302E3 /* echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = echelonrower.cpp; path = ../src/echelonrower.cpp; sourceTree = "<group>"; };
|
||||
@@ -1564,6 +1591,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8745B2782AFCB87B00991A39 /* libadb.a in Link Binary With Libraries */,
|
||||
879F74112893D5B8009A64C8 /* libqavfcamera.a in Link Binary With Libraries */,
|
||||
879F740F2893D592009A64C8 /* libqtmedia_audioengine.a in Link Binary With Libraries */,
|
||||
879F740C2893D4FA009A64C8 /* libqtaudio_coreaudio.a in Link Binary With Libraries */,
|
||||
@@ -1889,6 +1917,20 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8727C7D32B3BF1E4005429EB /* moc_proformtelnetbike.cpp */,
|
||||
8727C7D22B3BF1E4005429EB /* moc_QTelnet.cpp */,
|
||||
8727C7CC2B3BF1B8005429EB /* proformtelnetbike.cpp */,
|
||||
8727C7CD2B3BF1B8005429EB /* proformtelnetbike.h */,
|
||||
8727C7CF2B3BF1B8005429EB /* QTelnet.cpp */,
|
||||
8727C7CE2B3BF1B8005429EB /* QTelnet.h */,
|
||||
87A2E0212B2B053E00E6168F /* swiftDebug.mm */,
|
||||
8729149E2B2B010600565E33 /* qdomyoszwift-Bridging-Header.h */,
|
||||
8752C0E42B15D85600C3D1A5 /* eliteariafan.cpp */,
|
||||
8752C0E52B15D85600C3D1A5 /* eliteariafan.h */,
|
||||
8752C0E72B15D85600C3D1A5 /* ios_eliteariafan.h */,
|
||||
8752C0E62B15D85600C3D1A5 /* ios_eliteariafan.mm */,
|
||||
8752C0E22B15D84100C3D1A5 /* moc_eliteariafan.cpp */,
|
||||
8745B2772AFCB52800991A39 /* AdbClient.h */,
|
||||
87A0D7502A3A4517005147F2 /* fakerower.cpp */,
|
||||
87A0D7512A3A4517005147F2 /* fakerower.h */,
|
||||
878D83732A1F33C600D7F004 /* bkoolbike.cpp */,
|
||||
@@ -2246,6 +2288,7 @@
|
||||
35E903698E72424585D33829 /* virtualtreadmill.h */,
|
||||
C8CE72E7B224D8B886614E3F /* domyosbike.h */,
|
||||
8710707229C4A5E70094D0F3 /* GarminConnect.swift */,
|
||||
87A2E0202B2B024200E6168F /* swiftDebug.h */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -2338,6 +2381,7 @@
|
||||
AF39DD055C3EF8226FBE929D /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8745B2742AFCB3B300991A39 /* libadb.a */,
|
||||
873D388A29B0D744006A2611 /* ConnectIQ.xcframework */,
|
||||
879F74142893D732009A64C8 /* CoreMedia.framework */,
|
||||
879F74122893D705009A64C8 /* CoreVideo.framework */,
|
||||
@@ -2727,6 +2771,7 @@
|
||||
E8C543AB96796ECAA2E65C57 /* qdomyoszwift */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8745B2752AFCB4A300991A39 /* android */,
|
||||
8752B4CC27F43D9200E2EC6C /* qz.storekit */,
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */,
|
||||
25B08E2869634E9BCBA333A2 /* Generated Sources */,
|
||||
@@ -2863,6 +2908,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8745B2762AFCB4A300991A39 /* android in Copy Bundle Resources */,
|
||||
87C5F0BC26285E5F0067A1B5 /* SmtpMime in Copy Bundle Resources */,
|
||||
2188AA0A52E9CD610922F82E /* Default-568h@2x.png in Copy Bundle Resources */,
|
||||
8752B4CD27F43D9200E2EC6C /* qz.storekit in Copy Bundle Resources */,
|
||||
@@ -2985,6 +3031,8 @@
|
||||
87DF68BD25E2675100FCDA46 /* moc_eslinkertreadmill.cpp in Compile Sources */,
|
||||
87646C2027B5064600F82131 /* bhfitnesselliptical.cpp in Compile Sources */,
|
||||
8718CBAD263063CE004BF4EE /* moc_templateinfosender.cpp in Compile Sources */,
|
||||
8752C0E32B15D84100C3D1A5 /* moc_eliteariafan.cpp in Compile Sources */,
|
||||
8752C0E92B15D85600C3D1A5 /* ios_eliteariafan.mm in Compile Sources */,
|
||||
87C5F0D326285E7E0067A1B5 /* moc_mimecontentformatter.cpp in Compile Sources */,
|
||||
8718CBAB263063CE004BF4EE /* moc_templateinfosenderbuilder.cpp in Compile Sources */,
|
||||
C6B3CD471768392E18F85819 /* fit_accumulated_field.cpp in Compile Sources */,
|
||||
@@ -2992,8 +3040,10 @@
|
||||
2A61806454201575EDB3F94F /* fit_buffer_encode.cpp in Compile Sources */,
|
||||
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */,
|
||||
87E2F85D291ED308002BDC65 /* lifefitnesstreadmill.cpp in Compile Sources */,
|
||||
8752C0E82B15D85600C3D1A5 /* eliteariafan.cpp in Compile Sources */,
|
||||
87917A7328E768D200F8D9AC /* Browser.swift in Compile Sources */,
|
||||
873CD20B27EF8D8A000131BC /* inapptransaction.cpp in Compile Sources */,
|
||||
8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */,
|
||||
873824EF27E647A9004F1B46 /* query.cpp in Compile Sources */,
|
||||
876F45FF279350D9003CDA5A /* moc_concept2skierg.cpp in Compile Sources */,
|
||||
BE93C6EF2C2A6BFEEC9EA565 /* fit_buffered_mesg_broadcaster.cpp in Compile Sources */,
|
||||
@@ -3127,6 +3177,7 @@
|
||||
87EB918727EE5FE7002535E1 /* moc_nautilusbike.cpp in Compile Sources */,
|
||||
873824B827E64707004F1B46 /* moc_cache.cpp in Compile Sources */,
|
||||
873824E427E647A8004F1B46 /* cache.cpp in Compile Sources */,
|
||||
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */,
|
||||
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */,
|
||||
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */,
|
||||
C719682D8D421AF6B2DAAEA9 /* main.cpp in Compile Sources */,
|
||||
@@ -3228,6 +3279,7 @@
|
||||
873824E727E647A8004F1B46 /* record.cpp in Compile Sources */,
|
||||
B38F3288D4AE4025465C1953 /* moc_bike.cpp in Compile Sources */,
|
||||
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */,
|
||||
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */,
|
||||
C3D1FD2587BF6F15B58BA675 /* moc_bluetooth.cpp in Compile Sources */,
|
||||
87062648259480B700D06586 /* WorkoutTracking.swift in Compile Sources */,
|
||||
8C3422A825EF7ECD78951307 /* moc_bluetoothdevice.cpp in Compile Sources */,
|
||||
@@ -3276,6 +3328,7 @@
|
||||
87D269A025F535200076AA48 /* skandikawiribike.cpp in Compile Sources */,
|
||||
8738249427E646E3004F1B46 /* characteristicnotifier2a5b.cpp in Compile Sources */,
|
||||
8768D1FB285081FE00F58E3A /* nordictrackifitadbtreadmill.cpp in Compile Sources */,
|
||||
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */,
|
||||
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */,
|
||||
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
|
||||
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
|
||||
@@ -3307,6 +3360,7 @@
|
||||
8703BAED273C67B60058E206 /* moc_pafersbike.cpp in Compile Sources */,
|
||||
873824E627E647A8004F1B46 /* hostname.cpp in Compile Sources */,
|
||||
74C43649C9C4E2E5F9378019 /* moc_domyosbike.cpp in Compile Sources */,
|
||||
8727C7D02B3BF1B8005429EB /* proformtelnetbike.cpp in Compile Sources */,
|
||||
87E0761D277A081A00FDA0F9 /* technogymmyruntreadmillrfcomm.cpp in Compile Sources */,
|
||||
873824B327E64707004F1B46 /* moc_dirconprocessor.cpp in Compile Sources */,
|
||||
87A0771229B6420200A368BF /* moc_wahookickrheadwind.cpp in Compile Sources */,
|
||||
@@ -3666,7 +3720,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -3740,8 +3794,9 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/mediaservice,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
"-g",
|
||||
@@ -3818,6 +3873,7 @@
|
||||
QT_LIBRARY_SUFFIX = "";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "qdomyoszwift-Bridging-Header.h";
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift2.h";
|
||||
SWIFT_PRECOMPILE_BRIDGING_HEADER = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -3834,7 +3890,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -3910,8 +3966,9 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/mediaservice,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
"/Users/cagnulein/qdomyos-zwift/src/ios",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
@@ -3989,6 +4046,7 @@
|
||||
QT_LIBRARY_SUFFIX = _debug;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "qdomyoszwift-Bridging-Header.h";
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift2.h";
|
||||
SWIFT_PRECOMPILE_BRIDGING_HEADER = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -4038,7 +4096,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4063,7 +4121,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4134,7 +4192,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4155,7 +4213,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4226,7 +4284,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4271,7 +4329,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4340,7 +4398,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 693;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4381,7 +4439,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
|
||||
@@ -104,6 +104,9 @@ extension MainController: WorkoutTrackingDelegate {
|
||||
"\(heartRate)" as AnyObject])
|
||||
WorkoutTracking.distance = WatchKitConnection.distance
|
||||
WorkoutTracking.kcal = WatchKitConnection.kcal
|
||||
WorkoutTracking.speed = WatchKitConnection.speed
|
||||
WorkoutTracking.power = WatchKitConnection.power
|
||||
WorkoutTracking.cadence = WatchKitConnection.cadence
|
||||
|
||||
if Locale.current.measurementSystem != "Metric" {
|
||||
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")
|
||||
|
||||
@@ -24,6 +24,9 @@ class WatchKitConnection: NSObject {
|
||||
public static var distance = 0.0
|
||||
public static var kcal = 0.0
|
||||
public static var stepCadence = 0
|
||||
public static var speed = 0.0
|
||||
public static var cadence = 0.0
|
||||
public static var power = 0.0
|
||||
weak var delegate: WatchKitConnectionDelegate?
|
||||
|
||||
private override init() {
|
||||
@@ -66,6 +69,13 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
|
||||
WatchKitConnection.distance = dDistance
|
||||
let dKcal = Double(result["kcal"] as! Double)
|
||||
WatchKitConnection.kcal = dKcal
|
||||
|
||||
let dSpeed = Double(result["speed"] as! Double)
|
||||
WatchKitConnection.speed = dSpeed
|
||||
let dPower = Double(result["power"] as! Double)
|
||||
WatchKitConnection.power = dPower
|
||||
let dCadence = Double(result["cadence"] as! Double)
|
||||
WatchKitConnection.cadence = dCadence
|
||||
}, errorHandler: { (error) in
|
||||
print(error)
|
||||
})
|
||||
|
||||
@@ -31,6 +31,10 @@ class WorkoutTracking: NSObject {
|
||||
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
|
||||
public static var cadenceLastSteps = Double()
|
||||
public static var cadenceSteps = 0
|
||||
public static var speed = Double()
|
||||
public static var power = Double()
|
||||
public static var cadence = Double()
|
||||
public static var lastDateMetric = Date()
|
||||
var sport: Int = 0
|
||||
let healthStore = HKHealthStore()
|
||||
let configuration = HKWorkoutConfiguration()
|
||||
@@ -146,14 +150,37 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
|
||||
let infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
var infoToShare: Set<HKSampleType> = []
|
||||
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningStrideLength)!,
|
||||
HKSampleType.quantityType(forIdentifier: .runningVerticalOscillation)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .walkingStepLength)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
|
||||
HKHealthStore().requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
|
||||
if success {
|
||||
@@ -168,6 +195,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
}
|
||||
|
||||
func startWorkOut() {
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
print("Start workout")
|
||||
configWorkout()
|
||||
workoutSession.startActivity(with: Date())
|
||||
@@ -312,6 +340,135 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
|
||||
handleSendStatisticsData(statistics)
|
||||
}
|
||||
}
|
||||
|
||||
if(sport == 0) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
|
||||
doubleValue: WorkoutTracking.power)
|
||||
|
||||
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let powerType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingPower) else {
|
||||
return
|
||||
}
|
||||
let wattPerIntervalSample = HKQuantitySample(type: powerType,
|
||||
quantity: wattPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.cadence / 60.0)
|
||||
|
||||
guard let cadenceType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingCadence) else {
|
||||
return
|
||||
}
|
||||
let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
|
||||
quantity: cadencePerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} else if(sport == 1) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
|
||||
doubleValue: WorkoutTracking.power)
|
||||
|
||||
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let powerType = HKQuantityType.quantityType(
|
||||
forIdentifier: .runningPower) else {
|
||||
return
|
||||
}
|
||||
let wattPerIntervalSample = HKQuantitySample(type: powerType,
|
||||
quantity: wattPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .runningSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
} else if(sport == 2) {
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .walkingSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
}
|
||||
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
}
|
||||
|
||||
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
|
||||
|
||||
@@ -28,7 +28,7 @@ $ sudo ./qdomyos-zwift
|
||||
You will need to (at a minimum) to install the xcode Command Line Tools (CLI) thanks to @richardwait
|
||||
https://developer.apple.com/download/more/?=xcode
|
||||
|
||||
Download and install http://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-mac-x64-5.12.9.dmg and simply run the qdomyos-zwift release for MacOs
|
||||
Download and install https://download.qt.io/archive/qt/5.12/5.12.12/qt-opensource-mac-x64-5.12.12.dmg and simply run the qdomyos-zwift release for MacOs
|
||||
|
||||
## On Raspberry Pi Zero W
|
||||
|
||||
@@ -77,7 +77,7 @@ Apply the changes `sudo systemctl restart dhcpcd.service` and ensure you have in
|
||||
|
||||
#### Enable SSH access
|
||||
|
||||
You might want to access your raspberry remotely while it is attached to your fitness equipement.
|
||||
You might want to access your raspberry remotely while it is attached to your fitness equipment.
|
||||
|
||||
`sudo raspi-config` > `Interface Options` > `SSH`
|
||||
|
||||
@@ -175,7 +175,7 @@ Then reboot to check operations (`sudo reboot`)
|
||||
|
||||
### (optional) Enable overlay FS
|
||||
|
||||
Once that everything is working as expected, and if you dedicate your raspeberry pi to this usage, you might want to enable the read-only overlay FS.
|
||||
Once that everything is working as expected, and if you dedicate your Raspberry pi to this usage, you might want to enable the read-only overlay FS.
|
||||
|
||||
By enabling the overlay read-only system, your SD card will be read-only only and every file written will be to RAM.
|
||||
Then at each reboot the RAM is erased and you'll revert to the initial status of the overlay file-system.
|
||||
|
||||
@@ -18,7 +18,7 @@ Please refer to this article for more information under [QML Operations](https:/
|
||||
|
||||
## Configuration in NativeQT mode
|
||||
|
||||
This is the list of settings available in the application. These settings needs to be appended to the binary command line.
|
||||
This is the list of settings available in the application. These settings need to be appended to the binary command line.
|
||||
*Example :* `sudo ./qdomyos-zwift -no-gui` for disabling any graphical interface.
|
||||
|
||||
| **Option** | **Type** | **Default** | **Function** |
|
||||
@@ -35,8 +35,8 @@ This is the list of settings available in the application. These settings needs
|
||||
| -heart-service | Boolean | True | Simulate HR service (required for applications not reading FTMS) |
|
||||
| -only-virtualbike | Boolean | False | |
|
||||
| -only-virtualtreadmill | Boolean | False | |
|
||||
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipement if enabled |
|
||||
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipement |
|
||||
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipment if enabled |
|
||||
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipment |
|
||||
| -bike-cadence-sensor | Boolean | False | |
|
||||
| -bike-power-sensor | Boolean | False | |
|
||||
| -battery-service | Boolean | False | |
|
||||
@@ -45,7 +45,7 @@ This is the list of settings available in the application. These settings needs
|
||||
| -run-cadence-sensor | Boolean | False | |
|
||||
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
|
||||
| -train | String | | Force training program |
|
||||
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
|
||||
| -name | String | | Force bluetooth device name (if QZ struggles to find your fitness equipment) |
|
||||
| -poll-device-time | Int | 200 (ms) | Frequency to refresh information from QZ to Fitness equipment |
|
||||
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
|
||||
| -bike-resistance-offset | Int | | Set another resistance point than default |
|
||||
|
||||
BIN
qt-patches/android/5.15.0/jar/QtAndroidBluetooth.jar
Normal file
BIN
qt-patches/android/5.15.0/jar/QtAndroidBluetooth.jar
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/mingw64/Qt5Bluetooth.dll
Normal file
BIN
qt-patches/windows/5.15.2/binary/mingw64/Qt5Bluetooth.dll
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.dll
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.dll
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.exp
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.exp
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.lib
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetooth.lib
Normal file
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
QMAKE_PRL_BUILD_DIR = C:/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = Qt5Bluetooth.lib
|
||||
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin windows prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl flat debug_and_release precompile_header autogen_precompile_source embed_manifest_dll embed_manifest_exe shared shared release no_plugin_manifest win32 msvc copy_dir_files sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd rdseed shani x86SimdAlways prefix_build force_independent utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions release ReleaseBuild Release build_pass c++11 generated_privates relative_qt_rpath target_qt c++11 strict_c++ c++14 c++1z qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake skip_target_version_ext release ReleaseBuild Release build_pass have_target dll exclusive_builds debug_info no_autoqmake thread moc resources
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.dll
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.dll
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.exp
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.exp
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.lib
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/Qt5Bluetoothd.lib
Normal file
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
QMAKE_PRL_BUILD_DIR = C:/qt-everywhere-src-5.15.2/qtconnectivity/src/bluetooth
|
||||
QMAKE_PRO_INPUT = bluetooth.pro
|
||||
QMAKE_PRL_TARGET = Qt5Bluetoothd.lib
|
||||
QMAKE_PRL_CONFIG = lex yacc debug depend_includepath testcase_targets import_plugins import_qpa_plugin windows prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on link_prl flat debug_and_release precompile_header autogen_precompile_source embed_manifest_dll embed_manifest_exe shared shared no_plugin_manifest win32 msvc copy_dir_files sse2 aesni sse3 ssse3 sse4_1 sse4_2 avx avx2 avx512f avx512bw avx512cd avx512dq avx512er avx512ifma avx512pf avx512vbmi avx512vl compile_examples f16c force_debug_info largefile precompile_header rdrnd rdseed shani x86SimdAlways prefix_build force_independent utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions debug DebugBuild Debug build_pass c++11 generated_privates relative_qt_rpath target_qt c++11 strict_c++ c++14 c++1z qt_install_headers need_fwd_pri qt_install_module debug_and_release build_all create_cmake skip_target_version_ext debug DebugBuild Debug build_pass have_target dll no_plist exclusive_builds debug_info no_autoqmake thread moc resources
|
||||
QMAKE_PRL_VERSION = 5.15.2
|
||||
BIN
qt-patches/windows/5.15.2/binary/msvc2019/ucrtbased.dll
Normal file
BIN
qt-patches/windows/5.15.2/binary/msvc2019/ucrtbased.dll
Normal file
Binary file not shown.
1357
qt-patches/windows/5.15.2/qlowenergycontroller_win.cpp
Normal file
1357
qt-patches/windows/5.15.2/qlowenergycontroller_win.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1162
qt-patches/windows/5.15.2/qlowenergycontroller_winrt.cpp
Normal file
1162
qt-patches/windows/5.15.2/qlowenergycontroller_winrt.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1738
qt-patches/windows/5.15.2/qlowenergycontroller_winrt_new.cpp
Normal file
1738
qt-patches/windows/5.15.2/qlowenergycontroller_winrt_new.cpp
Normal file
File diff suppressed because it is too large
Load Diff
25
src/ChartFooter.qml
Normal file
25
src/ChartFooter.qml
Normal file
@@ -0,0 +1,25 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Loader {
|
||||
id: chartFooterLoader
|
||||
sourceComponent: ChartFooterInnerJS
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: CHARTJS ? "ChartFooterInnerJS.qml":"ChartFooterInnerNoJS.qml"
|
||||
onLoaded: {
|
||||
if(CHARTJS) {
|
||||
chartFooterLoader.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/ChartFooterInnerJS.qml
Normal file
31
src/ChartFooterInnerJS.qml
Normal file
@@ -0,0 +1,31 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtWebView 1.1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
WebView {
|
||||
id: webView
|
||||
anchors.fill: parent
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chartlive.htm"
|
||||
visible: rootItem.chartFooterVisible
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.errorString) {
|
||||
console.error(loadRequest.errorString);
|
||||
console.error("port " + settings.value("template_inner_QZWS_port"));
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
console.log("onVisibleChanged" + visible)
|
||||
if(visible === true) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/ChartFooterInnerNoJS.qml
Normal file
12
src/ChartFooterInnerNoJS.qml
Normal file
@@ -0,0 +1,12 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,9 @@
|
||||
#include <QThread>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windef.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winbase.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <winbase.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h> // unix!!
|
||||
|
||||
@@ -11,6 +11,7 @@ import QtLocation 5.6
|
||||
|
||||
ColumnLayout {
|
||||
signal trainprogram_open_clicked(url name)
|
||||
signal trainprogram_open_other_folder(url name)
|
||||
signal trainprogram_preview(url name)
|
||||
FileDialog {
|
||||
id: fileDialogTrainProgram
|
||||
@@ -18,7 +19,11 @@ ColumnLayout {
|
||||
folder: shortcuts.home
|
||||
onAccepted: {
|
||||
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
|
||||
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
|
||||
if(OS_VERSION === "Android") {
|
||||
trainprogram_open_other_folder(fileDialogTrainProgram.fileUrl)
|
||||
} else {
|
||||
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
|
||||
}
|
||||
fileDialogTrainProgram.close()
|
||||
}
|
||||
onRejected: {
|
||||
|
||||
100
src/Home.qml
100
src/Home.qml
@@ -291,59 +291,75 @@ HomeForm{
|
||||
}
|
||||
|
||||
footer:
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.top: gridView.bottom
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height / 2
|
||||
// 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()) } }
|
||||
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
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
//videoPlaybackHalf.playbackRate = rootItem.videoRate
|
||||
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()
|
||||
}
|
||||
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
|
||||
}
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property int currentId: -1 // Original position in model
|
||||
|
||||
522
src/QTelnet.cpp
Normal file
522
src/QTelnet.cpp
Normal file
@@ -0,0 +1,522 @@
|
||||
#include "QTelnet.h"
|
||||
|
||||
#include "QTelnet.h"
|
||||
#include <QHostAddress>
|
||||
|
||||
const char QTelnet::IACWILL[2] = { IAC, WILL };
|
||||
const char QTelnet::IACWONT[2] = { IAC, WONT };
|
||||
const char QTelnet::IACDO[2] = { IAC, DO };
|
||||
const char QTelnet::IACDONT[2] = { IAC, DONT };
|
||||
const char QTelnet::IACSB[2] = { IAC, SB };
|
||||
const char QTelnet::IACSE[2] = { IAC, SE };
|
||||
|
||||
char QTelnet::_sendCodeArray[2] = { IAC, 0 };
|
||||
char QTelnet::_arrCRLF[2] = { 13, 10 };
|
||||
char QTelnet::_arrCR[2] = { 13, 0 };
|
||||
|
||||
QTelnet::QTelnet(QObject *parent) :
|
||||
QTcpSocket(parent), m_actualSB(0)
|
||||
{
|
||||
connect( this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)) );
|
||||
connect( this, SIGNAL(readyRead()), this, SLOT(onReadyRead()) );
|
||||
}
|
||||
|
||||
QString QTelnet::peerInfo() const
|
||||
{
|
||||
return QString("%1 (%2):%3").arg(peerName()).arg(peerAddress().toString()).arg(peerPort());
|
||||
}
|
||||
|
||||
bool QTelnet::isConnected() const
|
||||
{
|
||||
return state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
bool QTelnet::testBinaryMode() const
|
||||
{
|
||||
return m_receivedDX[(unsigned char)TELOPT_BINARY] == DO;
|
||||
}
|
||||
|
||||
void QTelnet::connectToHost(const QString &host, quint16 port)
|
||||
{
|
||||
if( !isConnected() )
|
||||
{
|
||||
resetProtocol();
|
||||
abort();
|
||||
QTcpSocket::connectToHost(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::sendData(const QByteArray &ba)
|
||||
{
|
||||
if( isConnected() )
|
||||
transpose( ba.constData(), ba.count() );
|
||||
}
|
||||
|
||||
void QTelnet::socketError(QAbstractSocket::SocketError err)
|
||||
{
|
||||
Q_UNUSED(err);
|
||||
disconnectFromHost();
|
||||
}
|
||||
|
||||
void QTelnet::write(const char c)
|
||||
{
|
||||
QTcpSocket::write( (char*)&c, 1 );
|
||||
}
|
||||
|
||||
void QTelnet::setCustomCR(char cr, char cr2)
|
||||
{
|
||||
_arrCR[0] = cr;
|
||||
_arrCR[1] = cr2;
|
||||
}
|
||||
|
||||
void QTelnet::setCustomCRLF(char lf, char cr)
|
||||
{
|
||||
_arrCR[0] = lf;
|
||||
_arrCR[1] = cr;
|
||||
}
|
||||
|
||||
// Envia el codigo de control al servidor.
|
||||
void QTelnet::sendTelnetControl(char codigo)
|
||||
{
|
||||
_sendCodeArray[1] = codigo;
|
||||
QTcpSocket::write(_sendCodeArray, 2);
|
||||
}
|
||||
|
||||
void QTelnet::writeCustomCRLF()
|
||||
{
|
||||
QTcpSocket::write(_arrCRLF, 2);
|
||||
}
|
||||
|
||||
void QTelnet::writeCustomCR()
|
||||
{
|
||||
QTcpSocket::write(_arrCR, 2);
|
||||
}
|
||||
|
||||
/// Resetea los datos del protocolo. Debe llamarse cada vez que se inicia una conexión nueva.
|
||||
void QTelnet::resetProtocol()
|
||||
{
|
||||
for( int i = 0; i < 256; i++ )
|
||||
{
|
||||
m_receivedDX[i] =
|
||||
m_receivedWX[i] =
|
||||
m_sentDX[i] =
|
||||
m_sentWX[i] = 0;
|
||||
m_negotiationState = STATE_DATA;
|
||||
m_buffSB.clear();
|
||||
m_actualSB = 0;
|
||||
}
|
||||
m_oldWinSize.setHeight(-1);
|
||||
m_oldWinSize.setWidth(-1);
|
||||
}
|
||||
|
||||
void QTelnet::sendSB(char code, char *arr, int iLen)
|
||||
{
|
||||
write(IAC);
|
||||
write(SB);
|
||||
write(code);
|
||||
|
||||
QTcpSocket::write(arr, iLen);
|
||||
|
||||
write(IAC);
|
||||
write(SE);
|
||||
}
|
||||
void QTelnet::sendWindowSize()
|
||||
{
|
||||
if( isConnected() && (m_receivedDX[TELOPT_NAWS] == DO) && (m_oldWinSize != m_winSize) )
|
||||
{
|
||||
char size[4];
|
||||
|
||||
m_oldWinSize = m_winSize;
|
||||
size[0] = (m_winSize.width()>>8) & 0xFF;
|
||||
size[1] = m_winSize.width() & 0xFF;
|
||||
size[2] = (m_winSize.height()>>8) & 0xFF;
|
||||
size[3] = m_winSize.height() & 0xFF;
|
||||
sendSB(TELOPT_NAWS, size, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an incoming IAC SB type chars IAC SE
|
||||
void QTelnet::handleSB()
|
||||
{
|
||||
switch( m_actualSB )
|
||||
{
|
||||
case TELOPT_TTYPE:
|
||||
if( (m_buffSB.count() > 0) && ((unsigned char)m_buffSB[0] == (unsigned char)TELQUAL_SEND) )
|
||||
{
|
||||
QTcpSocket::write(IACSB, 2);
|
||||
write(TELOPT_TTYPE);
|
||||
write(TELQUAL_IS);
|
||||
/* FIXME: need more logic here if we use
|
||||
* more than one terminal type
|
||||
*/
|
||||
QTcpSocket::write("SiraggaTerminal", 15);
|
||||
QTcpSocket::write(IACSE, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Analiza el texto saliente para que cumpla las normas del protocolo.
|
||||
// Además ya lo escribe en el socket.
|
||||
void QTelnet::transpose(const char *buf, int iLen)
|
||||
{
|
||||
for( int i = 0; i < iLen; i++ )
|
||||
{
|
||||
switch( buf[i] )
|
||||
{
|
||||
case IAC:
|
||||
// Escape IAC twice in stream ... to be telnet protocol compliant
|
||||
// this is there in binary and non-binary mode.
|
||||
write(IAC);
|
||||
write(IAC);
|
||||
break;
|
||||
case 10: // \n
|
||||
// We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13
|
||||
// we assume that the Terminal sends \n for lf+cr and \r for just cr
|
||||
// linefeed+carriage return is CR LF
|
||||
|
||||
// En modo binario no se traduce nada.
|
||||
if( testBinaryMode() )
|
||||
write(buf[i]);
|
||||
else
|
||||
writeCustomCRLF();
|
||||
break;
|
||||
case 13: // \r
|
||||
// carriage return is CR NUL */
|
||||
|
||||
// En modo binario no se traduce nada.
|
||||
if( testBinaryMode() )
|
||||
write(buf[i]);
|
||||
else
|
||||
writeCustomCR();
|
||||
break;
|
||||
default:
|
||||
// all other characters are just copied
|
||||
write(buf[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::willsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentDX[(unsigned char)action]) || (WILL != m_receivedWX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentDX[(unsigned char)action] = reply;
|
||||
m_receivedWX[(unsigned char)action] = WILL;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::wontsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentDX[(unsigned char)action]) || (WONT != m_receivedWX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentDX[(unsigned char)action] = reply;
|
||||
m_receivedWX[(unsigned char)action] = WONT;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::doesReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentWX[(unsigned char)action]) || (DO != m_receivedDX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentWX[(unsigned char)action] = reply;
|
||||
m_receivedDX[(unsigned char)action] = DO;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::dontsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentWX[(unsigned char)action]) || (DONT != m_receivedDX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentWX[(unsigned char)action] = reply;
|
||||
m_receivedDX[(unsigned char)action] = DONT;
|
||||
}
|
||||
}
|
||||
|
||||
// Analiza el buffer de entrada colocá ndolo en el buffer de procesado usando el protocolo telnet.
|
||||
qint64 QTelnet::doTelnetInProtocol(qint64 buffSize)
|
||||
{
|
||||
qint64 iIn, iOut;
|
||||
char b;
|
||||
|
||||
for( iIn = 0, iOut = 0; iIn < buffSize; iIn++ )
|
||||
{
|
||||
b = m_buffIncoming[iIn];
|
||||
|
||||
switch( m_negotiationState )
|
||||
{
|
||||
case STATE_DATA:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IAC;
|
||||
break;
|
||||
case '\r':
|
||||
m_negotiationState = STATE_DATAR;
|
||||
break;
|
||||
case '\n':
|
||||
m_negotiationState = STATE_DATAN;
|
||||
break;
|
||||
default:
|
||||
m_buffProcessed[iOut++] = b;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_DATAN:
|
||||
case STATE_DATAR:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IAC;
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
m_buffProcessed[iOut++] = '\n';
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
default:
|
||||
m_buffProcessed[iOut++] = b;
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC: // Dos IAC seguidos, se intenta enviar un caracter con el valor IAC.
|
||||
m_negotiationState = STATE_DATA;
|
||||
m_buffProcessed[iOut++] = IAC;
|
||||
break;
|
||||
case WILL:
|
||||
m_negotiationState = STATE_IACWILL;
|
||||
break;
|
||||
case WONT:
|
||||
m_negotiationState = STATE_IACWONT;
|
||||
break;
|
||||
case DONT:
|
||||
m_negotiationState = STATE_IACDONT;
|
||||
break;
|
||||
case DO:
|
||||
m_negotiationState = STATE_IACDO;
|
||||
break;
|
||||
case EOR:
|
||||
emitEndOfRecord();
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case SB:
|
||||
m_negotiationState = STATE_IACSB;
|
||||
m_buffSB.clear();
|
||||
break;
|
||||
default:
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACWILL:
|
||||
switch( b )
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
emitEchoLocal(false);
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_EOR:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
default:
|
||||
willsReply(b, DONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACWONT:
|
||||
switch(b)
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
emitEchoLocal(true);
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_EOR:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
default:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACDO:
|
||||
switch( b )
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
doesReply(b, WILL);
|
||||
emitEchoLocal(true);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_TTYPE:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_NAWS:
|
||||
m_receivedDX[(unsigned char)b] = (unsigned char)DO;
|
||||
m_sentWX[(unsigned char)b] = (unsigned char)WILL;
|
||||
write(IAC);
|
||||
write(WILL);
|
||||
write(b);
|
||||
|
||||
// Enviamos el tamaño de la pantalla.
|
||||
sendWindowSize();
|
||||
break;
|
||||
default:
|
||||
doesReply(b, WONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACDONT:
|
||||
switch (b)
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
dontsReply(b, WONT);
|
||||
emitEchoLocal(false);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
case TELOPT_NAWS:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
default:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACSB:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
// Entramos en estado IAC en la sub-negociación.
|
||||
m_negotiationState = STATE_IACSBIAC;
|
||||
break;
|
||||
default:
|
||||
// Iniciamos la sub-negociación.
|
||||
m_buffSB.clear();
|
||||
m_actualSB = b;
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBDATA: // Estamos en datos de la subnegociación.
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IACSBDATAIAC;
|
||||
break;
|
||||
default:
|
||||
m_buffSB.append(b);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBIAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
// Reiniciamos la sub-negociación.
|
||||
m_buffSB.clear();
|
||||
m_actualSB = b;
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
default:
|
||||
// Salimos de la sub-negociación.
|
||||
m_negotiationState = STATE_DATA;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBDATAIAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
m_buffSB.append(IAC);
|
||||
break;
|
||||
case SE:
|
||||
handleSB();
|
||||
m_actualSB = 0;
|
||||
m_buffSB.clear();
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case SB:
|
||||
handleSB();
|
||||
m_buffSB.clear();
|
||||
m_negotiationState = STATE_IACSB;
|
||||
break;
|
||||
default:
|
||||
m_buffSB.clear();
|
||||
m_actualSB = 0;
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return iOut;
|
||||
}
|
||||
|
||||
void QTelnet::onReadyRead()
|
||||
{
|
||||
qint64 readed;
|
||||
qint64 processed;
|
||||
|
||||
while( (readed = read(m_buffIncoming, IncommingBufferSize)) != 0 )
|
||||
{
|
||||
switch( readed )
|
||||
{
|
||||
case -1:
|
||||
disconnectFromHost();
|
||||
break;
|
||||
default:
|
||||
processed = doTelnetInProtocol(readed);
|
||||
if( processed > 0 )
|
||||
Q_EMIT(newData(m_buffProcessed, processed));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/QTelnet.h
Normal file
136
src/QTelnet.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#ifndef QTELNET_H
|
||||
#define QTELNET_H
|
||||
|
||||
#include <QObject>
|
||||
#include <qtcpsocket.h>
|
||||
#include <qsize.h>
|
||||
#include <QString>
|
||||
|
||||
#define IncommingBufferSize (1500)
|
||||
|
||||
class QTelnet : public QTcpSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum SocketStatus
|
||||
{
|
||||
Disconnected,
|
||||
Resolving, // Resolving host
|
||||
Connecting, // Connecting to host.
|
||||
Connected // Connected to host.
|
||||
};
|
||||
|
||||
protected:
|
||||
enum TelnetStateCodes
|
||||
{
|
||||
STATE_DATA = (char)0,
|
||||
STATE_IAC = (char)1,
|
||||
STATE_IACSB = (char)2,
|
||||
STATE_IACWILL = (char)3,
|
||||
STATE_IACDO = (char)4,
|
||||
STATE_IACWONT = (char)5,
|
||||
STATE_IACDONT = (char)6,
|
||||
STATE_IACSBIAC = (char)7,
|
||||
STATE_IACSBDATA = (char)8,
|
||||
STATE_IACSBDATAIAC = (char)9,
|
||||
STATE_DATAR = (char)10,
|
||||
STATE_DATAN = (char)11
|
||||
};
|
||||
enum TelnetCodes
|
||||
{
|
||||
// Negociación entrada/salida (cliente<->servidor)
|
||||
IAC = (char)255, // Inicia la secuencia para la negociación telnet.
|
||||
EOR = (char)239, // Estando en la negociación, End Of Record.
|
||||
WILL = (char)251, // Estando en la negociación, Acepta el protocolo?
|
||||
WONT = (char)252, // Estando en la negociación, Acepta el protocolo?
|
||||
DO = (char)253, // Estando en la negociación, Protocolo aceptado.
|
||||
DONT = (char)254, // Estando en la negociación, Protocolo denegado.
|
||||
SB = (char)250, // Estando en la negociación, inicia secuencia de sub-negociación.
|
||||
SE = (char)240, // Estando en la sub-negociación, fin de sub-negociación.
|
||||
|
||||
// Negociación de salida (cliente->servidor)
|
||||
TELOPT_BINARY = (char)0, // Estando en la negociación, pide modo binario.
|
||||
TELOPT_ECHO = (char)1, // Estando en la negociación, pide echo local.
|
||||
TELOPT_SGA = (char)2, // Estando en la negociación, pide Supress Go Ahead.
|
||||
TELOPT_EOR = (char)25, // Estando en la negociación, informa End Of Record.
|
||||
TELOPT_NAWS = (char)31, // Estando en la negociación, Negotiate Abaut Window Size.
|
||||
TELOPT_TTYPE = (char)24 // Estando en la negociación, Terminal Type.
|
||||
};
|
||||
enum TelnetQualifiers
|
||||
{
|
||||
TELQUAL_IS = (char)0,
|
||||
TELQUAL_SEND = (char)1
|
||||
};
|
||||
|
||||
private:
|
||||
static const char IACWILL[2];
|
||||
static const char IACWONT[2];
|
||||
static const char IACDO[2];
|
||||
static const char IACDONT[2];
|
||||
static const char IACSB[2];
|
||||
static const char IACSE[2];
|
||||
static char _sendCodeArray[2];
|
||||
static char _arrCRLF[2];
|
||||
static char _arrCR[2];
|
||||
|
||||
QSize m_winSize; // Tamaño de la pantalla en caracteres.
|
||||
QSize m_oldWinSize; // Tamaño de la pantalla que se envió por última vez al server. Para no enviar el mismo dato.
|
||||
enum TelnetStateCodes m_negotiationState;
|
||||
char m_receivedDX[256]; // What IAC DO(NT) request do we have received already ?
|
||||
char m_receivedWX[256]; // What IAC WILL/WONT request do we have received already ?
|
||||
char m_sentDX[256]; // What IAC DO/DONT request do we have sent already ?
|
||||
char m_sentWX[256]; // What IAC WILL/WONT request do we have sent already ?
|
||||
void resetProtocol();
|
||||
|
||||
char m_buffIncoming[IncommingBufferSize];
|
||||
char m_buffProcessed[IncommingBufferSize];
|
||||
QByteArray m_buffSB;
|
||||
int m_actualSB;
|
||||
|
||||
void emitEndOfRecord() { Q_EMIT(endOfRecord()); }
|
||||
void emitEchoLocal(bool bEcho) { Q_EMIT(echoLocal(bEcho)); }
|
||||
|
||||
void sendTelnetControl(char codigo);
|
||||
void handleSB(void);
|
||||
void transpose(const char *buf, int iLen);
|
||||
|
||||
void willsReply(char action, char reply);
|
||||
void wontsReply(char action, char reply);
|
||||
void doesReply(char action, char reply);
|
||||
void dontsReply(char action, char reply);
|
||||
|
||||
void sendSB(char code, char *arr, int iLen);
|
||||
qint64 doTelnetInProtocol(qint64 buffSize);
|
||||
|
||||
public:
|
||||
explicit QTelnet(QObject *parent = 0);
|
||||
|
||||
virtual void connectToHost(const QString &host, quint16 port);
|
||||
void sendData(const QByteArray &ba);
|
||||
void setCustomCRLF(char lf = 13, char cr = 10);
|
||||
void setCustomCR(char cr = 10, char cr2 = 0);
|
||||
|
||||
void writeCustomCRLF();
|
||||
void writeCustomCR();
|
||||
|
||||
void write(const char c);
|
||||
|
||||
bool isConnected() const;
|
||||
bool testBinaryMode() const;
|
||||
void setWindSize(QSize s) {m_winSize = s;}
|
||||
void sendWindowSize();
|
||||
|
||||
QString peerInfo()const;
|
||||
|
||||
signals:
|
||||
void newData(const char *buff, int len);
|
||||
void endOfRecord();
|
||||
void echoLocal(bool echo);
|
||||
|
||||
private slots:
|
||||
void socketError(QAbstractSocket::SocketError err);
|
||||
void onReadyRead();
|
||||
};
|
||||
|
||||
#endif // QTELNET_H
|
||||
@@ -93,7 +93,7 @@ Item {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
/*Button {
|
||||
Button {
|
||||
id: restoreButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -101,7 +101,8 @@ Item {
|
||||
text: "Restore Purchases"
|
||||
onClicked: {
|
||||
console.log("restoring...");
|
||||
toast.show("Restoring...");
|
||||
iapStore.restorePurchases();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
signal trainprogram_open_clicked(url name)
|
||||
signal trainprogram_open_other_folder(url name)
|
||||
signal trainprogram_preview(url name)
|
||||
FileDialog {
|
||||
id: fileDialogTrainProgram
|
||||
@@ -16,7 +17,11 @@ ColumnLayout {
|
||||
folder: shortcuts.home
|
||||
onAccepted: {
|
||||
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
|
||||
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
|
||||
if(OS_VERSION === "Android") {
|
||||
trainprogram_open_other_folder(fileDialogTrainProgram.fileUrl)
|
||||
} else {
|
||||
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
|
||||
}
|
||||
fileDialogTrainProgram.close()
|
||||
}
|
||||
onRejected: {
|
||||
|
||||
@@ -297,6 +297,7 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
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();
|
||||
@@ -339,12 +340,12 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
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(QDateTime::currentDateTime()))));
|
||||
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
|
||||
((double)1000.0 / (double)(lastTimeCharacteristicChanged.msecsTo(now))));
|
||||
lastTimeCharacteristicChanged = now;
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
|
||||
|
||||
@@ -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.13.98" android:versionCode="614" 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.16.29" android:versionCode="693" 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,6 +79,19 @@
|
||||
android:name=".ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
<service
|
||||
android:name=".WearableMessageListenerService"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPrefix="/qz"
|
||||
android:scheme="wear" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".ChannelService"></service>
|
||||
<service android:name=".FloatingWindowGFG" android:enabled="true" android:exported="true"/>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ println(amazon)
|
||||
|
||||
dependencies {
|
||||
compile 'com.rvalerio:fgchecker:1.1.0'
|
||||
implementation "androidx.core:core-ktx:+"
|
||||
implementation "androidx.core:core-ktx:1.12.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
|
||||
|
||||
if(amazon == "1") {
|
||||
@@ -46,6 +46,7 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.6'
|
||||
androidTestImplementation "com.android.support:support-annotations:28.0.0"
|
||||
implementation 'com.google.android.gms:play-services-wearable:+'
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
<!-- Concept2 PM3,PM4 -->
|
||||
<usb-device vendor-id="17A4" product-id="0002" />
|
||||
<usb-device vendor-id="17A4" product-id="0001" />
|
||||
|
||||
<!-- CDC driver -->
|
||||
<usb-device vendor-id="9025" /> <!-- 0x2341 / ......: Arduino -->
|
||||
|
||||
@@ -35,11 +35,13 @@ public class Ant {
|
||||
static boolean speedRequest = false;
|
||||
static boolean heartRequest = false;
|
||||
static boolean garminKey = false;
|
||||
static boolean treadmill = false;
|
||||
|
||||
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey) {
|
||||
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill) {
|
||||
Log.v(TAG, "antStart");
|
||||
speedRequest = SpeedRequest;
|
||||
heartRequest = HeartRequest;
|
||||
treadmill = Treadmill;
|
||||
garminKey = GarminKey;
|
||||
|
||||
activity = a;
|
||||
|
||||
@@ -35,9 +35,14 @@ public class CSafeRowerUSBHID {
|
||||
|
||||
public static void open(Context context) {
|
||||
Log.d("QZ","CSafeRowerUSBHID open");
|
||||
hidBridge = new HidBridge(context, 0x0002, 0x17A4);
|
||||
hidBridge = new HidBridge(context, 0x0002, 0x17A4);
|
||||
boolean ret = hidBridge.OpenDevice();
|
||||
Log.d("QZ","hidBridge.OpenDevice " + ret);
|
||||
if(ret == false) {
|
||||
hidBridge = new HidBridge(context, 0x0001, 0x17A4);
|
||||
ret = hidBridge.OpenDevice();
|
||||
Log.d("QZ","hidBridge.OpenDevice " + ret);
|
||||
}
|
||||
hidBridge.StartReadingThread();
|
||||
Log.d("QZ","hidBridge.StartReadingThread");
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class ChannelService extends Service {
|
||||
HeartChannelController heartChannelController = null;
|
||||
PowerChannelController powerChannelController = null;
|
||||
SpeedChannelController speedChannelController = null;
|
||||
SDMChannelController sdmChannelController = null;
|
||||
|
||||
private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
@@ -104,6 +105,9 @@ public class ChannelService extends Service {
|
||||
if (null != speedChannelController) {
|
||||
speedChannelController.speed = speed;
|
||||
}
|
||||
if (null != sdmChannelController) {
|
||||
sdmChannelController.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
void setPower(int power) {
|
||||
@@ -119,6 +123,9 @@ public class ChannelService extends Service {
|
||||
if (null != speedChannelController) {
|
||||
speedChannelController.cadence = cadence;
|
||||
}
|
||||
if (null != sdmChannelController) {
|
||||
sdmChannelController.cadence = cadence;
|
||||
}
|
||||
}
|
||||
|
||||
int getHeart() {
|
||||
@@ -141,8 +148,12 @@ public class ChannelService extends Service {
|
||||
heartChannelController = new HeartChannelController(acquireChannel());
|
||||
|
||||
if (Ant.speedRequest) {
|
||||
powerChannelController = new PowerChannelController(acquireChannel());
|
||||
speedChannelController = new SpeedChannelController(acquireChannel());
|
||||
if(Ant.treadmill) {
|
||||
sdmChannelController = new SDMChannelController(acquireChannel());
|
||||
} else {
|
||||
powerChannelController = new PowerChannelController(acquireChannel());
|
||||
speedChannelController = new SpeedChannelController(acquireChannel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +164,12 @@ public class ChannelService extends Service {
|
||||
powerChannelController.close();
|
||||
if (speedChannelController != null)
|
||||
speedChannelController.close();
|
||||
if (sdmChannelController != null)
|
||||
sdmChannelController.close();
|
||||
heartChannelController = null;
|
||||
powerChannelController = null;
|
||||
speedChannelController = null;
|
||||
sdmChannelController = null;
|
||||
}
|
||||
|
||||
AntChannel acquireChannel() throws ChannelNotAvailableException {
|
||||
|
||||
28
src/android/src/ContentHelper.java
Normal file
28
src/android/src/ContentHelper.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
|
||||
public class ContentHelper {
|
||||
|
||||
public static String getFileName(Context context, Uri uri) {
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
Log.d("ContentHelper", "content");
|
||||
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
|
||||
Log.d("ContentHelper", "cursor " + cursor);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
Log.d("ContentHelper", "result " + result);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
308
src/android/src/SDMChannelController.java
Normal file
308
src/android/src/SDMChannelController.java
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2012 Dynastream Innovations Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dsi.ant.channel.AntChannel;
|
||||
import com.dsi.ant.channel.AntCommandFailedException;
|
||||
import com.dsi.ant.channel.IAntChannelEventHandler;
|
||||
import com.dsi.ant.message.ChannelId;
|
||||
import com.dsi.ant.message.ChannelType;
|
||||
import com.dsi.ant.message.EventCode;
|
||||
import com.dsi.ant.message.fromant.ChannelEventMessage;
|
||||
import com.dsi.ant.message.fromant.MessageFromAntType;
|
||||
import com.dsi.ant.message.ipc.AntMessageParcel;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class SDMChannelController {
|
||||
// The device type and transmission type to be part of the channel ID message
|
||||
private static final int CHANNEL_SPEED_DEVICE_TYPE = 0x7C;
|
||||
private static final int CHANNEL_SPEED_TRANSMISSION_TYPE = 1;
|
||||
|
||||
// The period and frequency values the channel will be configured to
|
||||
private static final int CHANNEL_SPEED_PERIOD = 8134; // 1 Hz
|
||||
private static final int CHANNEL_SPEED_FREQUENCY = 57;
|
||||
|
||||
private static final String TAG = SDMChannelController.class.getSimpleName();
|
||||
public static final int SPEED_SENSOR_ID = 0x9e3d4b99;
|
||||
|
||||
private static final double MILLISECOND_TO_1_1024_CONVERSION = 0.9765625;
|
||||
|
||||
private AntChannel mAntChannel;
|
||||
|
||||
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
|
||||
|
||||
private boolean mIsOpen;
|
||||
double speed = 0.0;
|
||||
int cadence = 0;
|
||||
byte stride_count = 0;
|
||||
|
||||
public SDMChannelController(AntChannel antChannel) {
|
||||
mAntChannel = antChannel;
|
||||
openChannel();
|
||||
}
|
||||
|
||||
boolean openChannel() {
|
||||
if (null != mAntChannel) {
|
||||
if (mIsOpen) {
|
||||
Log.w(TAG, "Channel was already open");
|
||||
} else {
|
||||
// Channel ID message contains device number, type and transmission type. In
|
||||
// order for master (TX) channels and slave (RX) channels to connect, they
|
||||
// must have the same channel ID, or wildcard (0) is used.
|
||||
ChannelId channelId = new ChannelId(SPEED_SENSOR_ID & 0xFFFF,
|
||||
CHANNEL_SPEED_DEVICE_TYPE, CHANNEL_SPEED_TRANSMISSION_TYPE);
|
||||
|
||||
try {
|
||||
// Setting the channel event handler so that we can receive messages from ANT
|
||||
mAntChannel.setChannelEventHandler(mChannelEventCallback);
|
||||
|
||||
// Performs channel assignment by assigning the type to the channel. Additional
|
||||
// features (such as, background scanning and frequency agility) can be enabled
|
||||
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
|
||||
mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
|
||||
|
||||
/*
|
||||
* Configures the channel ID, messaging period and rf frequency after assigning,
|
||||
* then opening the channel.
|
||||
*
|
||||
* For any additional ANT features such as proximity search or background scanning, refer to
|
||||
* the ANT Protocol Doc found at:
|
||||
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
|
||||
*/
|
||||
mAntChannel.setChannelId(channelId);
|
||||
mAntChannel.setPeriod(CHANNEL_SPEED_PERIOD);
|
||||
mAntChannel.setRfFrequency(CHANNEL_SPEED_FREQUENCY);
|
||||
mAntChannel.open();
|
||||
mIsOpen = true;
|
||||
|
||||
Log.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
} catch (AntCommandFailedException e) {
|
||||
// This will release, and therefore unassign if required
|
||||
channelError("Open failed", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No channel available");
|
||||
}
|
||||
|
||||
return mIsOpen;
|
||||
}
|
||||
|
||||
void channelError(RemoteException e) {
|
||||
String logString = "Remote service communication failed.";
|
||||
|
||||
Log.e(TAG, logString);
|
||||
}
|
||||
|
||||
void channelError(String error, AntCommandFailedException e) {
|
||||
StringBuilder logString;
|
||||
|
||||
if (e.getResponseMessage() != null) {
|
||||
String initiatingMessageId = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getInitiatingMessageId());
|
||||
String rawResponseCode = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getRawResponseCode());
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(initiatingMessageId)
|
||||
.append(" failed with code ")
|
||||
.append(rawResponseCode);
|
||||
} else {
|
||||
String attemptedMessageId = "0x" + Integer.toHexString(
|
||||
e.getAttemptedMessageType().getMessageId());
|
||||
String failureReason = e.getFailureReason().toString();
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(attemptedMessageId)
|
||||
.append(" failed with reason ")
|
||||
.append(failureReason);
|
||||
}
|
||||
|
||||
Log.e(TAG, logString.toString());
|
||||
|
||||
mAntChannel.release();
|
||||
|
||||
Log.e(TAG, "ANT Command Failed");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// TODO kill all our resources
|
||||
if (null != mAntChannel) {
|
||||
mIsOpen = false;
|
||||
|
||||
// Releasing the channel to make it available for others.
|
||||
// After releasing, the AntChannel instance cannot be reused.
|
||||
mAntChannel.release();
|
||||
mAntChannel = null;
|
||||
}
|
||||
|
||||
Log.e(TAG, "Channel Closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Channel Event Handler Interface so that messages can be
|
||||
* received and channel death events can be handled.
|
||||
*/
|
||||
public class ChannelEventCallback implements IAntChannelEventHandler {
|
||||
long lastTime = 0;
|
||||
double totalWay = 0.0;
|
||||
double totalRotations = 0.0;
|
||||
long lastSpeedEventTime = 0;
|
||||
long lastCadenceEventTime = 0;
|
||||
long elapsedMillis = 0;
|
||||
int rotations;
|
||||
int rev;
|
||||
double wheel = 0.1;
|
||||
Timer carousalTimer = null;
|
||||
|
||||
@Override
|
||||
public void onChannelDeath() {
|
||||
// Display channel death message when channel dies
|
||||
Log.e(TAG, "Channel Death");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
|
||||
Log.d(TAG, "Rx: " + antParcel);
|
||||
Log.d(TAG, "Message Type: " + messageType);
|
||||
|
||||
if(carousalTimer == null) {
|
||||
carousalTimer = new Timer(); // At this line a new Thread will be created
|
||||
carousalTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Tx Unsollicited");
|
||||
long realtimeMillis = SystemClock.elapsedRealtime();
|
||||
double speedM_s = speed / 3.6;
|
||||
long deltaTime = (realtimeMillis - lastTime);
|
||||
lastTime = realtimeMillis;
|
||||
|
||||
byte[] payload = new byte[8];
|
||||
|
||||
payload[0] = (byte) 0x01;
|
||||
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
|
||||
payload[2] = (byte) ((lastTime % 256000) / 1000);
|
||||
payload[3] = (byte) 0x00;
|
||||
payload[4] = (byte) speedM_s;
|
||||
payload[5] = (byte) ((speedM_s - (double)((int)speedM_s)) / (1.0/256.0));
|
||||
payload[6] = (byte) stride_count++; // bad but it works on zwift
|
||||
payload[7] = (byte) ((double)deltaTime * 0.03125);
|
||||
|
||||
if (mIsOpen) {
|
||||
try {
|
||||
// Setting the data to be broadcast on the next channel period
|
||||
mAntChannel.setBroadcastData(payload);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 250); // delay
|
||||
}
|
||||
|
||||
// Switching on message type to handle different types of messages
|
||||
switch (messageType) {
|
||||
// If data message, construct from parcel and update channel data
|
||||
case BROADCAST_DATA:
|
||||
// Rx Data
|
||||
//updateData(new BroadcastDataMessage(antParcel).getPayload());
|
||||
break;
|
||||
case ACKNOWLEDGED_DATA:
|
||||
// Rx Data
|
||||
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
|
||||
break;
|
||||
case CHANNEL_EVENT:
|
||||
// Constructing channel event message from parcel
|
||||
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
|
||||
EventCode code = eventMessage.getEventCode();
|
||||
Log.d(TAG, "Event Code: " + code);
|
||||
|
||||
// Switching on event code to handle the different types of channel events
|
||||
switch (code) {
|
||||
case TX:
|
||||
long realtimeMillis = SystemClock.elapsedRealtime();
|
||||
double speedM_s = speed / 3.6;
|
||||
long deltaTime = (realtimeMillis - lastTime);
|
||||
// in case the treadmill doesn't provide cadence, I have to force it. ANT+ requires cadence
|
||||
lastTime = realtimeMillis;
|
||||
|
||||
byte[] payload = new byte[8];
|
||||
|
||||
payload[0] = (byte) 0x01;
|
||||
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
|
||||
payload[2] = (byte) ((lastTime % 256000) / 1000);
|
||||
payload[3] = (byte) 0x00;
|
||||
payload[4] = (byte) speedM_s;
|
||||
payload[5] = (byte) ((speedM_s - (double)((int)speedM_s)) / (1.0/256.0));
|
||||
payload[6] = (byte) stride_count;
|
||||
payload[7] = (byte) ((double)deltaTime * 0.03125);
|
||||
|
||||
if (mIsOpen) {
|
||||
try {
|
||||
// Setting the data to be broadcast on the next channel period
|
||||
mAntChannel.setBroadcastData(payload);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLLISION:
|
||||
break;
|
||||
case RX_SEARCH_TIMEOUT:
|
||||
// TODO May want to keep searching
|
||||
Log.e(TAG, "No Device Found");
|
||||
break;
|
||||
case CHANNEL_CLOSED:
|
||||
case RX_FAIL:
|
||||
case RX_FAIL_GO_TO_SEARCH:
|
||||
case TRANSFER_RX_FAILED:
|
||||
case TRANSFER_TX_COMPLETED:
|
||||
case TRANSFER_TX_FAILED:
|
||||
case TRANSFER_TX_START:
|
||||
case UNKNOWN:
|
||||
// TODO More complex communication will need to handle these events
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ANT_VERSION:
|
||||
case BURST_TRANSFER_DATA:
|
||||
case CAPABILITIES:
|
||||
case CHANNEL_ID:
|
||||
case CHANNEL_RESPONSE:
|
||||
case CHANNEL_STATUS:
|
||||
case SERIAL_NUMBER:
|
||||
case OTHER:
|
||||
// TODO More complex communication will need to handle these message types
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/android/src/WearableController.java
Normal file
42
src/android/src/WearableController.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class WearableController {
|
||||
static Context _context;
|
||||
static Intent _intent = null;
|
||||
|
||||
public static void start(Context context) {
|
||||
_context = context;
|
||||
|
||||
if(_intent == null)
|
||||
_intent = new Intent(context, WearableMessageListenerService.class);
|
||||
// FloatingWindowGFG service is started
|
||||
context.startService(_intent);
|
||||
Log.v("WearableController", "started");
|
||||
}
|
||||
|
||||
public static int getHeart() {
|
||||
return WearableMessageListenerService.getHeart();
|
||||
}
|
||||
}
|
||||
131
src/android/src/WearableMessageListenerService.java
Normal file
131
src/android/src/WearableMessageListenerService.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.android.gms.wearable.MessageClient;
|
||||
import com.google.android.gms.wearable.DataClient;
|
||||
import com.google.android.gms.wearable.DataEvent;
|
||||
import com.google.android.gms.wearable.DataEventBuffer;
|
||||
import com.google.android.gms.wearable.MessageEvent;
|
||||
import com.google.android.gms.wearable.Wearable;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.wearable.DataItemBuffer;
|
||||
import com.google.android.gms.wearable.DataMap;
|
||||
import android.util.Log;
|
||||
import android.os.Bundle;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class WearableMessageListenerService extends Service implements
|
||||
MessageClient.OnMessageReceivedListener, GoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener,DataClient.OnDataChangedListener {
|
||||
|
||||
private GoogleApiClient googleApiClient;
|
||||
private MessageClient mWearableClient;
|
||||
private String TAG = "WearableMessageListenerService";
|
||||
private static int heart_rate = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.v("WearableMessageListenerService","onCreate");
|
||||
}
|
||||
|
||||
public static int getHeart() {
|
||||
return heart_rate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// Your service logic here
|
||||
|
||||
googleApiClient = new GoogleApiClient.Builder(this)
|
||||
.addApi(Wearable.API)
|
||||
.addConnectionCallbacks (this)
|
||||
.addOnConnectionFailedListener(this)
|
||||
.build();
|
||||
|
||||
googleApiClient.connect();
|
||||
|
||||
// Register the MessageClient.OnMessageReceivedListener
|
||||
mWearableClient = Wearable.getMessageClient(this);
|
||||
mWearableClient.addListener(this);
|
||||
Wearable.getDataClient(this).addListener(this);
|
||||
|
||||
Log.v("WearableMessageListenerService","onStartCommand");
|
||||
|
||||
// Return START_STICKY to restart the service if it's killed by the system
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||
for (DataEvent event : dataEvents) {
|
||||
if (event.getType() == DataEvent.TYPE_DELETED) {
|
||||
Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
|
||||
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||
Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri() + " " + event.getDataItem().getUri().getPath());
|
||||
if(event.getDataItem().getUri().getPath().equals("/qz")) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DataItemBuffer result = Wearable.DataApi.getDataItems(googleApiClient).await();
|
||||
if (result.getStatus().isSuccess()) {
|
||||
if (result.getCount() == 1) {
|
||||
heart_rate = DataMap.fromByteArray(result.get(0).getData())
|
||||
.getInt("heart_rate", 0);
|
||||
} else {
|
||||
Log.e(TAG, "Unexpected number of DataItems found.\n"
|
||||
+ "\tExpected: 1\n"
|
||||
+ "\tActual: " + result.getCount());
|
||||
}
|
||||
} else if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "onHandleIntent: failed to get current alarm state");
|
||||
}
|
||||
Log.d(TAG, "Heart: " + heart_rate);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConnected(Bundle bundle) {
|
||||
Log.v("WearableMessageListenerService","onConnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int i) {
|
||||
Log.v("WearableMessageListenerService","onConnectionSuspended");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||
Log.v("WearableMessageListenerService","onConnectionFailed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(final MessageEvent messageEvent) {
|
||||
String path = messageEvent.getPath();
|
||||
byte[] data = messageEvent.getData();
|
||||
|
||||
// Handle the received message data here
|
||||
String messageData = new String(data); // Assuming it's a simple string message
|
||||
|
||||
Log.v("Wearable", path);
|
||||
Log.v("Wearable", messageData);
|
||||
|
||||
// You can then perform actions or update data in your service based on the received message
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// This service does not support binding
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,7 @@ void apexbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -159,7 +160,7 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
if (watts())
|
||||
KCal +=
|
||||
@@ -167,17 +168,17 @@ void apexbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
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()) {
|
||||
|
||||
@@ -142,6 +142,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid::HeartRate && newValue.length() > 1) {
|
||||
Heart = (uint8_t)newValue[1];
|
||||
@@ -191,7 +192,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0),
|
||||
0 /* not useful for elliptical*/);
|
||||
}
|
||||
index += 2;
|
||||
@@ -241,7 +242,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
@@ -288,7 +289,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -301,7 +302,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(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 {
|
||||
@@ -326,7 +327,7 @@ void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
|
||||
@@ -116,6 +116,7 @@ void bike::clearStats() {
|
||||
Speed.clear(false);
|
||||
KCal.clear(true);
|
||||
Distance.clear(true);
|
||||
Distance1s.clear(true);
|
||||
Heart.clear(false);
|
||||
m_jouls.clear(true);
|
||||
elevationAcc = 0;
|
||||
@@ -140,6 +141,7 @@ void bike::setPaused(bool p) {
|
||||
Speed.setPaused(p);
|
||||
KCal.setPaused(p);
|
||||
Distance.setPaused(p);
|
||||
Distance1s.setPaused(p);
|
||||
Heart.setPaused(p);
|
||||
m_jouls.setPaused(p);
|
||||
m_watt.setPaused(p);
|
||||
@@ -161,6 +163,7 @@ void bike::setLap() {
|
||||
Speed.setLap(false);
|
||||
KCal.setLap(true);
|
||||
Distance.setLap(true);
|
||||
Distance1s.setLap(true);
|
||||
Heart.setLap(false);
|
||||
m_jouls.setLap(true);
|
||||
m_watt.setLap(false);
|
||||
@@ -288,6 +291,8 @@ uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
|
||||
} else {
|
||||
watt = 0;
|
||||
}
|
||||
} else {
|
||||
watt = currentCadence().value() * 1.2; // random value cloned from Zwift when HR is not available
|
||||
}
|
||||
return watt;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class bike : public bluetoothdevice {
|
||||
public:
|
||||
bike();
|
||||
|
||||
virtualbike * VirtualBike();
|
||||
virtualbike *VirtualBike();
|
||||
|
||||
metric lastRequestedResistance();
|
||||
metric lastRequestedPelotonResistance();
|
||||
@@ -36,8 +36,8 @@ class bike : public bluetoothdevice {
|
||||
uint8_t metrics_override_heartrate() override;
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
void setSpeedLimit(double speed) {m_speedLimit = speed;}
|
||||
double speedLimit() {return m_speedLimit;}
|
||||
void setSpeedLimit(double speed) { m_speedLimit = speed; }
|
||||
double speedLimit() { return m_speedLimit; }
|
||||
|
||||
/**
|
||||
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
|
||||
@@ -46,6 +46,7 @@ class bike : public bluetoothdevice {
|
||||
*/
|
||||
metric currentSteeringAngle() { return m_steeringAngle; }
|
||||
virtual bool inclinationAvailableByHardware();
|
||||
bool ergModeSupportedAvailableByHardware() { return ergModeSupported; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void changeResistance(resistance_t res) override;
|
||||
|
||||
@@ -192,6 +192,7 @@ void bkoolbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -253,8 +254,8 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
if (cadence >= 0 && cadence < 255) {
|
||||
Cadence = cadence;
|
||||
}
|
||||
lastGoodCadence = QDateTime::currentDateTime();
|
||||
} else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
|
||||
lastGoodCadence = now;
|
||||
} else if (lastGoodCadence.msecsTo(now) > 2000) {
|
||||
Cadence = 0;
|
||||
}
|
||||
|
||||
@@ -266,7 +267,7 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
.toDouble();
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
// Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
// (uint16_t)((uint8_t)newValue.at(index)))); debug("Current Resistance: " +
|
||||
@@ -301,9 +302,9 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
emit debug(QStringLiteral("Current CrankRevsRead: ") + QString::number(CrankRevsRead));
|
||||
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
|
||||
@@ -384,8 +385,8 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
if (cadence >= 0) {
|
||||
Cadence = cadence;
|
||||
}
|
||||
lastGoodCadence = QDateTime::currentDateTime();
|
||||
} else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
|
||||
lastGoodCadence = now;
|
||||
} else if (lastGoodCadence.msecsTo(now) > 2000) {
|
||||
Cadence = 0;
|
||||
}
|
||||
}
|
||||
@@ -403,12 +404,12 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
// if we change this, also change the wattsFromResistance function. We can create a standard function in
|
||||
@@ -456,7 +457,7 @@ void bkoolbike::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -109,8 +109,9 @@ void bluetooth::finished() {
|
||||
QSettings settings;
|
||||
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();
|
||||
// wifi devices on windows
|
||||
if (!nordictrack_2950_ip.isEmpty()) {
|
||||
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty()) {
|
||||
// faking a bluetooth device
|
||||
qDebug() << "faking a bluetooth device for nordictrack_2950_ip";
|
||||
deviceDiscovered(QBluetoothDeviceInfo());
|
||||
@@ -398,6 +399,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
|
||||
bool pafers_treadmill = settings.value(QZSettings::pafers_treadmill, QZSettings::default_pafers_treadmill).toBool();
|
||||
QString proformtdf4ip = settings.value(QZSettings::proformtdf4ip, QZSettings::default_proformtdf4ip).toString();
|
||||
QString proformtdf1ip = settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString();
|
||||
QString proformtreadmillip =
|
||||
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
|
||||
QString nordictrack_2950_ip =
|
||||
@@ -422,6 +424,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
bool sole_inclination =
|
||||
settings.value(QZSettings::sole_treadmill_inclination, QZSettings::default_sole_treadmill_inclination).toBool();
|
||||
QString ftms_rower = settings.value(QZSettings::ftms_rower, QZSettings::default_ftms_rower).toString();
|
||||
QString ftms_bike = settings.value(QZSettings::ftms_bike, QZSettings::default_ftms_bike).toString();
|
||||
QString ftms_treadmill = settings.value(QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill).toString();
|
||||
bool saris_trainer = settings.value(QZSettings::saris_trainer, QZSettings::default_saris_trainer).toBool();
|
||||
bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
|
||||
|
||||
if (!heartRateBeltFound) {
|
||||
|
||||
@@ -651,6 +657,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(proformWifiBike);
|
||||
} else if (!proformtdf1ip.isEmpty() && !proformTelnetBike) {
|
||||
this->stopDiscovery();
|
||||
proformTelnetBike =
|
||||
new proformtelnetbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(proformTelnetBike, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(proformTelnetBike, &proformtelnetbike::debug, this, &bluetooth::debug);
|
||||
proformTelnetBike->deviceDiscovered(b);
|
||||
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(proformTelnetBike);
|
||||
#ifndef Q_OS_IOS
|
||||
} else if (!computrainerSerialPort.isEmpty() && !computrainerBike) {
|
||||
this->stopDiscovery();
|
||||
@@ -711,7 +732,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(nordictrackifitadbTreadmill);
|
||||
} else if (!tdf_10_ip.isEmpty() && !nordictrackifitadbBike) {
|
||||
this->stopDiscovery();
|
||||
nordictrackifitadbBike = new nordictrackifitadbbike(noWriteResistance, noHeartService);
|
||||
nordictrackifitadbBike = new nordictrackifitadbbike(noWriteResistance, noHeartService,
|
||||
bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(nordictrackifitadbBike, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
@@ -819,7 +841,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(domyosElliptical);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) && !ypooElliptical && filter) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
|
||||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
ypooElliptical =
|
||||
@@ -835,7 +858,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(ypooElliptical);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("NAUTILUS E"))) &&
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("NAUTILUS E")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("NAUTILUS M"))) &&
|
||||
!nautilusElliptical && // NAUTILUS E616
|
||||
filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -998,7 +1022,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-HDSC-X21C")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-HDSY-X21C")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-NACH-X21C")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-X21C"))) &&
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-X21C")) ||
|
||||
// KingSmith Walking Pad G1
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-NGCH-G1C"))) &&
|
||||
!kingsmithR2Treadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1025,6 +1051,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("WALKINGPAD")) ||
|
||||
!b.name().toUpper().compare(QStringLiteral("RE")) || // just "RE"
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-H")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-BLC")) || // Walkingpad C2 #1672
|
||||
b.name().toUpper().startsWith(
|
||||
QStringLiteral("KS-BLR"))) && // Treadmill KingSmith WalkingPad R2 Pro KS-HCR1AA
|
||||
!kingsmithR1ProTreadmill &&
|
||||
@@ -1089,7 +1116,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive())
|
||||
emit searchingStop();
|
||||
this->signalBluetoothDeviceConnected(trueTreadmill);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("F80")) ||
|
||||
} else if (((b.name().toUpper().startsWith(QStringLiteral("F80")) && sole_inclination) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("F65")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TT8")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("F63")) ||
|
||||
@@ -1160,16 +1187,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("CT800")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("TRX4500")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("MATRIXTF50")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("T01_")) || // FTMS
|
||||
(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("MOBVOI TM")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) &&
|
||||
!horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1198,7 +1229,11 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("MYRUN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MERACH-U3")) // FTMS
|
||||
) &&
|
||||
!technogymmyrunTreadmill && filter) {
|
||||
!technogymmyrunTreadmill
|
||||
#ifndef Q_OS_IOS
|
||||
&& !technogymmyrunrfcommTreadmill
|
||||
#endif
|
||||
&& filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
bool technogym_myrun_treadmill_experimental =
|
||||
@@ -1258,9 +1293,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(technogymmyrunrfcommTreadmill);
|
||||
}
|
||||
#endif
|
||||
} else if ((b.name().toUpper().startsWith("TACX NEO") ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TACX FLOW")) ||
|
||||
} else if ((b.name().toUpper().startsWith("TACX ") ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("THINK X")) ||
|
||||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
|
||||
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
|
||||
!tacxneo2Bike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1296,28 +1331,33 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
npeCableBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(npeCableBike);
|
||||
} else if (((b.name().startsWith("FS-") && hammerRacerS) ||
|
||||
(b.name().toUpper().startsWith("DHZ-")) || // JK fitness 577
|
||||
(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("DS25-")) || // Bodytone DS25
|
||||
(b.name().toUpper().startsWith("DI") && b.name().length() == 2) || // Elite smart trainer #1682
|
||||
(b.name().toUpper().startsWith("DHZ-")) || // JK fitness 577
|
||||
(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("DS25-")) || // Bodytone DS25
|
||||
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
|
||||
(b.name().toUpper().startsWith("ZWIFT HUB")) || (b.name().toUpper().startsWith("MAGNUS ")) ||
|
||||
(b.name().toUpper().startsWith("HAMMER ")) || // HAMMER 64123
|
||||
(b.name().toUpper().startsWith("HAMMER ") && !power_as_bike && !saris_trainer) || // HAMMER 64123
|
||||
(b.name().toUpper().startsWith("FLXCY-")) || // Pro FlexBike
|
||||
(b.name().toUpper().startsWith("QB-WC01")) || // Nexgim QB-C01 smart bike
|
||||
(b.name().toUpper().startsWith("XBR55")) || // Sprint XBR555
|
||||
(b.name().toUpper().startsWith("ECHO_BIKE_")) || // Rogue echo bike V3.0
|
||||
(b.name().toUpper().startsWith("EW-JS-")) || // EW-JS-4990
|
||||
(b.name().toUpper().startsWith("DT-") && b.name().length() >= 14) || // SOLE SB700
|
||||
(b.name().toUpper().startsWith("URSB") && b.name().length() == 7) || // URSB005
|
||||
(b.name().toUpper().startsWith("DBF") && b.name().length() == 6) || // DBF135
|
||||
(b.name().toUpper().startsWith(ftmsAccessoryName.toUpper()) &&
|
||||
settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton)
|
||||
.toBool()) || // ss2k on a peloton bike
|
||||
(b.name().toUpper().startsWith("KICKR CORE")) ||
|
||||
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
|
||||
(b.name().toUpper().startsWith("B94")) || (b.name().toUpper().startsWith("STAGES BIKE")) ||
|
||||
(b.name().toUpper().startsWith("SUITO")) || (b.name().toUpper().startsWith("D2RIDE")) ||
|
||||
(b.name().toUpper().startsWith("DIRETO XR")) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
(b.name().toUpper().startsWith("DIRETO XR")) ||
|
||||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
!ftmsBike && !snodeBike && !fitPlusBike && !stagesBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1330,6 +1370,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(ftmsBike);
|
||||
} else if ((b.name().toUpper().startsWith("KICKR SNAP") || b.name().toUpper().startsWith("KICKR BIKE") ||
|
||||
b.name().toUpper().startsWith("KICKR ROLLR") ||
|
||||
(b.name().toUpper().startsWith("HAMMER ") && saris_trainer) ||
|
||||
(b.name().toUpper().startsWith("WAHOO KICKR"))) &&
|
||||
!wahooKickrSnapBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1358,6 +1399,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
horizonGr7Bike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(horizonGr7Bike);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
|
||||
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
|
||||
!stagesBike && !ftmsBike && filter) {
|
||||
@@ -1411,6 +1453,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-WLT")) || // KS-WLT-W1
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-ROWER")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SF-RW")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("DFIT-L-R")) ||
|
||||
!b.name().compare(ftms_rower, Qt::CaseInsensitive) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("PM5")) &&
|
||||
b.name().toUpper().endsWith(QStringLiteral("ROW")))) &&
|
||||
@@ -1429,6 +1472,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(ftmsRower);
|
||||
} 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("ECH-SD-SPT"))) &&
|
||||
!echelonStride && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1762,7 +1806,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// SLOT(inclinationChanged(double)));
|
||||
mcfBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(mcfBike);
|
||||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY"))) && !toorx && filter) {
|
||||
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
toorx = new toorxtreadmill();
|
||||
@@ -1831,6 +1876,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ICONSOLE+"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-RUNNING"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DKN RUN"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ADIDAS "))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK")))) &&
|
||||
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill &&
|
||||
filter) {
|
||||
@@ -1845,6 +1891,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
trxappgateusb->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(trxappgateusb);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("TUN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FITHIWAY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT HI WAY")) ||
|
||||
((b.name().startsWith(QStringLiteral("TOORX")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
|
||||
@@ -1915,6 +1963,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
skandikaWiriBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(skandikaWiriBike);
|
||||
} else if (((b.name().toUpper().startsWith("RQ") && b.name().length() == 5) ||
|
||||
(b.name().toUpper().startsWith("R-Q") && b.name().length() > 6) ||
|
||||
(b.name().toUpper().startsWith("SCH130")) || // not a renpho bike an FTMS one
|
||||
((b.name().startsWith(QStringLiteral("TOORX"))) && toorx_ftms && !toorx_ftms_treadmill)) &&
|
||||
!renphoBike && !snodeBike && !fitPlusBike && filter) {
|
||||
@@ -1967,13 +2016,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// connect(fitPlusBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
|
||||
fitPlusBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(fitPlusBike);
|
||||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !snode_bike && !fitplus_bike && !ftmsBike) ||
|
||||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && !horizonTreadmill && !snode_bike && !fitplus_bike && !ftmsBike && !iconsole_elliptical) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) || // FTMS
|
||||
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
|
||||
!b.name().contains('(') && !b.name().contains(')')) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("WINFITA"))) || // also FTMS
|
||||
(b.name().startsWith(QStringLiteral("BF70")))) &&
|
||||
!fitshowTreadmill && filter) {
|
||||
!fitshowTreadmill && !iconsole_elliptical && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
fitshowTreadmill = new fitshowtreadmill(this->pollDeviceTime, noConsole, noHeartService);
|
||||
@@ -2183,6 +2232,16 @@ void bluetooth::connectedAndDiscovered() {
|
||||
f->deviceDiscovered(b);
|
||||
wahookickrHeadWind.append(f);
|
||||
break;
|
||||
} else if (((b.name().toUpper().startsWith("ARIA")) && b.name().length() == 4) && !fitmetria_fanfit_isconnected(b.name())) {
|
||||
eliteariafan *f = new eliteariafan(this->device());
|
||||
|
||||
connect(f, &eliteariafan::debug, this, &bluetooth::debug);
|
||||
|
||||
connect(this->device(), SIGNAL(fanSpeedChanged(uint8_t)), f, SLOT(fanSpeedRequest(uint8_t)));
|
||||
|
||||
f->deviceDiscovered(b);
|
||||
eliteAriaFan.append(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2305,10 +2364,12 @@ void bluetooth::connectedAndDiscovered() {
|
||||
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
|
||||
"activity", "()Landroid/app/Activity;");
|
||||
KeepAwakeHelper::antObject(true)->callMethod<void>(
|
||||
"antStart", "(Landroid/app/Activity;ZZZ)V", activity.object<jobject>(),
|
||||
"antStart", "(Landroid/app/Activity;ZZZZ)V", activity.object<jobject>(),
|
||||
settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool(),
|
||||
settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool(),
|
||||
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool());
|
||||
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool(),
|
||||
device()->deviceType() == bluetoothdevice::TREADMILL ||
|
||||
device()->deviceType() == bluetoothdevice::ELLIPTICAL);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::android_notification, QZSettings::default_android_notification).toBool()) {
|
||||
@@ -2507,6 +2568,11 @@ void bluetooth::restart() {
|
||||
delete proformWifiBike;
|
||||
proformWifiBike = nullptr;
|
||||
}
|
||||
if (proformTelnetBike) {
|
||||
|
||||
delete proformTelnetBike;
|
||||
proformTelnetBike = nullptr;
|
||||
}
|
||||
if (proformWifiTreadmill) {
|
||||
|
||||
delete proformWifiTreadmill;
|
||||
@@ -2853,6 +2919,14 @@ void bluetooth::restart() {
|
||||
}
|
||||
wahookickrHeadWind.clear();
|
||||
}
|
||||
if (eliteAriaFan.length()) {
|
||||
|
||||
foreach (eliteariafan *f, eliteAriaFan) {
|
||||
delete f;
|
||||
f = nullptr;
|
||||
}
|
||||
eliteAriaFan.clear();
|
||||
}
|
||||
if (cadenceSensor) {
|
||||
|
||||
// heartRateBelt->disconnectBluetooth(); // to test
|
||||
@@ -2912,6 +2986,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return cscBike;
|
||||
} else if (proformWifiBike) {
|
||||
return proformWifiBike;
|
||||
} else if (proformTelnetBike) {
|
||||
return proformTelnetBike;
|
||||
} else if (proformWifiTreadmill) {
|
||||
return proformWifiTreadmill;
|
||||
} else if (nordictrackifitadbTreadmill) {
|
||||
@@ -3181,6 +3257,10 @@ bool bluetooth::fitmetria_fanfit_isconnected(QString name) {
|
||||
if (!name.compare(f->bluetoothDevice.name()))
|
||||
return true;
|
||||
}
|
||||
foreach (eliteariafan *f, eliteAriaFan) {
|
||||
if (!name.compare(f->bluetoothDevice.name()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#include "echelonconnectsport.h"
|
||||
#include "echelonrower.h"
|
||||
#include "eliteariafan.h"
|
||||
#include "eliterizer.h"
|
||||
#include "elitesterzosmart.h"
|
||||
#include "eslinkertreadmill.h"
|
||||
@@ -85,6 +86,7 @@
|
||||
#include "proformellipticaltrainer.h"
|
||||
#include "proformrower.h"
|
||||
#include "proformtreadmill.h"
|
||||
#include "proformtelnetbike.h"
|
||||
#include "proformwifibike.h"
|
||||
#include "proformwifitreadmill.h"
|
||||
#include "schwinn170bike.h"
|
||||
@@ -186,6 +188,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
pelotonbike *pelotonBike = nullptr;
|
||||
proformrower *proformRower = nullptr;
|
||||
proformbike *proformBike = nullptr;
|
||||
proformtelnetbike *proformTelnetBike = nullptr;
|
||||
proformwifibike *proformWifiBike = nullptr;
|
||||
proformwifitreadmill *proformWifiTreadmill = nullptr;
|
||||
proformelliptical *proformElliptical = nullptr;
|
||||
@@ -250,6 +253,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
faketreadmill *fakeTreadmill = nullptr;
|
||||
QList<fitmetria_fanfit *> fitmetriaFanfit;
|
||||
QList<wahookickrheadwind *> wahookickrHeadWind;
|
||||
QList<eliteariafan *> eliteAriaFan;
|
||||
QString filterDevice = QLatin1String("");
|
||||
|
||||
bool testResistance = false;
|
||||
|
||||
@@ -247,9 +247,15 @@ void bluetoothdevice::update_hr_from_external() {
|
||||
long appleWatchHeartRate = h.heartRate();
|
||||
h.setKcal(KCal.value());
|
||||
h.setDistance(Distance.value());
|
||||
h.setSpeed(Speed.value());
|
||||
h.setPower(m_watt.value());
|
||||
h.setCadence(Cadence.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
qDebug() << "Current Heart from Apple Watch: " << QString::number(appleWatchHeartRate);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
Heart = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/WearableController", "getHeart", "()I");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -261,6 +267,7 @@ void bluetoothdevice::clearStats() {
|
||||
Speed.clear(false);
|
||||
KCal.clear(true);
|
||||
Distance.clear(true);
|
||||
Distance1s.clear(true);
|
||||
Heart.clear(false);
|
||||
m_jouls.clear(true);
|
||||
elevationAcc = 0;
|
||||
@@ -278,6 +285,7 @@ void bluetoothdevice::setPaused(bool p) {
|
||||
Speed.setPaused(p);
|
||||
KCal.setPaused(p);
|
||||
Distance.setPaused(p);
|
||||
Distance1s.setPaused(p);
|
||||
Heart.setPaused(p);
|
||||
m_jouls.setPaused(p);
|
||||
m_watt.setPaused(p);
|
||||
@@ -293,6 +301,7 @@ void bluetoothdevice::setLap() {
|
||||
Speed.setLap(false);
|
||||
KCal.setLap(true);
|
||||
Distance.setLap(true);
|
||||
Distance1s.setLap(true);
|
||||
Heart.setLap(false);
|
||||
m_jouls.setLap(true);
|
||||
m_watt.setLap(false);
|
||||
|
||||
@@ -100,6 +100,9 @@ class bluetoothdevice : public QObject {
|
||||
* @return
|
||||
*/
|
||||
virtual double odometer();
|
||||
virtual metric currentDistance() {return Distance;}
|
||||
virtual metric currentDistance1s() {return Distance1s;}
|
||||
void addCurrentDistance1s(double distance) { Distance1s += distance; }
|
||||
|
||||
/**
|
||||
* @brief calories Gets a metric object to get and set the amount of energy expended.
|
||||
@@ -495,6 +498,7 @@ class bluetoothdevice : public QObject {
|
||||
* the length of belt traversed on a treadmill.
|
||||
*/
|
||||
metric Distance;
|
||||
metric Distance1s; // used to populate the distance on the FIT file. Since Strava is using the distance to graph it, it has to have 1s trigger.
|
||||
|
||||
/**
|
||||
* @brief FanSpeed The currently requested fan speed. Units: revolutions per second
|
||||
|
||||
@@ -6,18 +6,15 @@ CharacteristicNotifier2A53::CharacteristicNotifier2A53(bluetoothdevice *Bike, QO
|
||||
|
||||
int CharacteristicNotifier2A53::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
value.append(0x02); // total distance
|
||||
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
|
||||
uint32_t distance = Bike->odometer() * 10000.0;
|
||||
value.append((char)((speed & 0xFF)));
|
||||
value.append((char)((speed >> 8) & 0xFF));
|
||||
value.append((char)(Bike->currentCadence().value()));
|
||||
value.append((char)((distance & 0xFF)));
|
||||
value.append((char)((distance >> 8) & 0xFF));
|
||||
value.append((char)((distance >> 16) & 0xFF));
|
||||
value.append((char)((distance >> 24) & 0xFF));
|
||||
return CN_OK;
|
||||
} else
|
||||
return CN_INVALID;
|
||||
value.append(0x02); // total distance
|
||||
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
|
||||
uint32_t distance = Bike->odometer() * 10000.0;
|
||||
value.append((char)((speed & 0xFF)));
|
||||
value.append((char)((speed >> 8) & 0xFF));
|
||||
value.append((char)(Bike->currentCadence().value()));
|
||||
value.append((char)((distance & 0xFF)));
|
||||
value.append((char)((distance >> 8) & 0xFF));
|
||||
value.append((char)((distance >> 16) & 0xFF));
|
||||
value.append((char)((distance >> 24) & 0xFF));
|
||||
return CN_OK;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ int CharacteristicNotifier2ACC::notify(QByteArray &value) {
|
||||
value.append((char)0x14); // heart rate and elapsed time
|
||||
value.append((char)0x00);
|
||||
value.append((char)0x00);
|
||||
value.append((char)0x0C); // resistance and power target supported
|
||||
value.append((char)0x0F); // resistance, power, speed and inclination target supported
|
||||
value.append((char)0xE0); // indoor simulation, wheel and spin down supported
|
||||
value.append((char)0x00);
|
||||
value.append((char)0x00);
|
||||
|
||||
@@ -8,7 +8,7 @@ CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QO
|
||||
int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
value.append(0x08); // Inclination available
|
||||
value.append(0x0C); // Inclination available and distance for peloton
|
||||
value.append((char)0x01); // heart rate available
|
||||
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
|
||||
@@ -17,6 +17,17 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
QByteArray speedBytes;
|
||||
speedBytes.append(b);
|
||||
speedBytes.append(a);
|
||||
|
||||
uint16_t normalizeDistance = (uint16_t)qRound(Bike->odometer() * 1000);
|
||||
a = (normalizeDistance >> 16) & 0XFF;
|
||||
b = (normalizeDistance >> 8) & 0XFF;
|
||||
char c = normalizeDistance & 0XFF;
|
||||
QByteArray distanceBytes;
|
||||
distanceBytes.append(c);
|
||||
distanceBytes.append(b);
|
||||
distanceBytes.append(a);
|
||||
|
||||
|
||||
uint16_t normalizeIncline = 0;
|
||||
if (dt == bluetoothdevice::TREADMILL)
|
||||
normalizeIncline = (uint32_t)qRound(((treadmill *)Bike)->currentInclination().value() * 10);
|
||||
@@ -36,6 +47,8 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
rampBytes.append(a);
|
||||
|
||||
value.append(speedBytes); // Actual value.
|
||||
|
||||
value.append(distanceBytes); // Actual value.
|
||||
|
||||
value.append(inclineBytes); // incline
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "characteristicnotifier2ad2.h"
|
||||
#include "elliptical.h"
|
||||
#include "rower.h"
|
||||
#include "treadmill.h"
|
||||
#include <QSettings>
|
||||
|
||||
@@ -8,11 +9,17 @@ CharacteristicNotifier2AD2::CharacteristicNotifier2AD2(bluetoothdevice *Bike, QO
|
||||
|
||||
int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
|
||||
QSettings settings;
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
bool rowerAsABike = !virtual_device_rower && dt == bluetoothdevice::ROWING;
|
||||
|
||||
double normalizeWattage = Bike->wattsMetric().value();
|
||||
if (normalizeWattage < 0)
|
||||
normalizeWattage = 0;
|
||||
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
if (dt == bluetoothdevice::BIKE || rowerAsABike) {
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
|
||||
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
|
||||
value.append((char)0x02); // heart rate
|
||||
@@ -32,7 +39,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
value.append(char(Bike->currentHeart().value())); // Actual value.
|
||||
value.append((char)0); // Bkool FTMS protocol HRM offset 1280 fix
|
||||
return CN_OK;
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL || dt == bluetoothdevice::ROWING) {
|
||||
QSettings settings;
|
||||
bool double_cadence = settings.value(QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double).toBool();
|
||||
double cadence_multiplier = 2.0;
|
||||
@@ -50,6 +57,8 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
cadence = ((elliptical *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::TREADMILL)
|
||||
cadence = ((treadmill *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::ROWING)
|
||||
cadence = ((rower *)Bike)->currentCadence().value();
|
||||
|
||||
value.append((char)((uint16_t)(cadence * cadence_multiplier) & 0xFF)); // cadence
|
||||
value.append((char)(((uint16_t)(cadence * cadence_multiplier) >> 8) & 0xFF)); // cadence
|
||||
|
||||
@@ -126,6 +126,7 @@ void chronobike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void chronobike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -153,7 +154,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
if (watts())
|
||||
KCal +=
|
||||
@@ -161,10 +162,10 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
double ac = 0.01243107769;
|
||||
double bc = 1.145964912;
|
||||
@@ -190,7 +191,7 @@ void chronobike::characteristicChanged(const QLowEnergyCharacteristic &character
|
||||
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())
|
||||
|
||||
@@ -131,6 +131,7 @@ void concept2skierg::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
@@ -172,7 +173,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
break;
|
||||
case 0x32:
|
||||
qDebug() << "32";
|
||||
if (newValue.length() >= 20) {
|
||||
if (newValue.length() >= 19) {
|
||||
// 0.001 m/s
|
||||
uint16_t speed_ms = (((uint16_t)((uint16_t)newValue.at(5)) << 8) | (uint16_t)((uint8_t)newValue.at(4)));
|
||||
uint8_t stroke_rate = newValue.at(6);
|
||||
@@ -238,7 +239,7 @@ void concept2skierg::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
update_hr_from_external();
|
||||
|
||||
@@ -182,61 +182,7 @@ int csaferowerThread::openPort() {
|
||||
|
||||
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
|
||||
#else
|
||||
// WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE
|
||||
|
||||
COMMTIMEOUTS timeouts; // timeout settings on serial ports
|
||||
|
||||
// if deviceFilename references a port above COM9
|
||||
// then we need to open "\\.\COMX" not "COMX"
|
||||
QString portSpec;
|
||||
int portnum = deviceFilename.midRef(3).toString().toInt();
|
||||
if (portnum < 10)
|
||||
portSpec = deviceFilename;
|
||||
else
|
||||
portSpec = "\\\\.\\" + deviceFilename;
|
||||
wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough?
|
||||
MultiByteToWideChar(CP_ACP, 0, portSpec.toLatin1(), -1, (LPWSTR)deviceFilenameW, sizeof(deviceFilenameW));
|
||||
|
||||
// win32 commport API
|
||||
devicePort = CreateFile(deviceFilenameW, GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if (devicePort == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
if (GetCommState(devicePort, &deviceSettings) == false)
|
||||
return -1;
|
||||
|
||||
// so we've opened the comm port lets set it up for
|
||||
deviceSettings.BaudRate = CBR_2400;
|
||||
deviceSettings.fParity = NOPARITY;
|
||||
deviceSettings.ByteSize = 8;
|
||||
deviceSettings.StopBits = ONESTOPBIT;
|
||||
deviceSettings.XonChar = 11;
|
||||
deviceSettings.XoffChar = 13;
|
||||
deviceSettings.EofChar = 0x0;
|
||||
deviceSettings.ErrorChar = 0x0;
|
||||
deviceSettings.EvtChar = 0x0;
|
||||
deviceSettings.fBinary = true;
|
||||
deviceSettings.fOutX = 0;
|
||||
deviceSettings.fInX = 0;
|
||||
deviceSettings.XonLim = 0;
|
||||
deviceSettings.XoffLim = 0;
|
||||
deviceSettings.fRtsControl = RTS_CONTROL_ENABLE;
|
||||
deviceSettings.fDtrControl = DTR_CONTROL_ENABLE;
|
||||
deviceSettings.fOutxCtsFlow = FALSE; // TRUE;
|
||||
|
||||
if (SetCommState(devicePort, &deviceSettings) == false) {
|
||||
CloseHandle(devicePort);
|
||||
return -1;
|
||||
}
|
||||
|
||||
timeouts.ReadIntervalTimeout = 0;
|
||||
timeouts.ReadTotalTimeoutConstant = 1000;
|
||||
timeouts.ReadTotalTimeoutMultiplier = 50;
|
||||
timeouts.WriteTotalTimeoutConstant = 2000;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
SetCommTimeouts(devicePort, &timeouts);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -391,17 +337,19 @@ void csaferower::update() {
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
if (!virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,9 @@
|
||||
#include <QThread>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windef.h>
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winbase.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <winbase.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
#include <termios.h> // unix!!
|
||||
|
||||
@@ -89,6 +89,15 @@ void cscbike::update() {
|
||||
/*initDone*/) {
|
||||
update_metrics(true, watts());
|
||||
|
||||
if(lastGoodCadence.secsTo(QDateTime::currentDateTime()) > 5 && !charNotified) {
|
||||
readMethod = true;
|
||||
qDebug() << "no cadence for 5 secs, switching to reading method";
|
||||
}
|
||||
|
||||
if(readMethod && cadenceService) {
|
||||
cadenceService->readCharacteristic(cadenceChar);
|
||||
}
|
||||
|
||||
// updating the treadmill console every second
|
||||
if (sec1Update++ == (500 / refresh->interval())) {
|
||||
sec1Update = 0;
|
||||
@@ -129,6 +138,7 @@ void cscbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -141,6 +151,8 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
double _WheelRevs = 0;
|
||||
uint8_t battery = 0;
|
||||
|
||||
charNotified = true;
|
||||
|
||||
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
|
||||
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) {
|
||||
@@ -200,8 +212,8 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
double cadence = ((CrankRevs - oldCrankRevs) / deltaT) * 1024 * 60;
|
||||
if (cadence >= 0 && cadence < 256)
|
||||
Cadence = cadence;
|
||||
lastGoodCadence = QDateTime::currentDateTime();
|
||||
} else if (lastGoodCadence.msecsTo(QDateTime::currentDateTime()) > 2000) {
|
||||
lastGoodCadence = now;
|
||||
} else if (lastGoodCadence.msecsTo(now) > 2000) {
|
||||
Cadence = 0;
|
||||
}
|
||||
emit cadenceChanged(Cadence.value());
|
||||
@@ -217,12 +229,12 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
double ac = 0.01243107769;
|
||||
@@ -255,7 +267,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
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()));
|
||||
|
||||
@@ -264,7 +276,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (!noVirtualDevice) {
|
||||
#ifdef Q_OS_IOS
|
||||
@@ -311,8 +323,16 @@ void cscbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
qDebug() << QStringLiteral("all services discovered!");
|
||||
|
||||
QBluetoothUuid CyclingSpeedAndCadence(QBluetoothUuid::CyclingSpeedAndCadence);
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
if (s->state() == QLowEnergyService::ServiceDiscovered) {
|
||||
|
||||
if(s->serviceUuid() == CyclingSpeedAndCadence) {
|
||||
qDebug() << "CyclingSpeedAndCadence found";
|
||||
cadenceService = s;
|
||||
}
|
||||
|
||||
// establish hook into notifications
|
||||
connect(s, &QLowEnergyService::characteristicChanged, this, &cscbike::characteristicChanged);
|
||||
connect(s, &QLowEnergyService::characteristicWritten, this, &cscbike::characteristicWritten);
|
||||
@@ -327,7 +347,11 @@ void cscbike::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();
|
||||
if(c.uuid() == QBluetoothUuid((quint16)0x2A5B)) {
|
||||
qDebug() << "CyclingSpeedAndCadence char found";
|
||||
cadenceChar = c;
|
||||
}
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << QStringLiteral("properties") << c.properties();
|
||||
auto descriptors_list = c.descriptors();
|
||||
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
|
||||
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
|
||||
@@ -424,6 +448,8 @@ void cscbike::characteristicWritten(const QLowEnergyCharacteristic &characterist
|
||||
|
||||
void cscbike::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' ');
|
||||
|
||||
characteristicChanged(characteristic, newValue);
|
||||
}
|
||||
|
||||
void cscbike::serviceScanDone(void) {
|
||||
|
||||
@@ -47,13 +47,15 @@ class cscbike : public bike {
|
||||
QTimer *refresh;
|
||||
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
// QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyService* cadenceService = nullptr;
|
||||
QLowEnergyCharacteristic cadenceChar;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
QDateTime lastGoodCadence = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
bool charNotified = false;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
@@ -62,6 +64,8 @@ class cscbike : public bike {
|
||||
bool noHeartService = false;
|
||||
bool noVirtualDevice = false;
|
||||
|
||||
bool readMethod = false;
|
||||
|
||||
uint16_t oldLastCrankEventTime = 0;
|
||||
uint16_t oldCrankRevs = 0;
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "dirconmanager.h"
|
||||
#include <QNetworkInterface>
|
||||
#include <QSettings>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#define DM_MACHINE_TYPE_BIKE 1
|
||||
#define DM_MACHINE_TYPE_TREADMILL 2
|
||||
@@ -166,7 +169,10 @@ DirconManager::DirconManager(bluetoothdevice *Bike, uint8_t bikeResistanceOffset
|
||||
QObject::connect(&bikeTimer, &QTimer::timeout, this, &DirconManager::bikeProvider);
|
||||
QString mac = getMacAddress();
|
||||
DM_MACHINE_OP(DM_MACHINE_INIT_OP, services, proc_services, type)
|
||||
bikeTimer.start(1000);
|
||||
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool())
|
||||
bikeTimer.start(100ms);
|
||||
else
|
||||
bikeTimer.start(1s);
|
||||
}
|
||||
|
||||
#define DM_CHAR_NOTIF_NOTIF1_OP(UUID, P1, P2, P3) \
|
||||
|
||||
@@ -283,6 +283,7 @@ void domyosbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -400,7 +401,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
@@ -433,7 +434,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
KCal = kcal;
|
||||
Distance = distance;
|
||||
|
||||
@@ -56,8 +56,7 @@ void domyosrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QSt
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -196,7 +195,8 @@ void domyosrower::update() {
|
||||
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
debug("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this);
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset,
|
||||
bikeResistanceGain);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this,
|
||||
&domyosrower::changeInclinationRequested);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &domyosrower::changeInclination);
|
||||
@@ -274,6 +274,7 @@ void domyosrower::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void domyosrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -360,15 +361,16 @@ void domyosrower::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
Speed = speed;
|
||||
KCal = kcal;
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
}
|
||||
|
||||
double domyosrower::GetSpeedFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
|
||||
double data = (double)convertedData / 10.0f;
|
||||
return data;
|
||||
if (convertedData > 65000 || convertedData == 0 || currentCadence().value() == 0)
|
||||
return 0;
|
||||
return (60.0 / (double)(convertedData)) * 30.0;
|
||||
}
|
||||
|
||||
double domyosrower::GetKcalFromPacket(const QByteArray &packet) {
|
||||
|
||||
@@ -158,7 +158,7 @@ void echelonconnectsport::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
|
||||
resistance_t echelonconnectsport::pelotonToBikeResistance(int pelotonResistance) {
|
||||
for (resistance_t i = 1; i < max_resistance; i++) {
|
||||
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) >= pelotonResistance) {
|
||||
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) > pelotonResistance) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -404,6 +404,8 @@ void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
@@ -418,12 +420,19 @@ void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
if (virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "bike.h"
|
||||
#include "virtualbike.h"
|
||||
#include "virtualrower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
|
||||
@@ -25,6 +25,7 @@ echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, uint8_t
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
speedRaw.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
@@ -236,10 +237,13 @@ void echelonrower::characteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())) / 60000;
|
||||
}
|
||||
// instant pace to km/h
|
||||
if (((uint8_t)lastPacket.at(14)) > 0)
|
||||
Speed = (60.0 / (double)((uint8_t)lastPacket.at(14))) * 30.0;
|
||||
else
|
||||
if ((((uint8_t)lastPacket.at(14)) > 0 || ((uint8_t)lastPacket.at(13)) > 0) && Cadence.value() > 0) {
|
||||
speedRaw = (60.0 / (double)(((uint16_t)lastPacket.at(13) << 8) | ((uint16_t)lastPacket.at(14)))) * 30.0;
|
||||
Speed = speedRaw.average5s();
|
||||
} else {
|
||||
Speed = 0;
|
||||
speedRaw = 0;
|
||||
}
|
||||
|
||||
StrokesLength =
|
||||
((Speed.value() / 60.0) * 1000.0) /
|
||||
|
||||
@@ -76,6 +76,7 @@ class echelonrower : public rower {
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
metric speedRaw;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
|
||||
@@ -237,6 +237,7 @@ void echelonstride::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
double echelonstride::minStepInclination() { return 1.0; }
|
||||
|
||||
void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
@@ -300,10 +301,10 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600.0) /
|
||||
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(now))));
|
||||
}
|
||||
|
||||
if ((uint8_t)newValue.at(1) == 0xD1 && newValue.length() > 11)
|
||||
@@ -334,7 +335,7 @@ void echelonstride::characteristicChanged(const QLowEnergyCharacteristic &charac
|
||||
if (m_control->error() != QLowEnergyController::NoError)
|
||||
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
|
||||
|
||||
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastTimeCharacteristicChanged = now;
|
||||
firstCharacteristicChanged = false;
|
||||
}
|
||||
|
||||
|
||||
334
src/eliteariafan.cpp
Normal file
334
src/eliteariafan.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "eliteariafan.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QEventLoop>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
|
||||
#endif
|
||||
|
||||
// this module on iOS is completely handled from the ObjectiveC module in order to test if it's more stable than the Qt Bluetooth Implementation (crash midride)
|
||||
|
||||
eliteariafan::eliteariafan(bluetoothdevice *parentDevice) {
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
this->parentDevice = parentDevice;
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
refresh = new QTimer(this);
|
||||
connect(refresh, &QTimer::timeout, this, &eliteariafan::update);
|
||||
refresh->start(1000ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
void eliteariafan::update() {
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
|
||||
uint8_t init1[] = {0x02, 0x00, 0x00, 0x3d, 0x00};
|
||||
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, init1, sizeof(init1), "init", false, true);
|
||||
|
||||
uint8_t init2[] = {0x05, 0x00};
|
||||
writeCharacteristic(gattWrite1Service, &gattWrite2Characteristic, init2, sizeof(init2), "init", false, true);
|
||||
|
||||
initDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
void eliteariafan::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
|
||||
}
|
||||
|
||||
void eliteariafan::disconnectBluetooth() {
|
||||
qDebug() << QStringLiteral("eliteariafan::disconnect") << m_control;
|
||||
|
||||
if (m_control) {
|
||||
m_control->disconnectFromDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void eliteariafan::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
emit packetReceived();
|
||||
|
||||
qDebug() << QStringLiteral(" << ") << newValue.toHex(' ');
|
||||
}
|
||||
|
||||
void eliteariafan::fanSpeedRequest(uint8_t speed) {
|
||||
QSettings settings;
|
||||
if (speed > 100)
|
||||
speed = 100;
|
||||
double max = settings.value(QZSettings::fitmetria_fanfit_max, QZSettings::default_fitmetria_fanfit_max).toDouble();
|
||||
double min = settings.value(QZSettings::fitmetria_fanfit_min, QZSettings::default_fitmetria_fanfit_min).toDouble();
|
||||
|
||||
uint16_t speed8 = (uint8_t)((double)speed * (max - min) / 100.0 + min);
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
iOS_EliteAriaFan->eliteAriaFan_fanSpeedRequest(speed8);
|
||||
#endif
|
||||
#else
|
||||
uint8_t init10[] = {0x03, 0x01, 0x0e};
|
||||
init10[2] = speed8;
|
||||
writeCharacteristic(gattWrite1Service, &gattWrite2Characteristic, init10, sizeof(init10),
|
||||
"forcing fan" + QString::number(speed));
|
||||
#endif
|
||||
}
|
||||
|
||||
void eliteariafan::writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic *writeChar,
|
||||
uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if (service == nullptr || writeChar->isValid() == false) {
|
||||
qDebug() << QStringLiteral(
|
||||
"eliteariafan trying to change the fan speed before the connection is estabilished");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
|
||||
// one for the resistance changed event (spontaneous), and one for the other ones.
|
||||
if (wait_for_response) {
|
||||
connect(service, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
} else {
|
||||
connect(service, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
}
|
||||
|
||||
if (service->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
|
||||
m_control->state() == QLowEnergyController::UnconnectedState) {
|
||||
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!writeChar->isValid()) {
|
||||
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (writeBuffer) {
|
||||
delete writeBuffer;
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
service->writeCharacteristic(*writeChar, *writeBuffer, QLowEnergyService::WriteWithoutResponse);
|
||||
|
||||
if (!disable_log) {
|
||||
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info;
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void eliteariafan::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattWriteCharacteristicId1(QStringLiteral("347b0012-7635-408b-8918-8ff3949ce592")); // handle 0x1d
|
||||
QBluetoothUuid _gattWriteCharacteristicId2(QStringLiteral("347b0040-7635-408b-8918-8ff3949ce592")); // handle 0x27
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
|
||||
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
|
||||
qDebug() << QStringLiteral("not all services discovered");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (state != QLowEnergyService::ServiceState::ServiceDiscovered) {
|
||||
qDebug() << QStringLiteral("ignoring this state");
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("all services discovered!");
|
||||
|
||||
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
|
||||
if (s->state() == QLowEnergyService::ServiceDiscovered) {
|
||||
// establish hook into notifications
|
||||
connect(s, &QLowEnergyService::characteristicChanged, this, &eliteariafan::characteristicChanged);
|
||||
connect(s, &QLowEnergyService::characteristicWritten, this, &eliteariafan::characteristicWritten);
|
||||
connect(
|
||||
s, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &eliteariafan::errorService);
|
||||
connect(s, &QLowEnergyService::descriptorWritten, this, &eliteariafan::descriptorWritten);
|
||||
|
||||
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
|
||||
|
||||
auto characteristics_list = s->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
|
||||
auto descriptors_list = c.descriptors();
|
||||
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
|
||||
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
|
||||
}
|
||||
|
||||
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
|
||||
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
} 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("notification subscribed!");
|
||||
} else if ((c.properties() & QLowEnergyCharacteristic::Indicate) ==
|
||||
QLowEnergyCharacteristic::Indicate) {
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x02);
|
||||
descriptor.append((char)0x00);
|
||||
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
|
||||
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
} 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!");
|
||||
} else if ((c.properties() & QLowEnergyCharacteristic::Read) == QLowEnergyCharacteristic::Read) {
|
||||
// s->readCharacteristic(c);
|
||||
// qDebug() << s->serviceUuid() << c.uuid() << "reading!";
|
||||
}
|
||||
|
||||
if (c.uuid() == _gattWriteCharacteristicId1) {
|
||||
qDebug() << QStringLiteral("_gattWriteCharacteristicId1 found");
|
||||
gattWrite1Characteristic = c;
|
||||
gattWrite1Service = s;
|
||||
} else if (c.uuid() == _gattWriteCharacteristicId2) {
|
||||
qDebug() << QStringLiteral("_gattWriteCharacteristicId2 found");
|
||||
gattWrite2Characteristic = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eliteariafan::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
|
||||
initRequest = true;
|
||||
}
|
||||
|
||||
void eliteariafan::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
|
||||
const QByteArray &newValue) {
|
||||
Q_UNUSED(characteristic);
|
||||
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
|
||||
}
|
||||
|
||||
void eliteariafan::serviceScanDone(void) {
|
||||
emit debug(QStringLiteral("serviceScanDone"));
|
||||
|
||||
initRequest = false;
|
||||
|
||||
auto services_list = m_control->services();
|
||||
for (const QBluetoothUuid &s : qAsConst(services_list)) {
|
||||
gattCommunicationChannelService.append(m_control->createServiceObject(s));
|
||||
if (gattCommunicationChannelService.constLast()) {
|
||||
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
|
||||
&eliteariafan::stateChanged);
|
||||
gattCommunicationChannelService.constLast()->discoverDetails();
|
||||
} else {
|
||||
m_control->disconnectFromDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eliteariafan::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
emit debug(QStringLiteral("eliteariafan::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString());
|
||||
}
|
||||
|
||||
void eliteariafan::error(QLowEnergyController::Error err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
|
||||
emit debug(QStringLiteral("eliteariafan::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString());
|
||||
}
|
||||
|
||||
void eliteariafan::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
QSettings settings;
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
iOS_EliteAriaFan = new lockscreen();
|
||||
iOS_EliteAriaFan->eliteAriaFan();
|
||||
return;
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &eliteariafan::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &eliteariafan::serviceScanDone);
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, &eliteariafan::error);
|
||||
connect(m_control, &QLowEnergyController::stateChanged, this, &eliteariafan::controllerStateChanged);
|
||||
|
||||
connect(m_control,
|
||||
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
|
||||
this, [this](QLowEnergyController::Error error) {
|
||||
Q_UNUSED(error);
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Cannot connect to remote device."));
|
||||
emit disconnected();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::connected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("Controller connected. Search services..."));
|
||||
m_control->discoverServices();
|
||||
});
|
||||
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
|
||||
Q_UNUSED(this);
|
||||
emit debug(QStringLiteral("LowEnergy controller disconnected"));
|
||||
emit disconnected();
|
||||
});
|
||||
|
||||
// Connect
|
||||
m_control->connectToDevice();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool eliteariafan::connected() {
|
||||
#ifdef Q_OS_IOS
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if (!m_control) {
|
||||
return false;
|
||||
}
|
||||
return m_control->state() == QLowEnergyController::DiscoveredState;
|
||||
}
|
||||
|
||||
void eliteariafan::controllerStateChanged(QLowEnergyController::ControllerState state) {
|
||||
#ifdef Q_OS_IOS
|
||||
return;
|
||||
#endif
|
||||
|
||||
qDebug() << QStringLiteral("controllerStateChanged") << state;
|
||||
if (state == QLowEnergyController::UnconnectedState && m_control) {
|
||||
qDebug() << QStringLiteral("trying to connect back again...");
|
||||
initRequest = false;
|
||||
initDone = false;
|
||||
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
90
src/eliteariafan.h
Normal file
90
src/eliteariafan.h
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef ELITEARIAFAN_H
|
||||
#define ELITEARIAFAN_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
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QTime>
|
||||
|
||||
#include "bluetoothdevice.h"
|
||||
|
||||
class eliteariafan : public bluetoothdevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
eliteariafan(bluetoothdevice *parentDevice);
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattNotify1Characteristic;
|
||||
QLowEnergyCharacteristic gattNotify2Characteristic;
|
||||
QLowEnergyCharacteristic gattWrite1Characteristic;
|
||||
QLowEnergyService *gattWrite1Service;
|
||||
QLowEnergyCharacteristic gattWrite2Characteristic;
|
||||
|
||||
void writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic *writeChar, uint8_t *data,
|
||||
uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
|
||||
bluetoothdevice *parentDevice = nullptr;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
QTimer *refresh;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen* iOS_EliteAriaFan = nullptr;
|
||||
#endif
|
||||
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
void packetReceived();
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
void disconnectBluetooth();
|
||||
void fanSpeedRequest(uint8_t value);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
|
||||
void stateChanged(QLowEnergyService::ServiceState state);
|
||||
void controllerStateChanged(QLowEnergyController::ControllerState state);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void serviceScanDone(void);
|
||||
void update();
|
||||
void error(QLowEnergyController::Error err);
|
||||
void errorService(QLowEnergyService::ServiceError);
|
||||
};
|
||||
|
||||
#endif // ELITEARIAFAN_H
|
||||
@@ -67,6 +67,7 @@ double elliptical::speedFromWatts() {
|
||||
if (wattsMetric().value() > 0) {
|
||||
double vwatts = ((9.8 * weight) * (currentInclination().value() / 100.0));
|
||||
speed = 210.0 / ((wattsMetric().value() - vwatts) / 75.0 / weight * 1000.0);
|
||||
speed = 60.0 / speed;
|
||||
}
|
||||
return speed;
|
||||
}
|
||||
@@ -107,6 +108,7 @@ void elliptical::clearStats() {
|
||||
Speed.clear(false);
|
||||
KCal.clear(true);
|
||||
Distance.clear(true);
|
||||
Distance1s.clear(true);
|
||||
Heart.clear(false);
|
||||
m_jouls.clear(true);
|
||||
elevationAcc = 0;
|
||||
@@ -123,6 +125,7 @@ void elliptical::setPaused(bool p) {
|
||||
Speed.setPaused(p);
|
||||
KCal.setPaused(p);
|
||||
Distance.setPaused(p);
|
||||
Distance1s.setPaused(p);
|
||||
Heart.setPaused(p);
|
||||
m_jouls.setPaused(p);
|
||||
m_watt.setPaused(p);
|
||||
@@ -137,6 +140,7 @@ void elliptical::setLap() {
|
||||
Speed.setLap(false);
|
||||
KCal.setLap(true);
|
||||
Distance.setLap(true);
|
||||
Distance1s.setLap(true);
|
||||
Heart.setLap(false);
|
||||
m_jouls.setLap(true);
|
||||
m_watt.setLap(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ class elliptical : public bluetoothdevice {
|
||||
void clearStats() override;
|
||||
void setPaused(bool p) override;
|
||||
void setLap() override;
|
||||
uint16_t watts();
|
||||
virtual uint16_t watts();
|
||||
double speedFromWatts();
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
|
||||
@@ -390,7 +390,10 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
}
|
||||
} else if (treadmill_type == COSTAWAY) {
|
||||
const double miles = 1.60934;
|
||||
Speed = newValue.at(3) * miles;
|
||||
if(newValue.at(3) == 0xFF)
|
||||
Speed = 0;
|
||||
else
|
||||
Speed = (double)((uint8_t)newValue.at(3)) / 10.0 * miles;
|
||||
Inclination = 0; // this treadmill doesn't have inclination
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ void faketreadmill::update() {
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
|
||||
|
||||
@@ -50,8 +51,8 @@ void faketreadmill::update() {
|
||||
cadenceFromAppleWatch();
|
||||
|
||||
Distance += ((Speed.value() / (double)3600.0) /
|
||||
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(now))));
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
if (!firstStateChanged && !this->hasVirtualDevice()) {
|
||||
|
||||
@@ -376,6 +376,7 @@ void fitplusbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -428,7 +429,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
index += 2;
|
||||
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
|
||||
@@ -525,7 +526,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -538,7 +539,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(index))));
|
||||
Heart = ((double)(((uint8_t)newValue.at(index))));
|
||||
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
|
||||
qDebug() << (QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
} else {
|
||||
@@ -597,7 +598,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
else*/
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
|
||||
} else if (newValue.length() == 13) {
|
||||
|
||||
@@ -625,7 +626,7 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
else
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
|
||||
if (watts())
|
||||
@@ -634,17 +635,17 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
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())
|
||||
@@ -918,6 +919,7 @@ void fitplusbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
bluetoothDevice = device;
|
||||
|
||||
if (device.name().startsWith(QStringLiteral("MRK-"))) {
|
||||
qDebug() << QStringLiteral("merach_MRK workaround enabled!");
|
||||
merach_MRK = true;
|
||||
}
|
||||
|
||||
@@ -987,50 +989,91 @@ uint16_t fitplusbike::wattsFromResistance(double resistance) {
|
||||
(double)(currentCadence().value()))) * exp(0.088 * (double)(currentResistance().value())) );*/
|
||||
|
||||
const double Epsilon = 4.94065645841247E-324;
|
||||
const int wattTableFirstDimension = 25;
|
||||
const int wattTableSecondDimension = 11;
|
||||
double wattTable[wattTableFirstDimension][wattTableSecondDimension] = {
|
||||
{Epsilon, 15.0, 15.0, 15.0, 20.0, 30.0, 32.0, 38.0, 44.0, 56.0, 66.0},
|
||||
{Epsilon, 15.0, 15.0, 15.0, 20.0, 30.0, 32.0, 38.0, 44.0, 56.0, 66.0},
|
||||
{Epsilon, 16.0, 16.0, 16.0, 22.0, 30.0, 38.0, 45.0, 53.0, 67.0, 79.0},
|
||||
{Epsilon, 18.0, 18.0, 18.0, 26.0, 34.0, 43.0, 52.0, 62.0, 78.0, 92.0},
|
||||
{Epsilon, 20.0, 20.0, 20.0, 28.0, 38.0, 48.0, 59.0, 71.0, 89.0, 105.0},
|
||||
{Epsilon, 23.0, 23.0, 23.0, 32.0, 43.0, 54.0, 66.0, 80.0, 100.0, 118.0},
|
||||
{Epsilon, 24.0, 24.0, 24.0, 35.0, 46.0, 59.0, 73.0, 89.0, 110.0, 130.0},
|
||||
{Epsilon, 26.0, 26.0, 26.0, 37.0, 51.0, 65.0, 81.0, 98.0, 122.0, 143.0},
|
||||
{Epsilon, 28.0, 28.0, 28.0, 41.0, 56.0, 71.0, 88.0, 107.0, 133.0, 156.0},
|
||||
{Epsilon, 30.0, 30.0, 30.0, 44.0, 60.0, 77.0, 96.0, 116.0, 144.0, 169.0},
|
||||
{Epsilon, 33.0, 33.0, 33.0, 47.0, 65.0, 83.0, 103.0, 125.0, 155.0, 182.0},
|
||||
{Epsilon, 34.0, 34.0, 34.0, 50.0, 70.0, 89.0, 110.0, 134.0, 166.0, 195.0},
|
||||
{Epsilon, 37.0, 37.0, 37.0, 54.0, 74.0, 94.0, 117.0, 143.0, 177.0, 208.0},
|
||||
{Epsilon, 38.0, 38.0, 38.0, 56.0, 78.0, 100.0, 125.0, 152.0, 188.0, 220.0},
|
||||
{Epsilon, 41.0, 41.0, 41.0, 60.0, 82.0, 106.0, 132.0, 161.0, 199.0, 233.0},
|
||||
{Epsilon, 43.0, 43.0, 43.0, 62.0, 86.0, 111.0, 139.0, 170.0, 209.0, 245.0},
|
||||
{Epsilon, 45.0, 45.0, 45.0, 66.0, 91.0, 117.0, 147.0, 180.0, 220.0, 259.0},
|
||||
{Epsilon, 48.0, 48.0, 48.0, 70.0, 96.0, 124.0, 155.0, 190.0, 232.0, 273.0},
|
||||
{Epsilon, 50.0, 50.0, 50.0, 73.0, 101.0, 130.0, 163.0, 200.0, 244.0, 287.0},
|
||||
{Epsilon, 52.0, 52.0, 52.0, 76.0, 106.0, 136.0, 171.0, 210.0, 256.0, 300.0},
|
||||
{Epsilon, 54.0, 54.0, 54.0, 80.0, 111.0, 143.0, 179.0, 220.0, 268.0, 314.0},
|
||||
{Epsilon, 57.0, 57.0, 57.0, 84.0, 116.0, 149.0, 187.0, 230.0, 279.0, 327.0},
|
||||
{Epsilon, 59.0, 59.0, 59.0, 87.0, 121.0, 155.0, 195.0, 240.0, 290.0, 340.0},
|
||||
{Epsilon, 62.0, 62.0, 62.0, 91.0, 126.0, 162.0, 203.0, 250.0, 302.0, 353.0},
|
||||
{Epsilon, 64.0, 64.0, 64.0, 94.0, 130.0, 168.0, 211.0, 260.0, 313.0, 366.0}};
|
||||
|
||||
int level = resistance;
|
||||
if (level < 0) {
|
||||
level = 0;
|
||||
if (merach_MRK) {
|
||||
const int wattTableFirstDimension = 17;
|
||||
const int wattTableSecondDimension = 11;
|
||||
double wattTable[wattTableFirstDimension][wattTableSecondDimension] = {
|
||||
{Epsilon, 14.3, 28.6, 42.9, 57.2, 71.5, 85.8, 100.1, 114.4, 128.7, 143.0},
|
||||
{Epsilon, 14.3, 28.6, 42.9, 57.2, 71.5, 85.8, 100.1, 114.4, 128.7, 143.0},
|
||||
{Epsilon, 16.4, 32.8, 49.2, 65.6, 82.0, 98.4, 114.8, 131.2, 147.6, 164.0},
|
||||
{Epsilon, 18.7, 37.4, 56.1, 74.8, 93.5, 112.2, 130.9, 149.6, 168.3, 187.0},
|
||||
{Epsilon, 21.0, 42.0, 63.0, 84.0, 105.0, 126.0, 147.0, 168.0, 189.0, 210.0},
|
||||
{Epsilon, 23.2, 46.4, 69.6, 92.8, 116.0, 139.2, 162.4, 185.6, 208.8, 232.0},
|
||||
{Epsilon, 25.3, 50.6, 75.9, 101.2, 126.5, 151.8, 177.1, 202.4, 227.7, 253.0},
|
||||
{Epsilon, 27.6, 55.2, 82.8, 110.4, 138.0, 165.6, 193.2, 220.8, 248.4, 276.0},
|
||||
{Epsilon, 30.0, 60.0, 90.0, 120.0, 150.0, 180.0, 210.0, 240.0, 270.0, 300.0},
|
||||
{Epsilon, 31.9, 63.8, 95.7, 127.6, 159.5, 191.4, 223.3, 255.2, 287.1, 319.0},
|
||||
{Epsilon, 34.2, 68.4, 102.6, 136.8, 171.0, 205.2, 239.4, 273.6, 307.8, 342.0},
|
||||
{Epsilon, 36.5, 73.0, 109.5, 146.0, 182.5, 219.0, 255.5, 292.0, 328.5, 365.0},
|
||||
{Epsilon, 38.5, 77.0, 115.5, 154.0, 192.5, 231.0, 269.5, 308.0, 346.5, 385.0},
|
||||
{Epsilon, 40.8, 81.6, 122.4, 163.2, 204.0, 244.8, 285.6, 326.4, 367.2, 408.0},
|
||||
{Epsilon, 43.1, 86.2, 129.3, 172.4, 215.5, 258.6, 301.7, 344.8, 387.9, 431.0},
|
||||
{Epsilon, 45.1, 90.2, 135.3, 180.4, 225.5, 270.6, 315.7, 360.8, 405.9, 451.0},
|
||||
{Epsilon, 47.2, 94.4, 141.6, 188.8, 236.0, 283.2, 330.4, 377.6, 424.8, 472.0}};
|
||||
|
||||
int level = resistance;
|
||||
if (level < 0) {
|
||||
level = 0;
|
||||
}
|
||||
if (level >= wattTableFirstDimension) {
|
||||
level = wattTableFirstDimension - 1;
|
||||
}
|
||||
double *watts_of_level = wattTable[level];
|
||||
int watt_setp = (Cadence.value() / 10.0);
|
||||
if (watt_setp >= 10) {
|
||||
return (((double)Cadence.value()) / 100.0) * watts_of_level[wattTableSecondDimension - 1];
|
||||
}
|
||||
double watt_base = watts_of_level[watt_setp];
|
||||
return (((watts_of_level[watt_setp + 1] - watt_base) / 10.0) * ((double)(((int)(Cadence.value())) % 10))) +
|
||||
watt_base;
|
||||
} else {
|
||||
// VirtuFit Etappe 2.0i Spinbike ERG Table #1526
|
||||
const int wattTableFirstDimension = 25;
|
||||
const int wattTableSecondDimension = 11;
|
||||
double wattTable[wattTableFirstDimension][wattTableSecondDimension] = {
|
||||
{Epsilon, 15.0, 15.0, 15.0, 20.0, 30.0, 32.0, 38.0, 44.0, 56.0, 66.0},
|
||||
{Epsilon, 15.0, 15.0, 15.0, 20.0, 30.0, 32.0, 38.0, 44.0, 56.0, 66.0},
|
||||
{Epsilon, 16.0, 16.0, 16.0, 22.0, 30.0, 38.0, 45.0, 53.0, 67.0, 79.0},
|
||||
{Epsilon, 18.0, 18.0, 18.0, 26.0, 34.0, 43.0, 52.0, 62.0, 78.0, 92.0},
|
||||
{Epsilon, 20.0, 20.0, 20.0, 28.0, 38.0, 48.0, 59.0, 71.0, 89.0, 105.0},
|
||||
{Epsilon, 23.0, 23.0, 23.0, 32.0, 43.0, 54.0, 66.0, 80.0, 100.0, 118.0},
|
||||
{Epsilon, 24.0, 24.0, 24.0, 35.0, 46.0, 59.0, 73.0, 89.0, 110.0, 130.0},
|
||||
{Epsilon, 26.0, 26.0, 26.0, 37.0, 51.0, 65.0, 81.0, 98.0, 122.0, 143.0},
|
||||
{Epsilon, 28.0, 28.0, 28.0, 41.0, 56.0, 71.0, 88.0, 107.0, 133.0, 156.0},
|
||||
{Epsilon, 30.0, 30.0, 30.0, 44.0, 60.0, 77.0, 96.0, 116.0, 144.0, 169.0},
|
||||
{Epsilon, 33.0, 33.0, 33.0, 47.0, 65.0, 83.0, 103.0, 125.0, 155.0, 182.0},
|
||||
{Epsilon, 34.0, 34.0, 34.0, 50.0, 70.0, 89.0, 110.0, 134.0, 166.0, 195.0},
|
||||
{Epsilon, 37.0, 37.0, 37.0, 54.0, 74.0, 94.0, 117.0, 143.0, 177.0, 208.0},
|
||||
{Epsilon, 38.0, 38.0, 38.0, 56.0, 78.0, 100.0, 125.0, 152.0, 188.0, 220.0},
|
||||
{Epsilon, 41.0, 41.0, 41.0, 60.0, 82.0, 106.0, 132.0, 161.0, 199.0, 233.0},
|
||||
{Epsilon, 43.0, 43.0, 43.0, 62.0, 86.0, 111.0, 139.0, 170.0, 209.0, 245.0},
|
||||
{Epsilon, 45.0, 45.0, 45.0, 66.0, 91.0, 117.0, 147.0, 180.0, 220.0, 259.0},
|
||||
{Epsilon, 48.0, 48.0, 48.0, 70.0, 96.0, 124.0, 155.0, 190.0, 232.0, 273.0},
|
||||
{Epsilon, 50.0, 50.0, 50.0, 73.0, 101.0, 130.0, 163.0, 200.0, 244.0, 287.0},
|
||||
{Epsilon, 52.0, 52.0, 52.0, 76.0, 106.0, 136.0, 171.0, 210.0, 256.0, 300.0},
|
||||
{Epsilon, 54.0, 54.0, 54.0, 80.0, 111.0, 143.0, 179.0, 220.0, 268.0, 314.0},
|
||||
{Epsilon, 57.0, 57.0, 57.0, 84.0, 116.0, 149.0, 187.0, 230.0, 279.0, 327.0},
|
||||
{Epsilon, 59.0, 59.0, 59.0, 87.0, 121.0, 155.0, 195.0, 240.0, 290.0, 340.0},
|
||||
{Epsilon, 62.0, 62.0, 62.0, 91.0, 126.0, 162.0, 203.0, 250.0, 302.0, 353.0},
|
||||
{Epsilon, 64.0, 64.0, 64.0, 94.0, 130.0, 168.0, 211.0, 260.0, 313.0, 366.0}};
|
||||
|
||||
int level = resistance;
|
||||
if (level < 0) {
|
||||
level = 0;
|
||||
}
|
||||
if (level >= wattTableFirstDimension) {
|
||||
level = wattTableFirstDimension - 1;
|
||||
}
|
||||
double *watts_of_level = wattTable[level];
|
||||
int watt_setp = (Cadence.value() / 10.0);
|
||||
if (watt_setp >= 10) {
|
||||
return (((double)Cadence.value()) / 100.0) * watts_of_level[wattTableSecondDimension - 1];
|
||||
}
|
||||
double watt_base = watts_of_level[watt_setp];
|
||||
return (((watts_of_level[watt_setp + 1] - watt_base) / 10.0) * ((double)(((int)(Cadence.value())) % 10))) +
|
||||
watt_base;
|
||||
}
|
||||
if (level >= wattTableFirstDimension) {
|
||||
level = wattTableFirstDimension - 1;
|
||||
}
|
||||
double *watts_of_level = wattTable[level];
|
||||
int watt_setp = (Cadence.value() / 10.0);
|
||||
if (watt_setp >= 10) {
|
||||
return (((double)Cadence.value()) / 100.0) * watts_of_level[wattTableSecondDimension - 1];
|
||||
}
|
||||
double watt_base = watts_of_level[watt_setp];
|
||||
return (((watts_of_level[watt_setp + 1] - watt_base) / 10.0) * ((double)(((int)(Cadence.value())) % 10))) +
|
||||
watt_base;
|
||||
}
|
||||
|
||||
resistance_t fitplusbike::resistanceFromPowerRequest(uint16_t power) {
|
||||
|
||||
@@ -276,7 +276,8 @@ void fitshowtreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
QBluetoothUuid nobleproconnect(QStringLiteral("0000ae00-0000-1000-8000-00805f9b34fb"));
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString() + QStringLiteral(" ") +
|
||||
QString::number(servRepr));
|
||||
if (gatt == nobleproconnect || servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
|
||||
if ((gatt == nobleproconnect && serviceId.isNull()) || servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
|
||||
qDebug() << "adding" << gatt.toString() << "as the default service";
|
||||
serviceId = gatt; // NOTE: clazy-rule-of-tow
|
||||
}
|
||||
}
|
||||
@@ -470,7 +471,11 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
.toBool())
|
||||
miles = 1.60934;
|
||||
|
||||
Speed = speed * miles;
|
||||
if(IS_RUNNING)
|
||||
Speed = speed * miles;
|
||||
else
|
||||
Speed = 0;
|
||||
|
||||
if (Speed.value() != speed) {
|
||||
emit speedChanged(speed);
|
||||
}
|
||||
@@ -499,6 +504,9 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
long appleWatchHeartRate = h->heartRate();
|
||||
h->setKcal(KCal.value());
|
||||
h->setDistance(Distance.value());
|
||||
h->setSpeed(Speed.value());
|
||||
h->setPower(m_watt.value());
|
||||
h->setCadence(Cadence.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
|
||||
#else
|
||||
|
||||
@@ -240,6 +240,7 @@ void flywheelbike::updateStats() {
|
||||
}
|
||||
|
||||
void flywheelbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
static uint8_t zero_fix_filter = 0;
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
@@ -293,7 +294,7 @@ void flywheelbike::characteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
|
||||
// https://www.facebook.com/groups/149984563348738/permalink/174268944253633/?comment_id=174366620910532&reply_comment_id=174666314213896
|
||||
|
||||
@@ -39,6 +39,12 @@ void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
|
||||
if(!gattFTMSService) {
|
||||
qDebug() << QStringLiteral("gattFTMSService is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (wait_for_response) {
|
||||
connect(gattFTMSService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
@@ -52,11 +58,15 @@ void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -89,7 +99,8 @@ void ftmsbike::forcePower(int16_t requestPower) {
|
||||
void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
|
||||
resistance_lvl_mode == false) {
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
|
||||
@@ -186,6 +197,7 @@ void ftmsbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -235,7 +247,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
@@ -272,16 +284,20 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
}
|
||||
|
||||
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;
|
||||
1000.0;*/
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
}
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
if (Flags.resistanceLvl) {
|
||||
@@ -290,7 +306,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
emit resistanceRead(Resistance.value());
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
} else {
|
||||
resistance_received = true;
|
||||
}
|
||||
double ac = 0.01243107769;
|
||||
double bc = 1.145964912;
|
||||
double cc = -23.50977444;
|
||||
@@ -308,10 +325,13 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
(2.0 * ar)) *
|
||||
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
if (!resistance_received) {
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Flags.instantPower) {
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
@@ -348,7 +368,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -361,7 +381,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(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 {
|
||||
@@ -419,7 +439,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
index += 2;
|
||||
@@ -442,7 +462,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
@@ -538,7 +558,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -551,7 +571,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(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 {
|
||||
@@ -580,7 +600,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!heart || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
@@ -759,7 +779,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
|
||||
|
||||
// handling gears
|
||||
if (b.at(0) == 0x11) {
|
||||
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS) {
|
||||
qDebug() << "applying gears mod" << m_gears;
|
||||
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
|
||||
if (m_gears != 0) {
|
||||
@@ -847,6 +867,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
if (bluetoothDevice.name().toUpper().startsWith("SUITO")) {
|
||||
qDebug() << QStringLiteral("SUITO found");
|
||||
max_resistance = 16;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("MAGNUS "))) {
|
||||
qDebug() << QStringLiteral("MAGNUS found");
|
||||
resistance_lvl_mode = true;
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -86,7 +86,7 @@ class ftmsbike : public bike {
|
||||
|
||||
QList<QLowEnergyService *> gattCommunicationChannelService;
|
||||
QLowEnergyCharacteristic gattWriteCharControlPointId;
|
||||
QLowEnergyService *gattFTMSService;
|
||||
QLowEnergyService *gattFTMSService = nullptr;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
@@ -104,6 +104,9 @@ class ftmsbike : public bike {
|
||||
|
||||
bool powerForced = false;
|
||||
|
||||
bool resistance_lvl_mode = false;
|
||||
bool resistance_received = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
@@ -54,8 +54,7 @@ void ftmsrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -137,6 +136,7 @@ void ftmsrower::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
@@ -186,9 +186,11 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
|
||||
if (!Flags.moreData) {
|
||||
|
||||
if (WATER_ROWER && lastStroke.secsTo(QDateTime::currentDateTime()) > 3) {
|
||||
if ((WATER_ROWER || DFIT_L_R) && lastStroke.secsTo(now) > 3) {
|
||||
qDebug() << "Resetting cadence!";
|
||||
Cadence = 0;
|
||||
m_watt = 0;
|
||||
Speed = 0;
|
||||
} else {
|
||||
Cadence = ((uint8_t)newValue.at(index)) / cadence_divider;
|
||||
}
|
||||
@@ -197,7 +199,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
(((uint16_t)((uint8_t)newValue.at(index + 2)) << 8) | (uint16_t)((uint8_t)newValue.at(index + 1)));
|
||||
|
||||
if (lastStrokesCount != StrokesCount.value()) {
|
||||
lastStroke = QDateTime::currentDateTime();
|
||||
lastStroke = now;
|
||||
}
|
||||
lastStrokesCount = StrokesCount.value();
|
||||
|
||||
@@ -229,7 +231,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
@@ -242,8 +244,10 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Pace: ") + QString::number(instantPace));
|
||||
|
||||
Speed = (60.0 / instantPace) *
|
||||
if((DFIT_L_R && Cadence.value() > 0) || !DFIT_L_R) {
|
||||
Speed = (60.0 / instantPace) *
|
||||
30.0; // translating pace (min/500m) to km/h in order to match the pace function in the rower.cpp
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
|
||||
@@ -261,7 +265,8 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
if (!filterWattNull || watt != 0) {
|
||||
m_watt = watt;
|
||||
if((DFIT_L_R && Cadence.value() > 0) || !DFIT_L_R)
|
||||
m_watt = watt;
|
||||
}
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
}
|
||||
@@ -299,7 +304,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -342,7 +347,7 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
update_hr_from_external();
|
||||
@@ -466,6 +471,8 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
@@ -481,16 +488,25 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (virtual_device_enabled) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
if (!virtual_device_rower) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&ftmsrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&ftmsrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
|
||||
void ftmsrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
@@ -564,6 +580,9 @@ void ftmsrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("S4 COMMS"))) {
|
||||
WATER_ROWER = true;
|
||||
qDebug() << "WATER_ROWER found!";
|
||||
} 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("PM5"))) {
|
||||
PM5 = true;
|
||||
qDebug() << "PM5 found!";
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "rower.h"
|
||||
#include "virtualbike.h"
|
||||
#include "virtualrower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
@@ -69,6 +70,7 @@ class ftmsrower : public rower {
|
||||
bool PM5 = false;
|
||||
|
||||
bool WATER_ROWER = false;
|
||||
bool DFIT_L_R = false;
|
||||
QDateTime lastStroke = QDateTime::currentDateTime();
|
||||
double lastStrokesCount = 0;
|
||||
|
||||
|
||||
@@ -7,12 +7,16 @@
|
||||
|
||||
gpx::gpx(QObject *parent) : QObject(parent) {}
|
||||
|
||||
QList<gpx_altitude_point_for_treadmill> gpx::open(const QString &gpx) {
|
||||
QList<gpx_altitude_point_for_treadmill> gpx::open(const QString &gpx, bluetoothdevice::BLUETOOTH_TYPE device_type) {
|
||||
QSettings settings;
|
||||
const double meter_limit_for_auto_loop = 300;
|
||||
bool treadmill_force_speed =
|
||||
settings.value(QZSettings::treadmill_force_speed, QZSettings::default_treadmill_force_speed).toBool();
|
||||
bool gpx_loop = settings.value(QZSettings::gpx_loop, QZSettings::default_gpx_loop).toBool();
|
||||
|
||||
if(device_type == bluetoothdevice::BIKE)
|
||||
treadmill_force_speed = false;
|
||||
|
||||
QFile input(gpx);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QDomDocument doc;
|
||||
|
||||
@@ -29,7 +29,7 @@ class gpx : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit gpx(QObject *parent = nullptr);
|
||||
QList<gpx_altitude_point_for_treadmill> open(const QString &gpx);
|
||||
QList<gpx_altitude_point_for_treadmill> open(const QString &gpx, bluetoothdevice::BLUETOOTH_TYPE device_type);
|
||||
static void save(const QString &filename, QList<SessionLine> session, bluetoothdevice::BLUETOOTH_TYPE type);
|
||||
QString getVideoURL() {return videoUrl;}
|
||||
|
||||
|
||||
445
src/homeform.cpp
445
src/homeform.cpp
@@ -5,6 +5,7 @@
|
||||
#include "localipaddress.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#include <QAndroidJniObject>
|
||||
#endif
|
||||
#include "material.h"
|
||||
#include "qfit.h"
|
||||
@@ -47,23 +48,12 @@ using namespace std::chrono_literals;
|
||||
#include <QtAndroid>
|
||||
#endif
|
||||
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#define STRAVA_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef STRAVA_CLIENT_ID
|
||||
#define STRAVA_CLIENT_ID 7976
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_CLIENT_ID!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_CLIENT_ID!!!"
|
||||
#pragma message "DEFINE STRAVA_CLIENT_ID!!!"
|
||||
#endif
|
||||
#endif
|
||||
#define _STR(x) #x
|
||||
@@ -485,6 +475,9 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
QObject::connect(home, SIGNAL(start_clicked()), this, SLOT(Start()));
|
||||
QObject::connect(home, SIGNAL(stop_clicked()), this, SLOT(Stop()));
|
||||
QObject::connect(stack, SIGNAL(trainprogram_open_clicked(QUrl)), this, SLOT(trainprogram_open_clicked(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(trainprogram_open_other_folder(QUrl)), this, SLOT(trainprogram_open_other_folder(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(gpx_open_other_folder(QUrl)), this, SLOT(gpx_open_other_folder(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(profile_open_clicked(QUrl)), this, SLOT(profile_open_clicked(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(trainprogram_preview(QUrl)), this, SLOT(trainprogram_preview(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(gpxpreview_open_clicked(QUrl)), this, SLOT(gpxpreview_open_clicked(QUrl)));
|
||||
QObject::connect(stack, SIGNAL(trainprogram_zwo_loaded(QString)), this, SLOT(trainprogram_zwo_loaded(QString)));
|
||||
@@ -550,6 +543,22 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
// Android 14 restrics access to /Android/data folder
|
||||
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 14)) {
|
||||
QDirIterator itAndroid(getAndroidDataAppDir(), QDirIterator::Subdirectories);
|
||||
QDir().mkdir(getWritableAppDir());
|
||||
QDir().mkdir(getProfileDir());
|
||||
while (itAndroid.hasNext()) {
|
||||
qDebug() << itAndroid.filePath() << itAndroid.fileName() << itAndroid.filePath().replace(itAndroid.path(), "");
|
||||
if (!QFile(getWritableAppDir() + itAndroid.next().replace(itAndroid.path(), "")).exists()) {
|
||||
if(QFile::copy(itAndroid.filePath(), getWritableAppDir() + itAndroid.filePath().replace(itAndroid.path(), "")))
|
||||
QFile::remove(itAndroid.filePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
m_speech.setLocale(QLocale::English);
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
@@ -562,7 +571,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
deviceConnected(b);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#ifndef Q_OS_IOS
|
||||
iphone_browser = new QMdnsEngine::Browser(&iphone_server, "_qz_iphone._tcp.local.", &iphone_cache);
|
||||
|
||||
QObject::connect(iphone_browser, &QMdnsEngine::Browser::serviceAdded, [](const QMdnsEngine::Service &service) {
|
||||
@@ -652,8 +661,12 @@ void homeform::chartSaved(QString fileName) {
|
||||
if (!stopped)
|
||||
return;
|
||||
chartImagesFilenames.append(fileName);
|
||||
if (chartImagesFilenames.length() >= 8) {
|
||||
if (chartImagesFilenames.length() >= 9) {
|
||||
sendMail();
|
||||
qDebug() << "removing chart images";
|
||||
for (const QString &f : qAsConst(chartImagesFilenames)) {
|
||||
QFile::remove(f);
|
||||
}
|
||||
chartImagesFilenames.clear();
|
||||
}
|
||||
}
|
||||
@@ -760,7 +773,7 @@ void homeform::peloton_start_workout() {
|
||||
if (!stravaPelotonActivityName.isEmpty() && !stravaPelotonInstructorName.isEmpty()) {
|
||||
QString path = getWritableAppDir() + "training/" + workoutNameBasedOnBluetoothDevice() + "/" +
|
||||
stravaPelotonInstructorName + "/";
|
||||
QDir().mkdir(path);
|
||||
QDir().mkpath(path);
|
||||
lastTrainProgramFileSaved =
|
||||
path + stravaPelotonActivityName.replace("/", "-") + " - " + stravaPelotonInstructorName + ".xml";
|
||||
trainProgram->save(lastTrainProgramFileSaved);
|
||||
@@ -851,7 +864,13 @@ void homeform::pelotonWorkoutChanged(const QString &name, const QString &instruc
|
||||
QString homeform::getWritableAppDir() {
|
||||
QString path = QLatin1String("");
|
||||
#if defined(Q_OS_ANDROID)
|
||||
path = getAndroidDataAppDir() + "/";
|
||||
// Android 14 restrics access to /Android/data folder
|
||||
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Android, 14)) {
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/QZ/";
|
||||
QDir().mkdir(path);
|
||||
} else {
|
||||
path = getAndroidDataAppDir() + "/";
|
||||
}
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_OSX)
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/";
|
||||
#elif defined(Q_OS_IOS)
|
||||
@@ -875,7 +894,7 @@ void homeform::backup() {
|
||||
QFile::remove(filename);
|
||||
qfit::save(filename, Session, dev->deviceType(),
|
||||
qobject_cast<m3ibike *>(dev) ? QFIT_PROCESS_DISTANCENOISE : QFIT_PROCESS_NONE,
|
||||
stravaPelotonWorkoutType);
|
||||
stravaPelotonWorkoutType, dev->bluetoothDevice.name());
|
||||
|
||||
index++;
|
||||
if (index > 1) {
|
||||
@@ -1038,6 +1057,19 @@ void homeform::trainProgramSignals() {
|
||||
connect(this, &homeform::workoutEventStateChanged, bluetoothManager->device(),
|
||||
&bluetoothdevice::workoutEventStateChanged);
|
||||
|
||||
if (trainProgram) {
|
||||
setChartIconVisible(trainProgram->powerzoneWorkout());
|
||||
if (chartFooterVisible()) {
|
||||
if (trainProgram->powerzoneWorkout()) {
|
||||
// reloading
|
||||
setChartFooterVisible(false);
|
||||
setChartFooterVisible(true);
|
||||
} else {
|
||||
setChartFooterVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("trainProgram associated to a device");
|
||||
} else {
|
||||
qDebug() << QStringLiteral("trainProgram NOT associated to a device");
|
||||
@@ -1941,6 +1973,57 @@ void homeform::sortTiles() {
|
||||
target_pace->setName("T.Pace(m/500m)");
|
||||
dataList.append(target_pace);
|
||||
}
|
||||
|
||||
if (settings
|
||||
.value(QZSettings::tile_preset_resistance_1_enabled,
|
||||
QZSettings::default_tile_preset_resistance_1_enabled)
|
||||
.toBool() &&
|
||||
settings.value(QZSettings::tile_preset_resistance_1_order,
|
||||
QZSettings::default_tile_preset_resistance_1_order)
|
||||
.toInt() == i) {
|
||||
preset_resistance_1->setGridId(i);
|
||||
dataList.append(preset_resistance_1);
|
||||
}
|
||||
if (settings
|
||||
.value(QZSettings::tile_preset_resistance_2_enabled,
|
||||
QZSettings::default_tile_preset_resistance_2_enabled)
|
||||
.toBool() &&
|
||||
settings.value(QZSettings::tile_preset_resistance_2_order,
|
||||
QZSettings::default_tile_preset_resistance_2_order)
|
||||
.toInt() == i) {
|
||||
preset_resistance_2->setGridId(i);
|
||||
dataList.append(preset_resistance_2);
|
||||
}
|
||||
if (settings
|
||||
.value(QZSettings::tile_preset_resistance_3_enabled,
|
||||
QZSettings::default_tile_preset_resistance_3_enabled)
|
||||
.toBool() &&
|
||||
settings.value(QZSettings::tile_preset_resistance_3_order,
|
||||
QZSettings::default_tile_preset_resistance_3_order)
|
||||
.toInt() == i) {
|
||||
preset_resistance_3->setGridId(i);
|
||||
dataList.append(preset_resistance_3);
|
||||
}
|
||||
if (settings
|
||||
.value(QZSettings::tile_preset_resistance_4_enabled,
|
||||
QZSettings::default_tile_preset_resistance_4_enabled)
|
||||
.toBool() &&
|
||||
settings.value(QZSettings::tile_preset_resistance_4_order,
|
||||
QZSettings::default_tile_preset_resistance_4_order)
|
||||
.toInt() == i) {
|
||||
preset_resistance_4->setGridId(i);
|
||||
dataList.append(preset_resistance_4);
|
||||
}
|
||||
if (settings
|
||||
.value(QZSettings::tile_preset_resistance_5_enabled,
|
||||
QZSettings::default_tile_preset_resistance_5_enabled)
|
||||
.toBool() &&
|
||||
settings.value(QZSettings::tile_preset_resistance_5_order,
|
||||
QZSettings::default_tile_preset_resistance_5_order)
|
||||
.toInt() == i) {
|
||||
preset_resistance_5->setGridId(i);
|
||||
dataList.append(preset_resistance_5);
|
||||
}
|
||||
}
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
@@ -2242,6 +2325,12 @@ void homeform::sortTiles() {
|
||||
target_pace->setGridId(i);
|
||||
dataList.append(target_pace);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tile_pace_enabled, true).toBool() &&
|
||||
settings.value(QZSettings::tile_pace_order, 51).toInt() == i) {
|
||||
pace->setGridId(i);
|
||||
dataList.append(pace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2347,6 +2436,14 @@ void homeform::deviceConnected(QBluetoothDeviceInfo b) {
|
||||
if (settings.value(QZSettings::floating_startup, QZSettings::default_floating_startup).toBool()) {
|
||||
floatingOpen();
|
||||
}
|
||||
|
||||
if (!settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name)
|
||||
.toString()
|
||||
.compare(QZSettings::default_heart_rate_belt_name) &&
|
||||
!settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/WearableController", "start",
|
||||
"(Landroid/content/Context;)V", QtAndroid::androidContext().object());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool()) {
|
||||
@@ -2810,6 +2907,7 @@ void homeform::Plus(const QString &name) {
|
||||
} else if (name.contains(QStringLiteral("target_power"))) {
|
||||
if (bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
m_overridePower = true;
|
||||
((bike *)bluetoothManager->device())
|
||||
->changePower(((bike *)bluetoothManager->device())->lastRequestedPower().value() + 10);
|
||||
if (trainProgram) {
|
||||
@@ -2855,8 +2953,8 @@ void homeform::pelotonOffset_Minus() { Minus(QStringLiteral("peloton_offset"));
|
||||
void homeform::bluetoothDeviceConnected(bluetoothdevice *b) {
|
||||
this->innerTemplateManager->start(b);
|
||||
this->userTemplateManager->start(b);
|
||||
#ifdef Q_OS_ANDROID
|
||||
// heart rate received from apple watch while QZ is running on android via TCP socket (iphone_socket)
|
||||
#ifndef Q_OS_IOS
|
||||
// heart rate received from apple watch while QZ is running on a different device via TCP socket (iphone_socket)
|
||||
connect(this, SIGNAL(heartRate(uint8_t)), b, SLOT(heartRate(uint8_t)));
|
||||
#endif
|
||||
}
|
||||
@@ -3051,6 +3149,7 @@ void homeform::Minus(const QString &name) {
|
||||
} else if (name.contains(QStringLiteral("target_power"))) {
|
||||
if (bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
m_overridePower = true;
|
||||
((bike *)bluetoothManager->device())
|
||||
->changePower(((bike *)bluetoothManager->device())->lastRequestedPower().value() - 10);
|
||||
if (trainProgram) {
|
||||
@@ -3098,6 +3197,8 @@ void homeform::Start_inner(bool send_event_to_device) {
|
||||
QSettings settings;
|
||||
qDebug() << QStringLiteral("Start pressed - paused") << paused << QStringLiteral("stopped") << stopped;
|
||||
|
||||
m_overridePower = false;
|
||||
|
||||
if (settings.value(QZSettings::tts_enabled, QZSettings::default_tts_enabled).toBool())
|
||||
m_speech.say("Start pressed");
|
||||
|
||||
@@ -3202,6 +3303,9 @@ void homeform::StopRequested() {
|
||||
|
||||
void homeform::Stop() {
|
||||
QSettings settings;
|
||||
|
||||
m_startRequested = false;
|
||||
|
||||
qDebug() << QStringLiteral("Stop pressed - paused") << paused << QStringLiteral("stopped") << stopped;
|
||||
|
||||
if (stopped) {
|
||||
@@ -3252,6 +3356,10 @@ void homeform::Stop() {
|
||||
emit startIconChanged(startIcon());
|
||||
emit startTextChanged(startText());
|
||||
emit startColorChanged(startColor());
|
||||
|
||||
// clearing the label on top because if it was running a training program, with stop the program will be terminated
|
||||
m_info = workoutName();
|
||||
emit infoChanged(m_info);
|
||||
}
|
||||
|
||||
if (trainProgram) {
|
||||
@@ -3460,7 +3568,7 @@ void homeform::update() {
|
||||
else if (next.speed != -1)
|
||||
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.inclination != -1)
|
||||
else if (next.inclination != -200)
|
||||
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.power != -1) {
|
||||
@@ -3533,7 +3641,22 @@ void homeform::update() {
|
||||
wattKg->setSecondLine(
|
||||
QStringLiteral("AVG: ") + QString::number(bluetoothManager->device()->wattKg().average(), 'f', 1) +
|
||||
QStringLiteral("MAX: ") + QString::number(bluetoothManager->device()->wattKg().max(), 'f', 1));
|
||||
datetime->setValue(QTime::currentTime().toString(QStringLiteral("hh:mm:ss")));
|
||||
QLocale locale = QLocale::system();
|
||||
|
||||
// Format the time based on the locale
|
||||
QString timeFormat = locale.timeFormat(QLocale::ShortFormat);
|
||||
bool usesAMPMFormat = timeFormat.toUpper().contains("A");
|
||||
QDateTime currentTime = QDateTime::currentDateTime();
|
||||
|
||||
QString formattedTime;
|
||||
if (usesAMPMFormat) {
|
||||
// The locale uses 12-hour format with AM/PM
|
||||
formattedTime = currentTime.toString("h:mm:ss AP");
|
||||
} else {
|
||||
// The locale uses 24-hour format
|
||||
formattedTime = currentTime.toString("H:mm:ss");
|
||||
}
|
||||
datetime->setValue(formattedTime);
|
||||
if (power5s)
|
||||
watts = bluetoothManager->device()->wattsMetric().average5s();
|
||||
else
|
||||
@@ -3638,29 +3761,46 @@ void homeform::update() {
|
||||
QStringLiteral(" MAX: ") +
|
||||
QString::number(((treadmill *)bluetoothManager->device())->currentVerticalOscillation().max(), 'f', 0));
|
||||
|
||||
if (bluetoothManager->device()->currentSpeed().value() < 9) {
|
||||
speed->setValueFontColor(QStringLiteral("white"));
|
||||
this->pace->setValueFontColor(QStringLiteral("white"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 10) {
|
||||
speed->setValueFontColor(QStringLiteral("limegreen"));
|
||||
this->pace->setValueFontColor(QStringLiteral("limegreen"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 11) {
|
||||
speed->setValueFontColor(QStringLiteral("gold"));
|
||||
this->pace->setValueFontColor(QStringLiteral("gold"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 12) {
|
||||
speed->setValueFontColor(QStringLiteral("orange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orange"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 13) {
|
||||
speed->setValueFontColor(QStringLiteral("darkorange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("darkorange"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 14) {
|
||||
speed->setValueFontColor(QStringLiteral("orangered"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orangered"));
|
||||
// if there is no training program, the color is based on presets
|
||||
if (!trainProgram || trainProgram->currentRow().speed == -1) {
|
||||
if (bluetoothManager->device()->currentSpeed().value() < 9) {
|
||||
speed->setValueFontColor(QStringLiteral("white"));
|
||||
this->pace->setValueFontColor(QStringLiteral("white"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 10) {
|
||||
speed->setValueFontColor(QStringLiteral("limegreen"));
|
||||
this->pace->setValueFontColor(QStringLiteral("limegreen"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 11) {
|
||||
speed->setValueFontColor(QStringLiteral("gold"));
|
||||
this->pace->setValueFontColor(QStringLiteral("gold"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 12) {
|
||||
speed->setValueFontColor(QStringLiteral("orange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orange"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 13) {
|
||||
speed->setValueFontColor(QStringLiteral("darkorange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("darkorange"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() < 14) {
|
||||
speed->setValueFontColor(QStringLiteral("orangered"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orangered"));
|
||||
} else {
|
||||
speed->setValueFontColor(QStringLiteral("red"));
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
} else {
|
||||
speed->setValueFontColor(QStringLiteral("red"));
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
if (bluetoothManager->device()->currentSpeed().value() <= trainProgram->currentRow().upper_speed &&
|
||||
bluetoothManager->device()->currentSpeed().value() >= trainProgram->currentRow().lower_speed) {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("limegreen"));
|
||||
this->pace->setValueFontColor(QStringLiteral("limegreen"));
|
||||
} else if (bluetoothManager->device()->currentSpeed().value() <=
|
||||
(trainProgram->currentRow().upper_speed + 0.2) &&
|
||||
bluetoothManager->device()->currentSpeed().value() >=
|
||||
(trainProgram->currentRow().lower_speed - 0.2)) {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("orange"));
|
||||
this->pace->setValueFontColor(QStringLiteral("orange"));
|
||||
} else {
|
||||
this->target_zone->setValueFontColor(QStringLiteral("red"));
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
}
|
||||
bluetoothManager->device()->currentSpeed().setColor(speed->valueFontColor());
|
||||
|
||||
this->target_pace->setValue(
|
||||
((treadmill *)bluetoothManager->device())->lastRequestedPace().toString(QStringLiteral("m:ss")));
|
||||
@@ -3756,6 +3896,14 @@ void homeform::update() {
|
||||
this->steeringAngle->setValue(
|
||||
QString::number(((bike *)bluetoothManager->device())->currentSteeringAngle().value(), 'f', 1));
|
||||
|
||||
if ((!trainProgram || (trainProgram && !trainProgram->isStarted())) &&
|
||||
!((bike *)bluetoothManager->device())->ergModeSupportedAvailableByHardware() &&
|
||||
((bike *)bluetoothManager->device())->lastRequestedPower().value() > 0 && m_overridePower) {
|
||||
qDebug() << QStringLiteral("using target power tile for ERG workout manually");
|
||||
((bike *)bluetoothManager->device())
|
||||
->changePower(((bike *)bluetoothManager->device())->lastRequestedPower().value());
|
||||
}
|
||||
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
|
||||
if (bluetoothManager->device()->currentSpeed().value()) {
|
||||
pace = 10000 / (((rower *)bluetoothManager->device())->currentPace().second() +
|
||||
@@ -3809,17 +3957,23 @@ void homeform::update() {
|
||||
}
|
||||
switch (trainProgram->currentRow().pace_intensity) {
|
||||
case 0:
|
||||
this->target_zone->setValue(tr("Easy"));
|
||||
this->target_zone->setValue(tr("Rec."));
|
||||
break;
|
||||
case 1:
|
||||
this->target_zone->setValue(tr("Moder."));
|
||||
this->target_zone->setValue(tr("Easy"));
|
||||
break;
|
||||
case 2:
|
||||
this->target_zone->setValue(tr("Chall."));
|
||||
this->target_zone->setValue(tr("Moder."));
|
||||
break;
|
||||
case 3:
|
||||
this->target_zone->setValue(tr("Chall."));
|
||||
break;
|
||||
case 4:
|
||||
this->target_zone->setValue(tr("Max"));
|
||||
break;
|
||||
default:
|
||||
this->target_zone->setValue(tr("N/A"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer() * 1000.0, 'f', 0));
|
||||
@@ -3897,10 +4051,19 @@ void homeform::update() {
|
||||
speed->setValueFontColor(QStringLiteral("red"));
|
||||
this->pace->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
bluetoothManager->device()->currentSpeed().setColor(speed->valueFontColor());
|
||||
}
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
|
||||
if (((elliptical *)bluetoothManager->device())->currentSpeed().value() > 2)
|
||||
this->pace->setValue(
|
||||
((elliptical *)bluetoothManager->device())->currentPace().toString(QStringLiteral("m:ss")));
|
||||
else
|
||||
this->pace->setValue("N/A");
|
||||
this->pace->setSecondLine(
|
||||
QStringLiteral("AVG: ") +
|
||||
((elliptical *)bluetoothManager->device())->averagePace().toString(QStringLiteral("m:ss")) +
|
||||
QStringLiteral(" MAX: ") +
|
||||
((elliptical *)bluetoothManager->device())->maxPace().toString(QStringLiteral("m:ss")));
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer() * unit_conversion, 'f', 2));
|
||||
resistance = ((elliptical *)bluetoothManager->device())->currentResistance().value();
|
||||
peloton_resistance = ((elliptical *)bluetoothManager->device())->pelotonResistance().value();
|
||||
@@ -3953,6 +4116,18 @@ void homeform::update() {
|
||||
if (trainProgram) {
|
||||
int8_t lower_requested_peloton_resistance = trainProgram->currentRow().lower_requested_peloton_resistance;
|
||||
int8_t upper_requested_peloton_resistance = trainProgram->currentRow().upper_requested_peloton_resistance;
|
||||
double lower_requested_peloton_resistance_to_bike_resistance = 0;
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((bike *)bluetoothManager->device())->pelotonToBikeResistance(lower_requested_peloton_resistance);
|
||||
else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((rower *)bluetoothManager->device())->pelotonToBikeResistance(lower_requested_peloton_resistance);
|
||||
else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->pelotonToEllipticalResistance(lower_requested_peloton_resistance);
|
||||
|
||||
if (lower_requested_peloton_resistance != -1) {
|
||||
this->target_peloton_resistance->setSecondLine(
|
||||
QStringLiteral("MIN: ") + QString::number(lower_requested_peloton_resistance, 'f', 0) +
|
||||
@@ -3967,17 +4142,16 @@ void homeform::update() {
|
||||
.toBool()) {
|
||||
if (lower_requested_peloton_resistance == -1) {
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("white"));
|
||||
} else if (((int8_t)qRound(peloton_resistance)) < lower_requested_peloton_resistance) {
|
||||
} else if (resistance < lower_requested_peloton_resistance_to_bike_resistance) {
|
||||
// we need to compare the real resistance and not the peloton resistance because most of the bikes
|
||||
// have a 1:3 conversion so this compare will be always true even if the actual resistance is the
|
||||
// same #1608
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("red"));
|
||||
} else if (((int8_t)qRound(peloton_resistance)) <= upper_requested_peloton_resistance) {
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("limegreen"));
|
||||
} else {
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("orange"));
|
||||
}
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
((bike *)bluetoothManager->device())
|
||||
->pelotonResistance()
|
||||
.setColor(this->peloton_resistance->valueFontColor());
|
||||
}
|
||||
|
||||
int16_t lower_cadence = trainProgram->currentRow().lower_cadence;
|
||||
@@ -4000,7 +4174,6 @@ void homeform::update() {
|
||||
} else {
|
||||
this->cadence->setValueFontColor(QStringLiteral("orange"));
|
||||
}
|
||||
bluetoothManager->device()->currentCadence().setColor(this->cadence->valueFontColor());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4096,7 +4269,6 @@ void homeform::update() {
|
||||
ftp->setValueFontColor(QStringLiteral("red"));
|
||||
watt->setValueFontColor(QStringLiteral("red"));
|
||||
}
|
||||
bluetoothManager->device()->wattsMetric().setColor(watt->valueFontColor());
|
||||
bluetoothManager->device()->setPowerZone(ftpZone);
|
||||
ftp->setValue(QStringLiteral("Z") + QString::number(ftpZone, 'f', 1));
|
||||
ftp->setSecondLine(ftpMinW + QStringLiteral("-") + ftpMaxW + QStringLiteral("W ") +
|
||||
@@ -4294,7 +4466,6 @@ void homeform::update() {
|
||||
pidHR->setValueFontColor(QStringLiteral("white"));
|
||||
break;
|
||||
}
|
||||
bluetoothManager->device()->currentHeart().setColor(heart->valueFontColor());
|
||||
bluetoothManager->device()->setHeartZone(currentHRZone);
|
||||
Z = QStringLiteral("Z") + QString::number(currentHRZone, 'f', 1);
|
||||
heart->setSecondLine(Z + QStringLiteral(" AVG: ") +
|
||||
@@ -4322,9 +4493,11 @@ void homeform::update() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool() &&
|
||||
KeepAwakeHelper::antObject(false)) {
|
||||
KeepAwakeHelper::antObject(false)->callMethod<void>(
|
||||
"setCadenceSpeedPower", "(FII)V", (float)bluetoothManager->device()->currentSpeed().value(), (int)watts,
|
||||
(int)cadence);
|
||||
double v = bluetoothManager->device()->currentSpeed().value();
|
||||
v *= settings.value(QZSettings::ant_speed_gain, QZSettings::default_ant_speed_gain).toDouble();
|
||||
v += settings.value(QZSettings::ant_speed_offset, QZSettings::default_ant_speed_offset).toDouble();
|
||||
KeepAwakeHelper::antObject(false)->callMethod<void>("setCadenceSpeedPower", "(FII)V", (float)v, (int)watts,
|
||||
(int)cadence);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -4742,6 +4915,16 @@ void homeform::update() {
|
||||
}
|
||||
|
||||
if (!stopped && !paused) {
|
||||
if(settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble() != 0) {
|
||||
if (bluetoothManager->device()->currentDistance().lapValue() >=
|
||||
settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble()) {
|
||||
qDebug() << QStringLiteral("Autolap based on distance");
|
||||
Lap();
|
||||
setToastRequested("AutoLap " + QString::number(settings.value(QZSettings::autolap_distance, QZSettings::default_autolap_distance).toDouble(), 'f', 1));
|
||||
emit toastRequestedChanged(toastRequested());
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tts_enabled, QZSettings::default_tts_enabled).toBool()) {
|
||||
static double tts_speed_played = 0;
|
||||
bool description =
|
||||
@@ -4980,8 +5163,13 @@ void homeform::update() {
|
||||
}
|
||||
}
|
||||
|
||||
if(bluetoothManager->device()->currentSpeed().value() > 0 && !isinf(bluetoothManager->device()->currentSpeed().value()))
|
||||
bluetoothManager->device()->addCurrentDistance1s((bluetoothManager->device()->currentSpeed().value() / 3600.0));
|
||||
|
||||
qDebug() << "Current Distance 1s:" << bluetoothManager->device()->currentDistance1s().value() << bluetoothManager->device()->currentSpeed().value();
|
||||
|
||||
SessionLine s(
|
||||
bluetoothManager->device()->currentSpeed().value(), inclination, bluetoothManager->device()->odometer(),
|
||||
bluetoothManager->device()->currentSpeed().value(), inclination, bluetoothManager->device()->currentDistance1s().value(),
|
||||
watts, resistance, peloton_resistance, (uint8_t)bluetoothManager->device()->currentHeart().value(),
|
||||
pace, cadence, bluetoothManager->device()->calories().value(),
|
||||
bluetoothManager->device()->elevationGain().value(),
|
||||
@@ -4998,7 +5186,7 @@ void homeform::update() {
|
||||
lapTrigger = false;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#ifndef Q_OS_IOS
|
||||
if (iphone_socket && iphone_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
QString toSend =
|
||||
"SENDER=PAD#HR=" + QString::number(bluetoothManager->device()->currentHeart().value()) +
|
||||
@@ -5040,11 +5228,67 @@ bool homeform::getLap() {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString homeform::getFileNameFromContentUri(const QString &uriString) {
|
||||
qDebug() << "getFileNameFromContentUri" << uriString;
|
||||
if(!uriString.startsWith("content")) {
|
||||
return uriString;
|
||||
}
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
||||
QAndroidJniObject jUriString = QAndroidJniObject::fromString(uriString);
|
||||
QAndroidJniObject jUri = QAndroidJniObject::callStaticObjectMethod("android/net/Uri", "parse", "(Ljava/lang/String;)Landroid/net/Uri;", jUriString.object<jstring>());
|
||||
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
|
||||
"org/cagnulen/qdomyoszwift/ContentHelper",
|
||||
"getFileName",
|
||||
"(Landroid/content/Context;Landroid/net/Uri;)Ljava/lang/String;",
|
||||
QtAndroid::androidContext().object(),
|
||||
jUri.object());
|
||||
return result.toString();
|
||||
#else
|
||||
return uriString;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString homeform::copyAndroidContentsURI(QUrl file, QString subfolder) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
QString fileNameLocal = getFileNameFromContentUri(file.toString());
|
||||
if(fileNameLocal.contains(getWritableAppDir() + subfolder + "/")) {
|
||||
qDebug() << "no need to copy file, the file is already in QZ subfolder" << file << subfolder;
|
||||
return file.toString();
|
||||
}
|
||||
QFileInfo f(fileNameLocal);
|
||||
QString filename = f.fileName();
|
||||
QFile fileFile(QQmlFile::urlToLocalFileOrQrc(file));
|
||||
QString dest = getWritableAppDir() + subfolder + "/" + filename;
|
||||
qDebug() << file.fileName() << fileNameLocal << filename;
|
||||
QFile::remove(dest);
|
||||
bool copy = fileFile.copy(dest);
|
||||
qDebug() << "copy" << dest << copy << fileFile.exists() << fileFile.isReadable();
|
||||
return dest;
|
||||
#endif
|
||||
return file.toString();
|
||||
}
|
||||
|
||||
void homeform::profile_open_clicked(const QUrl &fileName) {
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
copyAndroidContentsURI(fileName, "profiles");
|
||||
}
|
||||
|
||||
void homeform::trainprogram_open_other_folder(const QUrl &fileName) {
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
copyAndroidContentsURI(fileName, "training");
|
||||
}
|
||||
|
||||
void homeform::gpx_open_other_folder(const QUrl &fileName) {
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
copyAndroidContentsURI(fileName, "gpx");
|
||||
}
|
||||
|
||||
void homeform::trainprogram_open_clicked(const QUrl &fileName) {
|
||||
qDebug() << QStringLiteral("trainprogram_open_clicked") << fileName;
|
||||
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
qDebug() << file.fileName();
|
||||
|
||||
if (!file.fileName().isEmpty()) {
|
||||
{
|
||||
if (previewTrainProgram) {
|
||||
@@ -5055,7 +5299,7 @@ void homeform::trainprogram_open_clicked(const QUrl &fileName) {
|
||||
delete trainProgram;
|
||||
}
|
||||
|
||||
trainProgram = trainprogram::load(file.fileName(), bluetoothManager);
|
||||
trainProgram = trainprogram::load(file.fileName(), bluetoothManager, file.fileName().right(3).toUpper());
|
||||
|
||||
QString movieName = file.fileName().left(file.fileName().length() - 3) + "mp4";
|
||||
if (QFile::exists(movieName)) {
|
||||
@@ -5072,7 +5316,7 @@ void homeform::trainprogram_open_clicked(const QUrl &fileName) {
|
||||
trainingProgram()->setVideoAvailable(false);
|
||||
}
|
||||
|
||||
stravaWorkoutName = QFileInfo(fileName.fileName()).baseName();
|
||||
stravaWorkoutName = QFileInfo(file.fileName()).baseName();
|
||||
stravaPelotonInstructorName = QStringLiteral("");
|
||||
emit workoutNameChanged(workoutName());
|
||||
emit instructorNameChanged(instructorName());
|
||||
@@ -5092,14 +5336,15 @@ void homeform::trainprogram_preview(const QUrl &fileName) {
|
||||
qDebug() << QStringLiteral("trainprogram_preview") << fileName;
|
||||
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
qDebug() << file.fileName();
|
||||
if (!file.fileName().isEmpty()) {
|
||||
QString fileNameLocal = getFileNameFromContentUri(file.fileName());
|
||||
qDebug() << fileNameLocal;
|
||||
if (!fileNameLocal.isEmpty()) {
|
||||
{
|
||||
if (previewTrainProgram) {
|
||||
delete previewTrainProgram;
|
||||
previewTrainProgram = 0;
|
||||
}
|
||||
previewTrainProgram = trainprogram::load(file.fileName(), bluetoothManager);
|
||||
previewTrainProgram = trainprogram::load(file.fileName(), bluetoothManager, fileNameLocal.right(3).toUpper());
|
||||
emit previewWorkoutPointsChanged(preview_workout_points());
|
||||
emit previewWorkoutDescriptionChanged(previewWorkoutDescription());
|
||||
emit previewWorkoutTagsChanged(previewWorkoutTags());
|
||||
@@ -5158,7 +5403,7 @@ void homeform::fit_save_clicked() {
|
||||
|
||||
qfit::save(filename, Session, dev->deviceType(),
|
||||
qobject_cast<m3ibike *>(dev) ? QFIT_PROCESS_DISTANCENOISE : QFIT_PROCESS_NONE,
|
||||
stravaPelotonWorkoutType, workoutName);
|
||||
stravaPelotonWorkoutType, workoutName, dev->bluetoothDevice.name());
|
||||
lastFitFileSaved = filename;
|
||||
|
||||
QSettings settings;
|
||||
@@ -5179,7 +5424,7 @@ void homeform::gpx_open_clicked(const QUrl &fileName) {
|
||||
qDebug() << QStringLiteral("gpx_open_clicked") << fileName;
|
||||
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(fileName));
|
||||
qDebug() << file.fileName();
|
||||
|
||||
stravaWorkoutName = QFileInfo(file.fileName()).baseName();
|
||||
if (!file.fileName().isEmpty()) {
|
||||
{
|
||||
@@ -5191,7 +5436,7 @@ void homeform::gpx_open_clicked(const QUrl &fileName) {
|
||||
// KML to GPX https://www.gpsvisualizer.com/elevation
|
||||
gpx g;
|
||||
QList<trainrow> list;
|
||||
auto g_list = g.open(file.fileName());
|
||||
auto g_list = g.open(file.fileName(), bluetoothManager->device() ? bluetoothManager->device()->deviceType() : bluetoothdevice::BIKE);
|
||||
if (bluetoothManager->device())
|
||||
bluetoothManager->device()->setGPXFile(file.fileName());
|
||||
gpx_altitude_point_for_treadmill last;
|
||||
@@ -5261,7 +5506,7 @@ void homeform::gpxpreview_open_clicked(const QUrl &fileName) {
|
||||
|
||||
if (!file.fileName().isEmpty()) {
|
||||
gpx g;
|
||||
auto g_list = g.open(file.fileName());
|
||||
auto g_list = g.open(file.fileName(), bluetoothManager->device() ? bluetoothManager->device()->deviceType() : bluetoothdevice::BIKE);
|
||||
gpx_preview.clearPath();
|
||||
for (const auto &p : g_list) {
|
||||
gpx_preview.addCoordinate(QGeoCoordinate(p.latitude, p.longitude, p.elevation));
|
||||
@@ -5424,6 +5669,10 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
|
||||
activityNamePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant(QStringLiteral("form-data; name=\"name\"")));
|
||||
|
||||
QString prefix = QStringLiteral("");
|
||||
if (settings.value(QZSettings::strava_date_prefix, QZSettings::default_strava_date_prefix).toBool())
|
||||
prefix = " " + QDate::currentDate().toString(Qt::TextDate);
|
||||
|
||||
// use metadata config if the user selected it
|
||||
QString activityName =
|
||||
QStringLiteral(" ") + settings.value(QZSettings::strava_suffix, QZSettings::default_strava_suffix).toString();
|
||||
@@ -5436,11 +5685,11 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
|
||||
pelotonHandler->current_ride_id;
|
||||
} else {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
|
||||
activityName = QStringLiteral("Run") + activityName;
|
||||
activityName = prefix + QStringLiteral("Run") + activityName;
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
|
||||
activityName = QStringLiteral("Row") + activityName;
|
||||
activityName = prefix + QStringLiteral("Row") + activityName;
|
||||
} else {
|
||||
activityName = QStringLiteral("Ride") + activityName;
|
||||
activityName = prefix + QStringLiteral("Ride") + activityName;
|
||||
}
|
||||
}
|
||||
activityNamePart.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
@@ -5682,7 +5931,7 @@ QOAuth2AuthorizationCodeFlow *homeform::strava_connect() {
|
||||
#elif defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#pragma message "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
strava->setModifyParametersFunction(
|
||||
buildModifyParametersFunction(QUrl(QLatin1String("")), QUrl(QLatin1String(""))));
|
||||
@@ -5739,6 +5988,14 @@ void homeform::setVideoIconVisible(bool value) {
|
||||
emit videoIconVisibleChanged(m_VideoIconVisible);
|
||||
}
|
||||
|
||||
bool homeform::chartIconVisible() { return m_ChartIconVisible; }
|
||||
|
||||
void homeform::setChartIconVisible(bool value) {
|
||||
|
||||
m_ChartIconVisible = value;
|
||||
emit chartIconVisibleChanged(m_ChartIconVisible);
|
||||
}
|
||||
|
||||
int homeform::videoPosition() { return m_VideoPosition; }
|
||||
|
||||
void homeform::setVideoPosition(int value) {
|
||||
@@ -5795,7 +6052,7 @@ void homeform::sendMail() {
|
||||
SmtpClient smtp(STRINGIFY(SMTP_SERVER), 587, SmtpClient::TlsConnection);
|
||||
connect(&smtp, SIGNAL(smtpError(SmtpClient::SmtpError)), this, SLOT(smtpError(SmtpClient::SmtpError)));
|
||||
#else
|
||||
#warning "stmp server is unset!"
|
||||
#pragma message "stmp server is unset!"
|
||||
SmtpClient smtp(QLatin1String(""), 25, SmtpClient::TlsConnection);
|
||||
return;
|
||||
#endif
|
||||
@@ -5807,7 +6064,7 @@ void homeform::sendMail() {
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
smtp.setUser(STRINGIFY(SMTP_USERNAME));
|
||||
#else
|
||||
#warning "smtp username is unset!"
|
||||
#pragma message "smtp username is unset!"
|
||||
return;
|
||||
#endif
|
||||
#ifdef SMTP_PASSWORD
|
||||
@@ -5815,7 +6072,7 @@ void homeform::sendMail() {
|
||||
#define STRINGIFY(x) _STR(x)
|
||||
smtp.setPassword(STRINGIFY(SMTP_PASSWORD));
|
||||
#else
|
||||
#warning "smtp password is unset!"
|
||||
#pragma message "smtp password is unset!"
|
||||
return;
|
||||
#endif
|
||||
|
||||
@@ -5889,8 +6146,30 @@ void homeform::sendMail() {
|
||||
QStringLiteral("Moving Time: ") + bluetoothManager->device()->movingTime().toString() + QStringLiteral("\n");
|
||||
textMessage += QStringLiteral("Weight Loss (") + weightLossUnit + "): " + QString::number(WeightLoss, 'f', 2) +
|
||||
QStringLiteral("\n");
|
||||
textMessage += QStringLiteral("Estimated VO2Max: ") + QString::number(metric::calculateVO2Max(&Session), 'f', 1) +
|
||||
textMessage += QStringLiteral("Estimated VO2Max: ") + QString::number(metric::calculateVO2Max(&Session), 'f', 0) +
|
||||
QStringLiteral("\n");
|
||||
double peak = metric::powerPeak(&Session, 5);
|
||||
double weightKg = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
textMessage += QStringLiteral("5 Seconds Power: ") + QString::number(peak, 'f', 0) +
|
||||
QStringLiteral("W ") + QString::number(peak/weightKg, 'f', 1) + QStringLiteral("W/Kg\n");
|
||||
peak = metric::powerPeak(&Session, 60);
|
||||
textMessage += QStringLiteral("1 Minute Power: ") + QString::number(peak, 'f', 0) +
|
||||
QStringLiteral("W ") + QString::number(peak/weightKg, 'f', 1) + QStringLiteral("W/Kg\n");
|
||||
peak = metric::powerPeak(&Session, 5 * 60);
|
||||
textMessage += QStringLiteral("5 Minutes Power: ") + QString::number(peak, 'f', 0) +
|
||||
QStringLiteral("W ") + QString::number(peak/weightKg, 'f', 1) + QStringLiteral("W/Kg\n");
|
||||
|
||||
// FTP
|
||||
double ftpSetting = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
|
||||
peak = (metric::powerPeak(&Session, 20 * 60) * 0.95) * 0.95;
|
||||
textMessage += QStringLiteral("Estimated FTP: ") + QString::number(peak, 'f', 0) +
|
||||
QStringLiteral("W ");
|
||||
if(peak > ftpSetting) {
|
||||
textMessage += QStringLiteral(" FTP IMPROVED +") + QString::number(peak - ftpSetting, 'f', 0) +
|
||||
QStringLiteral("W!");
|
||||
}
|
||||
textMessage += QStringLiteral("\n");
|
||||
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
textMessage += QStringLiteral("Average Cadence: ") +
|
||||
QString::number(((bike *)bluetoothManager->device())->currentCadence().average(), 'f', 0) +
|
||||
@@ -5959,6 +6238,10 @@ void homeform::sendMail() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SMTP_SERVER
|
||||
textMessage += QStringLiteral("\n\nSMTP server: ") + QString(STRINGIFY(SMTP_SERVER));
|
||||
#endif
|
||||
|
||||
text.setText(textMessage);
|
||||
message.addPart(&text);
|
||||
|
||||
@@ -6139,10 +6422,14 @@ void homeform::saveSettings(const QUrl &filename) {
|
||||
}
|
||||
|
||||
void homeform::loadSettings(const QUrl &filename) {
|
||||
qDebug() << "homeform::loadSettings" << filename;
|
||||
|
||||
QFile file(QQmlFile::urlToLocalFileOrQrc(filename));
|
||||
copyAndroidContentsURI(filename, "settings");
|
||||
|
||||
qDebug() << "homeform::loadSettings" << file.fileName();
|
||||
|
||||
QSettings settings;
|
||||
QSettings settings2Load(filename.toLocalFile(), QSettings::IniFormat);
|
||||
QSettings settings2Load(file.fileName(), QSettings::IniFormat);
|
||||
auto settings2LoadAllKeys = settings2Load.allKeys();
|
||||
for (const QString &s : qAsConst(settings2LoadAllKeys)) {
|
||||
if (!s.contains(QZSettings::cryptoKeySettingsProfiles)) {
|
||||
|
||||
173
src/homeform.h
173
src/homeform.h
@@ -6,6 +6,9 @@
|
||||
#include "fit_profile.hpp"
|
||||
#include "gpx.h"
|
||||
#include "peloton.h"
|
||||
#include "qmdnsengine/browser.h"
|
||||
#include "qmdnsengine/cache.h"
|
||||
#include "qmdnsengine/resolver.h"
|
||||
#include "screencapture.h"
|
||||
#include "sessionline.h"
|
||||
#include "smtpclient/src/SmtpMime"
|
||||
@@ -20,9 +23,17 @@
|
||||
#include <QQuickItem>
|
||||
#include <QQuickItemGrabResult>
|
||||
#include <QTextToSpeech>
|
||||
#include "qmdnsengine/browser.h"
|
||||
#include "qmdnsengine/cache.h"
|
||||
#include "qmdnsengine/resolver.h"
|
||||
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#define STRAVA_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class DataObject : public QObject {
|
||||
|
||||
@@ -137,6 +148,9 @@ class homeform : public QObject {
|
||||
Q_PROPERTY(bool mapsVisible READ mapsVisible NOTIFY mapsVisibleChanged WRITE setMapsVisible)
|
||||
Q_PROPERTY(bool videoIconVisible READ videoIconVisible NOTIFY videoIconVisibleChanged WRITE setVideoIconVisible)
|
||||
Q_PROPERTY(bool videoVisible READ videoVisible NOTIFY videoVisibleChanged WRITE setVideoVisible)
|
||||
Q_PROPERTY(bool chartIconVisible READ chartIconVisible NOTIFY chartIconVisibleChanged WRITE setChartIconVisible)
|
||||
Q_PROPERTY(
|
||||
bool chartFooterVisible READ chartFooterVisible NOTIFY chartFooterVisibleChanged WRITE setChartFooterVisible)
|
||||
Q_PROPERTY(QUrl videoPath READ videoPath NOTIFY videoPathChanged)
|
||||
Q_PROPERTY(int videoPosition READ videoPosition NOTIFY videoPositionChanged WRITE setVideoPosition)
|
||||
Q_PROPERTY(double videoRate READ videoRate NOTIFY videoRateChanged WRITE setVideoRate)
|
||||
@@ -185,6 +199,7 @@ class homeform : public QObject {
|
||||
QObject *stack = rootObject;
|
||||
screenCapture s(reinterpret_cast<QQuickView *>(stack));
|
||||
s.capture(filenameScreenshot);
|
||||
chartImagesFilenames.append(filenameScreenshot);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void save_screenshot_chart(QQuickItem *item, QString filename) {
|
||||
@@ -384,6 +399,8 @@ class homeform : public QObject {
|
||||
bool mapsVisible();
|
||||
bool videoIconVisible();
|
||||
bool videoVisible() { return m_VideoVisible; }
|
||||
bool chartIconVisible();
|
||||
bool chartFooterVisible() { return m_ChartFooterVisible; }
|
||||
int videoPosition();
|
||||
double videoRate();
|
||||
double currentSpeed() {
|
||||
@@ -415,10 +432,15 @@ class homeform : public QObject {
|
||||
}
|
||||
void setLicensePopupVisible(bool value);
|
||||
void setVideoIconVisible(bool value);
|
||||
void setChartIconVisible(bool value);
|
||||
void setVideoVisible(bool value) {
|
||||
m_VideoVisible = value;
|
||||
emit videoVisibleChanged(m_VideoVisible);
|
||||
}
|
||||
void setChartFooterVisible(bool value) {
|
||||
m_ChartFooterVisible = value;
|
||||
emit chartFooterVisibleChanged(m_ChartFooterVisible);
|
||||
}
|
||||
void setVideoPosition(int position); // on startup
|
||||
void videoSeekPosition(int ms); // in realtime
|
||||
void setVideoRate(double rate);
|
||||
@@ -528,73 +550,12 @@ class homeform : public QObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool trainProgramLoadedWithVideo() {
|
||||
return (trainProgram && trainProgram->videoAvailable);
|
||||
}
|
||||
bool trainProgramLoadedWithVideo() { return (trainProgram && trainProgram->videoAvailable); }
|
||||
|
||||
QString getStravaAuthUrl() { return stravaAuthUrl; }
|
||||
bool stravaWebVisible() { return stravaAuthWebVisible; }
|
||||
trainprogram *trainingProgram() { return trainProgram; }
|
||||
|
||||
private:
|
||||
static homeform *m_singleton;
|
||||
TemplateInfoSenderBuilder *userTemplateManager = nullptr;
|
||||
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
|
||||
QList<QObject *> dataList;
|
||||
QList<SessionLine> Session;
|
||||
bluetooth *bluetoothManager;
|
||||
QQmlApplicationEngine *engine;
|
||||
trainprogram *trainProgram = nullptr;
|
||||
trainprogram *previewTrainProgram = nullptr;
|
||||
QString backupFitFileName =
|
||||
QStringLiteral("QZ-backup-") +
|
||||
QDateTime::currentDateTime().toString().replace(QStringLiteral(":"), QStringLiteral("_")) +
|
||||
QStringLiteral(".fit");
|
||||
|
||||
int m_topBarHeight = 120;
|
||||
QString m_info = QStringLiteral("Connecting...");
|
||||
bool m_labelHelp = true;
|
||||
bool m_generalPopupVisible = false;
|
||||
bool m_LicensePopupVisible = false;
|
||||
bool m_MapsVisible = false;
|
||||
bool m_VideoIconVisible = false;
|
||||
bool m_VideoVisible = false;
|
||||
int m_VideoPosition = 0;
|
||||
double m_VideoRate = 1;
|
||||
QOAuth2AuthorizationCodeFlow *strava = nullptr;
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
QOAuthHttpServerReplyHandler *stravaReplyHandler = nullptr;
|
||||
|
||||
bool paused = false;
|
||||
bool stopped = false;
|
||||
bool lapTrigger = false;
|
||||
|
||||
peloton *pelotonHandler = nullptr;
|
||||
bool m_pelotonAskStart = false;
|
||||
QString m_pelotonProvider = "";
|
||||
QString m_toastRequested = "";
|
||||
int m_pelotonLoginState = -1;
|
||||
int m_pzpLoginState = -1;
|
||||
QString stravaPelotonActivityName;
|
||||
QString stravaPelotonInstructorName;
|
||||
QString stravaWorkoutName = "";
|
||||
QUrl movieFileName;
|
||||
FIT_SPORT stravaPelotonWorkoutType = FIT_SPORT_INVALID;
|
||||
QString activityDescription;
|
||||
QString pelotonAskedName = QStringLiteral("");
|
||||
QString pelotonAskedInstructor = QStringLiteral("");
|
||||
QString pelotonAbortedName = QStringLiteral("");
|
||||
QString pelotonAbortedInstructor = QStringLiteral("");
|
||||
|
||||
QString lastFitFileSaved = QLatin1String("");
|
||||
QString lastTrainProgramFileSaved = QLatin1String("");
|
||||
|
||||
QList<QString> chartImagesFilenames;
|
||||
|
||||
bool m_autoresistance = true;
|
||||
bool m_stopRequested = false;
|
||||
bool m_startRequested = false;
|
||||
|
||||
|
||||
DataObject *speed;
|
||||
DataObject *inclination;
|
||||
DataObject *cadence;
|
||||
@@ -657,6 +618,68 @@ class homeform : public QObject {
|
||||
DataObject *preset_inclination_5;
|
||||
DataObject *pace_last500m;
|
||||
|
||||
private:
|
||||
static homeform *m_singleton;
|
||||
TemplateInfoSenderBuilder *userTemplateManager = nullptr;
|
||||
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
|
||||
QList<QObject *> dataList;
|
||||
QList<SessionLine> Session;
|
||||
bluetooth *bluetoothManager;
|
||||
QQmlApplicationEngine *engine;
|
||||
trainprogram *trainProgram = nullptr;
|
||||
trainprogram *previewTrainProgram = nullptr;
|
||||
QString backupFitFileName =
|
||||
QStringLiteral("QZ-backup-") +
|
||||
QDateTime::currentDateTime().toString().replace(QStringLiteral(":"), QStringLiteral("_")) +
|
||||
QStringLiteral(".fit");
|
||||
|
||||
int m_topBarHeight = 120;
|
||||
QString m_info = QStringLiteral("Connecting...");
|
||||
bool m_labelHelp = true;
|
||||
bool m_generalPopupVisible = false;
|
||||
bool m_LicensePopupVisible = false;
|
||||
bool m_MapsVisible = false;
|
||||
bool m_VideoIconVisible = false;
|
||||
bool m_VideoVisible = false;
|
||||
bool m_ChartFooterVisible = false;
|
||||
bool m_ChartIconVisible = false;
|
||||
int m_VideoPosition = 0;
|
||||
double m_VideoRate = 1;
|
||||
QOAuth2AuthorizationCodeFlow *strava = nullptr;
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
QOAuthHttpServerReplyHandler *stravaReplyHandler = nullptr;
|
||||
|
||||
bool paused = false;
|
||||
bool stopped = false;
|
||||
bool lapTrigger = false;
|
||||
|
||||
peloton *pelotonHandler = nullptr;
|
||||
bool m_pelotonAskStart = false;
|
||||
QString m_pelotonProvider = "";
|
||||
QString m_toastRequested = "";
|
||||
int m_pelotonLoginState = -1;
|
||||
int m_pzpLoginState = -1;
|
||||
QString stravaPelotonActivityName;
|
||||
QString stravaPelotonInstructorName;
|
||||
QString stravaWorkoutName = "";
|
||||
QUrl movieFileName;
|
||||
FIT_SPORT stravaPelotonWorkoutType = FIT_SPORT_INVALID;
|
||||
QString activityDescription;
|
||||
QString pelotonAskedName = QStringLiteral("");
|
||||
QString pelotonAskedInstructor = QStringLiteral("");
|
||||
QString pelotonAbortedName = QStringLiteral("");
|
||||
QString pelotonAbortedInstructor = QStringLiteral("");
|
||||
|
||||
QString lastFitFileSaved = QLatin1String("");
|
||||
QString lastTrainProgramFileSaved = QLatin1String("");
|
||||
|
||||
QList<QString> chartImagesFilenames;
|
||||
|
||||
bool m_autoresistance = true;
|
||||
bool m_stopRequested = false;
|
||||
bool m_startRequested = false;
|
||||
bool m_overridePower = false;
|
||||
|
||||
QTimer *timer;
|
||||
QTimer *backupTimer;
|
||||
|
||||
@@ -672,6 +695,9 @@ class homeform : public QObject {
|
||||
|
||||
static quint64 cryptoKeySettingsProfiles();
|
||||
|
||||
static QString copyAndroidContentsURI(QUrl file, QString subfolder);
|
||||
static QString getFileNameFromContentUri(const QString &uriString);
|
||||
|
||||
int16_t fanOverride = 0;
|
||||
|
||||
void update();
|
||||
@@ -696,15 +722,17 @@ class homeform : public QObject {
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
bool floating_open = false;
|
||||
#endif
|
||||
|
||||
QMdnsEngine::Browser* iphone_browser = nullptr;
|
||||
QMdnsEngine::Resolver* iphone_resolver = nullptr;
|
||||
#ifndef Q_OS_IOS
|
||||
QMdnsEngine::Browser *iphone_browser = nullptr;
|
||||
QMdnsEngine::Resolver *iphone_resolver = nullptr;
|
||||
QMdnsEngine::Server iphone_server;
|
||||
QMdnsEngine::Cache iphone_cache;
|
||||
QTcpSocket* iphone_socket = nullptr;
|
||||
QTcpSocket *iphone_socket = nullptr;
|
||||
QMdnsEngine::Service iphone_service;
|
||||
QHostAddress iphone_address;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void aboutToQuit();
|
||||
@@ -734,6 +762,9 @@ class homeform : public QObject {
|
||||
void deviceConnected(QBluetoothDeviceInfo b);
|
||||
void ftmsAccessoryConnected(smartspin2k *d);
|
||||
void trainprogram_open_clicked(const QUrl &fileName);
|
||||
void trainprogram_open_other_folder(const QUrl &fileName);
|
||||
void gpx_open_other_folder(const QUrl &fileName);
|
||||
void profile_open_clicked(const QUrl &fileName);
|
||||
void trainprogram_preview(const QUrl &fileName);
|
||||
void gpxpreview_open_clicked(const QUrl &fileName);
|
||||
void trainprogram_zwo_loaded(const QString &comp);
|
||||
@@ -804,6 +835,8 @@ class homeform : public QObject {
|
||||
void videoPositionChanged(int value);
|
||||
void videoPathChanged(QUrl value);
|
||||
void videoRateChanged(double value);
|
||||
void chartIconVisibleChanged(bool value);
|
||||
void chartFooterVisibleChanged(bool value);
|
||||
void currentSpeedChanged(double value);
|
||||
void mapsVisibleChanged(bool value);
|
||||
void autoResistanceChanged(bool value);
|
||||
|
||||
@@ -144,6 +144,7 @@ void horizongr7bike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -195,7 +196,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
@@ -206,12 +207,12 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
return;
|
||||
@@ -231,7 +232,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
@@ -286,13 +287,13 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
1000.0;*/
|
||||
if (firstPacket)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
index += 3;
|
||||
} else {
|
||||
if (firstPacket)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
}
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
@@ -341,7 +342,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
}
|
||||
|
||||
@@ -354,7 +355,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(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 {
|
||||
@@ -380,7 +381,7 @@ void horizongr7bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
|
||||
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
|
||||
|
||||
@@ -27,10 +27,6 @@ horizontreadmill::horizontreadmill(bool noWriteResistance, bool noHeartService)
|
||||
|
||||
testProfileCRC();
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
@@ -835,7 +831,26 @@ void horizontreadmill::update() {
|
||||
settings.value(QZSettings::horizon_treadmill_7_8, QZSettings::default_horizon_treadmill_7_8).toBool();
|
||||
bool horizon_paragon_x =
|
||||
settings.value(QZSettings::horizon_paragon_x, QZSettings::default_horizon_paragon_x).toBool();
|
||||
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
|
||||
bool power_sensor = !(settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")));
|
||||
update_metrics(!power_sensor, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
|
||||
|
||||
if (firstDistanceCalculated) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
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)lastRefreshCharacteristicChanged.msecsTo(
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
}
|
||||
|
||||
// updating the treadmill console every second
|
||||
if (sec1Update++ == (500 / refresh->interval())) {
|
||||
@@ -845,7 +860,8 @@ void horizontreadmill::update() {
|
||||
}
|
||||
|
||||
if (requestSpeed != -1) {
|
||||
bool minSpeed = fabs(requestSpeed - float_one_point_round(currentSpeed().value())) >= minStepSpeed();
|
||||
bool minSpeed =
|
||||
fabs(requestSpeed - float_one_point_round(currentSpeed().value())) >= (minStepSpeed() - 0.09);
|
||||
bool forceSpeedNeed = checkIfForceSpeedNeeding(requestSpeed);
|
||||
qDebug() << "requestSpeed=" << requestSpeed << minSpeed << forceSpeedNeed
|
||||
<< float_one_point_round(currentSpeed().value());
|
||||
@@ -1099,12 +1115,14 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
|
||||
false, false);
|
||||
if(!anplus_treadmill) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
|
||||
false, false);
|
||||
}
|
||||
|
||||
uint8_t writeS[] = {FTMS_SET_TARGET_SPEED, 0x00, 0x00};
|
||||
writeS[1] = ((uint16_t)(requestSpeed * 100)) & 0xFF;
|
||||
@@ -1160,12 +1178,14 @@ void horizontreadmill::forceIncline(double requestIncline) {
|
||||
}
|
||||
} else if (gattFTMSService) {
|
||||
// for the Tecnogym Myrun
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
|
||||
false, false);
|
||||
if(!anplus_treadmill) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
|
||||
false);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start simulation",
|
||||
false, false);
|
||||
}
|
||||
|
||||
uint8_t writeS[] = {FTMS_SET_TARGET_INCLINATION, 0x00, 0x00};
|
||||
if (kettler_treadmill) {
|
||||
@@ -1283,6 +1303,8 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + characteristic.uuid().toString() + " " + QString::number(newValue.length()) +
|
||||
" " + newValue.toHex(' '));
|
||||
|
||||
@@ -1330,14 +1352,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
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()));
|
||||
|
||||
if (firstDistanceCalculated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
distanceEval = true;
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() > 70 &&
|
||||
@@ -1357,14 +1379,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
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()));
|
||||
|
||||
if (firstDistanceCalculated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
distanceEval = true;
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() == 29 &&
|
||||
@@ -1381,14 +1403,14 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
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()));
|
||||
|
||||
if (firstDistanceCalculated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
distanceEval = true;
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0xFFF4) && newValue.length() > 10 &&
|
||||
@@ -1459,7 +1481,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
{
|
||||
if (firstDistanceCalculated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
distanceEval = true;
|
||||
}
|
||||
|
||||
@@ -1506,7 +1528,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
distanceEval = true;
|
||||
}
|
||||
@@ -1522,7 +1544,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
if (Flags.heartRate) {
|
||||
if (index < newValue.length()) {
|
||||
|
||||
heart = ((double)((newValue.at(index))));
|
||||
heart = ((double)(((uint8_t)newValue.at(index))));
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(heart));
|
||||
} else {
|
||||
emit debug(QStringLiteral("Error on parsing heart!"));
|
||||
@@ -1602,7 +1624,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
} else {
|
||||
if (firstDistanceCalculated)
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
distanceEval = true;
|
||||
}
|
||||
|
||||
@@ -1681,7 +1703,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
200.0) /
|
||||
(60000.0 /
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in
|
||||
// kg * 3.5) / 200 ) / 60
|
||||
distanceEval = true;
|
||||
}
|
||||
@@ -1695,7 +1717,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(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 {
|
||||
@@ -1736,7 +1758,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
|
||||
if (distanceEval) {
|
||||
firstDistanceCalculated = true;
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
lastRefreshCharacteristicChanged = now;
|
||||
}
|
||||
|
||||
if (m_control->error() != QLowEnergyController::NoError) {
|
||||
@@ -1915,11 +1937,24 @@ void horizontreadmill::serviceScanDone(void) {
|
||||
firstStateChanged = 0;
|
||||
auto services_list = m_control->services();
|
||||
QBluetoothUuid ftmsService((quint16)0x1826);
|
||||
QBluetoothUuid CustomService((quint16)0xFFF0);
|
||||
|
||||
for (const QBluetoothUuid &s : qAsConst(services_list)) {
|
||||
gattCommunicationChannelService.append(m_control->createServiceObject(s));
|
||||
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
|
||||
&horizontreadmill::stateChanged);
|
||||
gattCommunicationChannelService.constLast()->discoverDetails();
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1943,6 +1978,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
// horizon treadmill and F80 treadmill, so if we want to add inclination support we have to separate the 2
|
||||
// devices
|
||||
// ***************************************************************************************************************
|
||||
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
|
||||
device.address().toString() + ')');
|
||||
{
|
||||
@@ -1954,8 +1990,19 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} 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("ANPLUS-"))) {
|
||||
anplus_treadmill = true;
|
||||
qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!");
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
} else {
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &horizontreadmill::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &horizontreadmill::serviceScanDone);
|
||||
|
||||
@@ -88,6 +88,7 @@ class horizontreadmill : public treadmill {
|
||||
|
||||
bool mobvoi_treadmill = false;
|
||||
bool kettler_treadmill = false;
|
||||
bool anplus_treadmill = false;
|
||||
|
||||
void testProfileCRC();
|
||||
void updateProfileCRC();
|
||||
|
||||
43
src/inner_templates/chartjs/chartlive.htm
Normal file
43
src/inner_templates/chartjs/chartlive.htm
Normal file
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Line Chart</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">
|
||||
<script src="resize-observer.min.js"></script>
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script src="chartjs.3.4.1.min.js"></script>
|
||||
<script src="moment.js"></script>
|
||||
<script src="chartjs-adapter-moment.js"></script>
|
||||
<script src="chartjs-plugin-annotation.min.js"></script>
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="dochartlive.js"></script>
|
||||
<script src="html2canvas.min.js"></script>
|
||||
<style>
|
||||
canvas{
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
html, body
|
||||
{
|
||||
-ms-content-zooming:none;
|
||||
touch-action: none;
|
||||
content-zooming: none;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: none;
|
||||
overflow-x: none;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#1d2330">
|
||||
<div id="divcanvas" style="width:100vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -177,7 +177,7 @@ function process_arr(arr) {
|
||||
$('.summary_watts_avg').text(Math.floor(watts_avg) + ' W');
|
||||
$('.summary_jouls').text(Math.floor(jouls / 1000.0) + ' kJ');
|
||||
$('.summary_calories').text(Math.floor(calories) + ' kcal');
|
||||
$('.summary_distance').text(Math.floor(distance * miles) + (miles === 1 ? ' km' : ' mi'));
|
||||
$('.summary_distance').text((distance * miles).toFixed(1) + (miles === 1 ? ' km' : ' mi'));
|
||||
$('.summary_cadence_avg').text(Math.floor(cadence_avg) + ' rpm');
|
||||
$('.summary_resistance_avg').text(Math.floor(peloton_resistance_avg) + ' lvl');
|
||||
|
||||
|
||||
530
src/inner_templates/chartjs/dochartlive.js
Normal file
530
src/inner_templates/chartjs/dochartlive.js
Normal file
@@ -0,0 +1,530 @@
|
||||
window.chartColors = {
|
||||
red: 'rgb(255, 29, 0)',
|
||||
redt: 'rgb(255, 29, 0, 0.55)',
|
||||
orange: 'rgb(255, 159, 64)',
|
||||
oranget: 'rgb(255, 159, 64, 0.55)',
|
||||
darkorange: 'rgb(255, 140, 0)',
|
||||
darkoranget: 'rgb(255, 140, 0, 0.55)',
|
||||
orangered: 'rgb(255, 69, 0)',
|
||||
orangeredt: 'rgb(255, 69, 0, 0.55)',
|
||||
yellow: 'rgb(255, 205, 86)',
|
||||
yellowt: 'rgb(255, 205, 86, 0.55)',
|
||||
green: 'rgb(75, 192, 192)',
|
||||
greent: 'rgb(75, 192, 192, 0.55)',
|
||||
blue: 'rgb(54, 162, 235)',
|
||||
purple: 'rgb(153, 102, 255)',
|
||||
grey: 'rgb(201, 203, 207)',
|
||||
greyt: 'rgb(201, 203, 207, 0.55)',
|
||||
white: 'rgb(255, 255, 255)',
|
||||
whitet: 'rgb(255, 255, 255, 0.55)',
|
||||
limegreen: 'rgb(50, 205, 50)',
|
||||
limegreent: 'rgb(50, 205, 50, 0.55)',
|
||||
gold: 'rgb(255, 215, 0)',
|
||||
goldt: 'rgb(255, 215, 0, 0.55)',
|
||||
black: 'rgb(0, 0, 0)',
|
||||
blackt: 'rgb(0, 0, 0, 0.55)',
|
||||
lightsteelblue: 'rgb(176,192,222)',
|
||||
lightsteelbluet: 'rgb(176,192,222, 0.55)',
|
||||
};
|
||||
|
||||
var ftp = 200;
|
||||
var ftpZones = [];
|
||||
var maxHeartRate = 190;
|
||||
var heartZones = [];
|
||||
var miles = 1;
|
||||
var powerChart = null;
|
||||
|
||||
function process_trainprogram(arr) {
|
||||
let powerWorkout = false;
|
||||
let elapsed = 0;
|
||||
|
||||
for (let el of arr.list) {
|
||||
if(el.power != -1) {
|
||||
powerWorkout = true;
|
||||
for (i=0; i<el.duration_s; i++) {
|
||||
powerChart.data.datasets[1].data.push({x: elapsed++, y: el.power});
|
||||
}
|
||||
}
|
||||
}
|
||||
powerChart.options.scales.x.max = elapsed;
|
||||
powerChart.update();
|
||||
}
|
||||
|
||||
function process_arr(arr) {
|
||||
let ctx = document.getElementById('canvas').getContext('2d');
|
||||
let div = document.getElementById('divcanvas');
|
||||
|
||||
let reqpower = [];
|
||||
let reqcadence = [];
|
||||
let heart = [];
|
||||
let cadence = [];
|
||||
let speed = [];
|
||||
let inclination = [];
|
||||
let resistance = [];
|
||||
let watts = [];
|
||||
let reqresistance = [];
|
||||
let pelotonresistance = [];
|
||||
let pelotonreqresistance = [];
|
||||
let distributionPowerZones = [];
|
||||
let maxEl = 0;
|
||||
let saveScreenshot = [];
|
||||
let workoutName = '';
|
||||
let workoutStartDate = '';
|
||||
let instructorName = '';
|
||||
let watts_avg = 0;
|
||||
let watts_max = 0;
|
||||
let heart_avg = 0;
|
||||
let heart_max = 0;
|
||||
let jouls = 0;
|
||||
let deviceType = 0;
|
||||
let cadence_avg = 0;
|
||||
let peloton_resistance_avg = 0;
|
||||
let calories = 0;
|
||||
let distance = 0;
|
||||
saveScreenshot[0] = false;
|
||||
saveScreenshot[1] = false;
|
||||
saveScreenshot[2] = false;
|
||||
saveScreenshot[3] = false;
|
||||
saveScreenshot[4] = false;
|
||||
saveScreenshot[5] = false;
|
||||
saveScreenshot[6] = false;
|
||||
saveScreenshot[7] = false;
|
||||
distributionPowerZones[0] = 0;
|
||||
distributionPowerZones[1] = 0;
|
||||
distributionPowerZones[2] = 0;
|
||||
distributionPowerZones[3] = 0;
|
||||
distributionPowerZones[4] = 0;
|
||||
distributionPowerZones[5] = 0;
|
||||
distributionPowerZones[6] = 0;
|
||||
|
||||
for (let el of arr) {
|
||||
let wattel = {};
|
||||
let reqpowerel = {};
|
||||
let reqcadenceel = {};
|
||||
let heartel = {};
|
||||
let cadenceel = {};
|
||||
let resistanceel = {};
|
||||
let reqresistanceel = {};
|
||||
let pelotonresistanceel = {};
|
||||
let pelotonreqresistanceel = {};
|
||||
let speedel = {};
|
||||
let inclinationel = {};
|
||||
let time = el.elapsed_s + el.elapsed_m * 60 + el.elapsed_h * 3600;
|
||||
workoutName = el.workoutName;
|
||||
workoutStartDate = el.workoutStartDate;
|
||||
instructorName = el.instructorName;
|
||||
watts_avg = el.watts_avg;
|
||||
watts_max = el.watts_max;
|
||||
heart_avg = el.heart_avg;
|
||||
heart_max = el.heart_max;
|
||||
jouls = el.jouls;
|
||||
deviceType = el.deviceType;
|
||||
peloton_resistance_avg = el.peloton_resistance_avg;
|
||||
cadence_avg = el.cadence_avg;
|
||||
distance = el.distance;
|
||||
calories = el.calories;
|
||||
maxEl = time;
|
||||
wattel.x = time;
|
||||
wattel.y = el.watts;
|
||||
watts.push(wattel);
|
||||
if(el.watts < ftpZones[0])
|
||||
distributionPowerZones[0]++;
|
||||
else if(el.watts < ftpZones[1])
|
||||
distributionPowerZones[1]++;
|
||||
else if(el.watts < ftpZones[2])
|
||||
distributionPowerZones[2]++;
|
||||
else if(el.watts < ftpZones[3])
|
||||
distributionPowerZones[3]++;
|
||||
else if(el.watts < ftpZones[4])
|
||||
distributionPowerZones[4]++;
|
||||
else if(el.watts < ftpZones[5])
|
||||
distributionPowerZones[5]++;
|
||||
else
|
||||
distributionPowerZones[6]++;
|
||||
reqpowerel.x = time;
|
||||
reqpowerel.y = el.req_power;
|
||||
// they are added from the process_trainprogram()
|
||||
//reqpower.push(reqpowerel);
|
||||
|
||||
reqcadenceel.x = time;
|
||||
reqcadenceel.y = el.req_cadence;
|
||||
reqcadence.push(reqcadenceel);
|
||||
heartel.x = time;
|
||||
heartel.y = el.heart;
|
||||
heart.push(heartel);
|
||||
cadenceel.x = time;
|
||||
cadenceel.y = el.cadence;
|
||||
cadence.push(cadenceel);
|
||||
resistanceel.x = time;
|
||||
resistanceel.y = el.resistance;
|
||||
resistance.push(resistanceel);
|
||||
reqresistanceel.x = time;
|
||||
reqresistanceel.y = el.req_resistance;
|
||||
reqresistance.push(reqresistanceel);
|
||||
|
||||
pelotonresistanceel.x = time;
|
||||
pelotonresistanceel.y = el.peloton_resistance;
|
||||
pelotonresistance.push(pelotonresistanceel);
|
||||
pelotonreqresistanceel.x = time;
|
||||
pelotonreqresistanceel.y = el.peloton_req_resistance;
|
||||
pelotonreqresistance.push(pelotonreqresistanceel);
|
||||
|
||||
speedel.x = time;
|
||||
speedel.y = el.speed;
|
||||
speed.push(speedel);
|
||||
inclinationel.x = time;
|
||||
inclinationel.y = el.inclination;
|
||||
inclination.push(inclinationel);
|
||||
}
|
||||
|
||||
const backgroundFill = {
|
||||
id: 'custom_canvas_background_color',
|
||||
beforeDraw: (chart) => {
|
||||
const ctx = chart.canvas.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, chart.width, chart.height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
let config = {
|
||||
type: 'line',
|
||||
plugins: [backgroundFill],
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Watts',
|
||||
backgroundColor: window.chartColors.red,
|
||||
borderColor: window.chartColors.red,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
data: watts,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
segment: {
|
||||
borderColor: ctx => ctx.p0.parsed.y < ftpZones[0] && ctx.p1.parsed.y < ftpZones[0] ? window.chartColors.grey :
|
||||
ctx.p0.parsed.y < ftpZones[1] && ctx.p1.parsed.y < ftpZones[1] ? window.chartColors.limegreen :
|
||||
ctx.p0.parsed.y < ftpZones[2] && ctx.p1.parsed.y < ftpZones[2] ? window.chartColors.gold :
|
||||
ctx.p0.parsed.y < ftpZones[3] && ctx.p1.parsed.y < ftpZones[3] ? window.chartColors.orange :
|
||||
ctx.p0.parsed.y < ftpZones[4] && ctx.p1.parsed.y < ftpZones[4] ? window.chartColors.darkorange :
|
||||
ctx.p0.parsed.y < ftpZones[5] && ctx.p1.parsed.y < ftpZones[5] ? window.chartColors.orangered :
|
||||
window.chartColors.red,
|
||||
}
|
||||
}, {
|
||||
label: 'Req. Watts',
|
||||
backgroundColor: window.chartColors.black,
|
||||
borderColor: window.chartColors.black,
|
||||
//cubicInterpolationMode: 'monotone',
|
||||
data: reqpower,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
aspectRatio: div.width / div.height,
|
||||
grid: {
|
||||
zeroLineColor: 'rgba(0,255,0,1)'
|
||||
},
|
||||
plugins: {
|
||||
/*
|
||||
title:{
|
||||
display:true,
|
||||
backgroundColor: "#1d2330",
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2
|
||||
},
|
||||
text:'Watt'
|
||||
},*/
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
annotation: {
|
||||
annotations: {
|
||||
box1: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: 0,
|
||||
yMax: ftpZones[0],
|
||||
backgroundColor: "#d6d6d620"
|
||||
},
|
||||
box2: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[0],
|
||||
yMax: ftpZones[1],
|
||||
backgroundColor: window.chartColors.limegreent,
|
||||
},
|
||||
box3: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[1],
|
||||
yMax: ftpZones[2],
|
||||
backgroundColor: window.chartColors.goldt,
|
||||
},
|
||||
box4: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[2],
|
||||
yMax: ftpZones[3],
|
||||
backgroundColor: window.chartColors.oranget,
|
||||
},
|
||||
box5: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[3],
|
||||
yMax: ftpZones[4],
|
||||
backgroundColor: window.chartColors.darkoranget,
|
||||
},
|
||||
box6: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[4],
|
||||
yMax: ftpZones[5],
|
||||
backgroundColor: window.chartColors.orangeredt,
|
||||
},
|
||||
box7: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[5],
|
||||
yMax: (watts_max > ftpZones[3] * 2 ? watts_max + 10 : ftpZones[3] * 2),
|
||||
backgroundColor: window.chartColors.redt,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: true
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Time'
|
||||
},
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback: function(value, index, values) {
|
||||
return value !== 0 ? Math.floor(value / 3600).toString().padStart(2, "0") + ":" + Math.floor((value / 60) - (Math.floor(value / 3600) * 60)).toString().padStart(2, "0") : "";
|
||||
},
|
||||
padding: -20,
|
||||
//stepSize: 300,
|
||||
align: "end",
|
||||
},
|
||||
//max: maxEl,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Watt'
|
||||
},
|
||||
min: 0,
|
||||
max: (watts_max > ftpZones[4] + 10 ? watts_max + 10 : ftpZones[4] + 10),
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
autoSkip: false,
|
||||
callback: value => [ftpZones[0] * 0.8, ftpZones[0], ftpZones[1], ftpZones[2], ftpZones[3], ftpZones[4], ftpZones[5]].includes(value) ?
|
||||
value === ftpZones[0] * 0.8 ? 'zone 1' :
|
||||
value === ftpZones[0] ? 'zone 2' :
|
||||
value === ftpZones[1] ? 'zone 3' :
|
||||
value === ftpZones[2] ? 'zone 4' :
|
||||
value === ftpZones[3] ? 'zone 5' :
|
||||
value === ftpZones[4] ? 'zone 6' :
|
||||
value === ftpZones[5] ? 'zone 7' : undefined : undefined,
|
||||
color: 'black',
|
||||
padding: -50,
|
||||
align: 'end',
|
||||
z: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
powerChart = new Chart(ctx, config);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
el = new MainWSQueueElement({
|
||||
msg: null
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 2000, 1);
|
||||
el.enqueue().then(process_workout).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
function process_workout(arr) {
|
||||
powerChart.data.datasets[0].data.push({x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600), y: arr.watts});
|
||||
powerChart.update();
|
||||
refresh();
|
||||
}
|
||||
|
||||
function dochart_init() {
|
||||
onSettingsOK = true;
|
||||
keys_arr = ['ftp', 'miles_unit', 'age', 'heart_rate_zone1', 'heart_rate_zone2', 'heart_rate_zone3', 'heart_rate_zone4', 'heart_max_override_enable', 'heart_max_override_value']
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getsettings',
|
||||
content: {
|
||||
keys: keys_arr
|
||||
}
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getsettings') {
|
||||
var heart_max_override_enable = false;
|
||||
var heart_max_override_value = 195;
|
||||
var heart_rate_zone1 = 0;
|
||||
var heart_rate_zone2 = 0;
|
||||
var heart_rate_zone3 = 0;
|
||||
var heart_rate_zone4 = 0;
|
||||
|
||||
for (let key of keys_arr) {
|
||||
if (msg.content[key] === undefined)
|
||||
return null;
|
||||
if (key === 'ftp') {
|
||||
ftp = msg.content[key];
|
||||
ftpZones[0] = Math.round(ftp * 0.55);
|
||||
ftpZones[1] = Math.round(ftp * 0.75);
|
||||
ftpZones[2] = Math.round(ftp * 0.90);
|
||||
ftpZones[3] = Math.round(ftp * 1.05);
|
||||
ftpZones[4] = Math.round(ftp * 1.20);
|
||||
ftpZones[5] = Math.round(ftp * 1.50);
|
||||
} else if (key === 'age') {
|
||||
age = msg.content[key];
|
||||
maxHeartRate = 220 - age;
|
||||
} else if (key === 'heart_max_override_enable') {
|
||||
heart_max_override_enable = msg.content[key];
|
||||
} else if (key === 'heart_max_override_value') {
|
||||
heart_max_override_value = msg.content[key];
|
||||
} else if (key === 'heart_rate_zone1') {
|
||||
heart_rate_zone1 = msg.content[key];
|
||||
heartZones[0] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone2') {
|
||||
heart_rate_zone2 = msg.content[key];
|
||||
heartZones[1] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone3') {
|
||||
heart_rate_zone3 = msg.content[key];
|
||||
heartZones[2] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone4') {
|
||||
heart_rate_zone4 = msg.content[key];
|
||||
heartZones[3] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'miles_unit') {
|
||||
if(msg.content[key] === true || msg.content[key] === 'true')
|
||||
miles = 0.621371;
|
||||
}
|
||||
}
|
||||
if(heart_max_override_enable) {
|
||||
maxHeartRate = heart_max_override_value;
|
||||
heartZones[0] = Math.round(maxHeartRate * (heart_rate_zone1 / 100));
|
||||
heartZones[1] = Math.round(maxHeartRate * (heart_rate_zone2 / 100));
|
||||
heartZones[2] = Math.round(maxHeartRate * (heart_rate_zone3 / 100));
|
||||
heartZones[3] = Math.round(maxHeartRate * (heart_rate_zone4 / 100));
|
||||
}
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 5000, 3);
|
||||
el.enqueue().then(onSettingsOK).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
})
|
||||
|
||||
el = new MainWSQueueElement({
|
||||
msg: 'getsessionarray'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getsessionarray') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_arr).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
|
||||
el = new MainWSQueueElement({
|
||||
msg: 'gettrainingprogram'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_gettrainingprogram') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_trainprogram).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$(window).on('load', function () {
|
||||
dochart_init(); return;
|
||||
|
||||
// DEBUG
|
||||
ftpZones[0] = Math.round(ftp * 0.55);
|
||||
ftpZones[1] = Math.round(ftp * 0.75);
|
||||
ftpZones[2] = Math.round(ftp * 0.90);
|
||||
ftpZones[3] = Math.round(ftp * 1.05);
|
||||
ftpZones[4] = Math.round(ftp * 1.20);
|
||||
ftpZones[5] = Math.round(ftp * 1.50);
|
||||
|
||||
heartZones[0] = 110;
|
||||
heartZones[1] = 130;
|
||||
heartZones[2] = 150;
|
||||
heartZones[3] = 170;
|
||||
|
||||
arr = [{'watts': 50, 'req_power': 150, 'elapsed_s':0,'elapsed_m':0,'elapsed_h':0, 'heart':90, 'resistance': 10, 'req_resistance': 15, 'cadence': 80, 'req_cadence': 90, 'speed': 10, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 60, 'req_power': 150, 'elapsed_s':1,'elapsed_m':1,'elapsed_h':0, 'heart':92, 'resistance': 11, 'req_resistance': 30, 'cadence': 90, 'req_cadence': 100, 'speed': 8, 'inclination': 2, 'peloton_resistance': 20, 'peloton_req_resistance': 25},
|
||||
{'watts': 70, 'req_power': 170, 'elapsed_s':2,'elapsed_m':2,'elapsed_h':0, 'heart':110, 'resistance': 12, 'req_resistance': 40, 'cadence': 100, 'req_cadence': 90, 'speed': 9, 'inclination': 2.5, 'peloton_resistance': 30, 'peloton_req_resistance': 35},
|
||||
{'watts': 140, 'req_power': 170, 'elapsed_s':3,'elapsed_m':3,'elapsed_h':0, 'heart':115, 'resistance': 16, 'req_resistance': 41, 'cadence': 90, 'req_cadence': 95, 'speed': 11, 'inclination': 1, 'peloton_resistance': 40, 'peloton_req_resistance': 45},
|
||||
{'watts': 130, 'req_power': 170, 'elapsed_s':4,'elapsed_m':4,'elapsed_h':0, 'heart':130, 'resistance': 18, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 95, 'speed': 10, 'inclination': 4, 'peloton_resistance': 50, 'peloton_req_resistance': 55},
|
||||
{'watts': 160, 'req_power': 170, 'elapsed_s':5,'elapsed_m':5,'elapsed_h':0, 'heart':135, 'resistance': 22, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 95, 'speed': 12, 'inclination': 1, 'peloton_resistance': 60, 'peloton_req_resistance': 15},
|
||||
{'watts': 180, 'req_power': 130, 'elapsed_s':6,'elapsed_m':6,'elapsed_h':0, 'heart':140, 'resistance': 31, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 90, 'speed': 10, 'inclination': 3, 'peloton_resistance': 70, 'peloton_req_resistance': 15},
|
||||
{'watts': 120, 'req_power': 130, 'elapsed_s':7,'elapsed_m':7,'elapsed_h':0, 'heart':150, 'resistance': 18, 'req_resistance': 35, 'cadence': 95, 'req_cadence': 80, 'speed': 10, 'inclination': 4, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 190, 'req_power': 150, 'elapsed_s':1,'elapsed_m':8,'elapsed_h':0, 'heart':155, 'resistance': 17, 'req_resistance': 35, 'cadence': 95, 'req_cadence': 80, 'speed': 13, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 195, 'req_power': 170, 'elapsed_s':2,'elapsed_m':9,'elapsed_h':0, 'heart':165, 'resistance': 19, 'req_resistance': 30, 'cadence': 80, 'req_cadence': 80, 'speed': 12, 'inclination': 3, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 200, 'req_power': 170, 'elapsed_s':3,'elapsed_m':10,'elapsed_h':0, 'heart':153, 'resistance': 20, 'req_resistance': 25, 'cadence': 90, 'req_cadence': 90, 'speed': 10, 'inclination': 2, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 206, 'req_power': 170, 'elapsed_s':4,'elapsed_m':11,'elapsed_h':0, 'heart':152, 'resistance': 21, 'req_resistance': 35, 'cadence': 90, 'req_cadence': 90, 'speed': 12, 'inclination': 7, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 211, 'req_power': 170, 'elapsed_s':5,'elapsed_m':12,'elapsed_h':0, 'heart':180, 'resistance': 25, 'req_resistance': 35, 'cadence': 90, 'req_cadence': 70, 'speed': 10, 'inclination': 10, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 222, 'req_power': 130, 'elapsed_s':6,'elapsed_m':13,'elapsed_h':0, 'heart':182, 'resistance': 31, 'req_resistance': 35, 'cadence': 80, 'req_cadence': 70, 'speed': 7, 'inclination': 12, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 237, 'req_power': 130, 'elapsed_s':7,'elapsed_m':14,'elapsed_h':0, 'heart':160, 'resistance': 20, 'req_resistance': 50, 'cadence': 90, 'req_cadence': 70, 'speed': 6, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 250, 'req_power': 170, 'elapsed_s':3,'elapsed_m':15,'elapsed_h':0, 'heart':115, 'resistance': 20, 'req_resistance': 50, 'cadence': 90, 'req_cadence': 90, 'speed': 10, 'inclination': 14, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 266, 'req_power': 170, 'elapsed_s':4,'elapsed_m':16,'elapsed_h':0, 'heart':120, 'resistance': 11, 'req_resistance': 35, 'cadence': 80, 'req_cadence': 60, 'speed': 10, 'inclination': 10, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 351, 'req_power': 170, 'elapsed_s':5,'elapsed_m':17,'elapsed_h':0, 'heart':112, 'resistance': 22, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 60, 'speed': 5, 'inclination': 9, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 322, 'req_power': 130, 'elapsed_s':6,'elapsed_m':18,'elapsed_h':0, 'heart':90, 'resistance': 25, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 96, 'speed': 10, 'inclination': 5, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 257, 'req_power': 130, 'elapsed_s':7,'elapsed_m':19,'elapsed_h':0, 'heart':120, 'resistance': 10, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 97, 'speed': 10, 'inclination': 1, 'workoutName': '45min Power Zone Ride', 'workoutStartDate': '20/12/2021', 'instructorName': "Robin Arzon", 'watts_avg': 200, 'watts_max' : 351, 'heart_avg': 120, 'heart_max' : 150, 'jouls': 138000, 'calories': 950, 'distance': 11, 'cadence_avg': 65, 'peloton_resistance_avg': 22, 'deviceType': 1},
|
||||
]
|
||||
process_arr(arr);
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#loading').hide();
|
||||
});
|
||||
@@ -590,11 +590,11 @@
|
||||
keys_arr = ['speed', 'speed_lapavg', 'cadence', 'cadence_lapavg', 'heart', 'heart_lapavg', 'calories', 'distance', 'watts', 'watts_lapavg',
|
||||
'elapsed_h', 'elapsed_m', 'elapsed_s', 'resistance', 'resistance_lapavg', 'peloton_resistance', 'peloton_resistance_lapavg',
|
||||
'speed_lapmax', 'cadence_lapmax', 'heart_lapmax', 'watts_lapmax', 'resistance_lapmax', 'peloton_resistance_lapmax',
|
||||
'speed_color', 'cadence_color', 'heart_color', 'watts_color', 'peloton_resistance_color', 'target_resistance', 'target_peloton_resistance',
|
||||
'speed_color', 'power_zone_color', 'cadence_color', 'heart_color', 'watts_color', 'peloton_resistance_color', 'target_resistance', 'target_peloton_resistance',
|
||||
'target_cadence', 'target_power', 'peloton_offset', 'peloton_ask_start', 'target_speed', 'target_pace', 'inclination', 'inclination_lapavg',
|
||||
'inclination_lapmax', 'target_inclination', 'power_zone', 'power_zone_lapavg', 'power_zone_lapmax', 'target_power_zone', 'jouls',
|
||||
'row_remaining_time_s', 'row_remaining_time_m', 'row_remaining_time_h' , 'autoresistance', 'gears', 'elevation', 'pace_s' , 'pace_m',
|
||||
'avgpace_s', 'avgpace_m', 'maxpace_s' , 'maxpace_m',]
|
||||
'avgpace_s', 'avgpace_m', 'maxpace_s' , 'maxpace_m', 'remaining_time_s', 'remaining_time_m', 'remaining_time_h']
|
||||
let ell = new MainWSQueueElement(null, function (msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
var speed = 0;
|
||||
@@ -628,6 +628,9 @@
|
||||
var row_remaining_time_s = 0;
|
||||
var row_remaining_time_m = 0;
|
||||
var row_remaining_time_h = 0;
|
||||
var remaining_time_s = 0;
|
||||
var remaining_time_m = 0;
|
||||
var remaining_time_h = 0;
|
||||
var resistance = 0;
|
||||
var resistance_lapavg = 0;
|
||||
var peloton_resistance = 0;
|
||||
@@ -721,6 +724,12 @@
|
||||
row_remaining_time_m = msg.content[key];
|
||||
} else if (key === 'row_remaining_time_s') {
|
||||
row_remaining_time_s = msg.content[key];
|
||||
} else if (key === 'remaining_time_h') {
|
||||
remaining_time_h = msg.content[key];
|
||||
} else if (key === 'remaining_time_m') {
|
||||
remaining_time_m = msg.content[key];
|
||||
} else if (key === 'remaining_time_s') {
|
||||
remaining_time_s = msg.content[key];
|
||||
} else if (key === 'target_pace_h') {
|
||||
target_pace_h = msg.content[key];
|
||||
} else if (key === 'target_pace_m') {
|
||||
@@ -758,7 +767,7 @@
|
||||
} else if (key === 'gears') {
|
||||
gears = msg.content[key];
|
||||
} else if (key === 'peloton_resistance_color') {
|
||||
$('.peloton_resistance-value').css('color', msg.content[key]);
|
||||
$('.pelotonresistance-value').css('color', msg.content[key]);
|
||||
} else if (key === 'heart_color') {
|
||||
$('.heart-value').css('color', msg.content[key]);
|
||||
} else if (key === 'cadence_color') {
|
||||
@@ -767,6 +776,8 @@
|
||||
$('.watt-value').css('color', msg.content[key]);
|
||||
} else if (key === 'speed_color') {
|
||||
$('.speed-value').css('color', msg.content[key]);
|
||||
} else if (key === 'power_zone_color') {
|
||||
$('.powerzone-value').css('color', msg.content[key]);
|
||||
} else if (key === 'peloton_ask_start' && !peloton_ask_already_running && (msg.content[key] === true || msg.content[key] === 'true')) {
|
||||
peloton_ask_already_running = true;
|
||||
document.getElementById("overlay").hidden = false;
|
||||
@@ -822,7 +833,7 @@
|
||||
$('.pelotonresistance-max').html(peloton_resistance_lapmax.toFixed(0));
|
||||
$('.distance-value').html("<b>" + odometer.toFixed(2) + "</b>");
|
||||
$('.rowremainingtime-value').html("<b>" + row_remaining_time_h.toString().padStart(2, "0") + ":" + row_remaining_time_m.toString().padStart(2, "0") + ":" + row_remaining_time_s.toString().padStart(2, "0") + "</b>");
|
||||
$('.elapsed-value').html("<b>" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":" + elapsed_s.toString().padStart(2, "0") + "</b>");
|
||||
$('.elapsed-value').html("<b>" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":" + elapsed_s.toString().padStart(2, "0") + "</b>" + (remaining_time_h > 0 || remaining_time_m > 0 || remaining_time_s > 0 ? " / " + "<b>" + remaining_time_h.toString().padStart(2, "0") + ":" + remaining_time_m.toString().padStart(2, "0") + ":" + remaining_time_s.toString().padStart(2, "0") + "</b>" : ""));
|
||||
$('.gears-value').html("<b>" + gears.toFixed(0) + "</b>");
|
||||
if(pace_s.toString() === "-1" || (pace_s.toString() === "0" && pace_m.toString() === "0"))
|
||||
$('.pace-value').html("<b>N/A</b>");
|
||||
@@ -849,7 +860,7 @@
|
||||
else
|
||||
$('.powerzone-value').html("<b>" + powerzone.toFixed(1) + "</b>");
|
||||
$('.powerzone-avg').html(powerzone_lapavg.toFixed(1));
|
||||
$('.spepowerzoneed-max').html(powerzone_lapmax.toFixed(1));
|
||||
$('.powerzone-max').html(powerzone_lapmax.toFixed(1));
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
|
||||
@@ -127,6 +127,7 @@ void inspirebike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
}
|
||||
|
||||
void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
QSettings settings;
|
||||
@@ -151,17 +152,17 @@ void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = 0.37497622 * ((double)Cadence.value());
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(watts(), Inclination.value(), Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
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) * settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
|
||||
if (settings.value(QZSettings::inspire_peloton_formula2, QZSettings::default_inspire_peloton_formula2).toBool()) {
|
||||
// y = 0,0002x^3 - 0.1478x^2 + 4.2412x + 1.8102
|
||||
@@ -184,7 +185,7 @@ void inspirebike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
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())
|
||||
|
||||
41
src/ios/AdbClient.h
Normal file
41
src/ios/AdbClient.h
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// AdbClient.h
|
||||
// adb-ios
|
||||
//
|
||||
// Created by Li Zonghai on 9/28/15.
|
||||
// Copyright © 2015 Li Zonghai. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
typedef void (^ResponseBlock)(BOOL succ, NSString *result);
|
||||
|
||||
|
||||
typedef NSUInteger ADBInstallFlag;
|
||||
NS_ENUM(ADBInstallFlag) {
|
||||
|
||||
ADBInstallFlag_Sdcard = 0x1,
|
||||
ADBInstallFlag_GrantAllRuntimePermission = 0x2,
|
||||
ADBInstallFlag_Replace = 0x4,
|
||||
|
||||
};
|
||||
|
||||
|
||||
@interface AdbClient : NSObject
|
||||
|
||||
|
||||
-(id)init;
|
||||
-(id)initWithVerbose:(BOOL)flg;
|
||||
|
||||
-(void) devices:(ResponseBlock)block;
|
||||
|
||||
-(void) connect: (NSString *)addr didResponse:(ResponseBlock)block;
|
||||
-(void) disconnect: (NSString *)addr didResponse:(ResponseBlock)block;
|
||||
|
||||
-(void) installApk: (NSString *)apkPath flags:(ADBInstallFlag)flags didResponse:(ResponseBlock)block;
|
||||
-(void) uninstallApk: (NSString *)packageName didResponse:(ResponseBlock)block;
|
||||
|
||||
-(void) shell: (NSString *)cmd didResponse:(ResponseBlock)block;
|
||||
|
||||
@end
|
||||
@@ -17,9 +17,11 @@ var pedometer = CMPedometer()
|
||||
|
||||
@objc public class healthkit:NSObject {
|
||||
let w = watchAppStart()
|
||||
let SwiftDebug = swiftDebug()
|
||||
|
||||
@objc public func request()
|
||||
{
|
||||
SwiftDebug.qtDebug("swift debug test")
|
||||
if #available(iOS 13.0, *) {
|
||||
Client.client.start()
|
||||
} else {
|
||||
@@ -62,8 +64,8 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#ODO=\(distance)#")
|
||||
WatchKitConnection.distance = distance;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setKcal(kcal: Double) -> Void
|
||||
@@ -74,8 +76,48 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(kcal)#")
|
||||
WatchKitConnection.kcal = kcal;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setCadence(cadence: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.cadence = cadence;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setSpeed(speed: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.speed = speed;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setPower(power: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.power = power;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
func createString(sender: String) -> String {
|
||||
return "SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#";
|
||||
}
|
||||
|
||||
@objc func updateHeartRate() {
|
||||
@@ -85,8 +127,7 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#CAD=\(WatchKitConnection.stepCadence)#")
|
||||
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user