Compare commits

...

361 Commits

Author SHA1 Message Date
Roberto Viola
53b245cba0 disable avg inclination for gpx treadmill 2023-12-27 12:46:29 +00:00
Roberto Viola
2c71206327 Update project.pbxproj 2023-12-27 07:04:33 +01:00
Roberto Viola
e3be4ffa0e distance with 1 decimal to the summary view (#1914) 2023-12-27 06:37:46 +01:00
Roberto Viola
868d5cc3a8 ProForm Gen 1 Connection Issue #1825 (#1844) 2023-12-27 04:34:18 +00:00
Roberto Viola
94eb866740 Nordictrack Treadmill / speed and inclination (Issue #1908) 2023-12-26 10:06:22 +01:00
Roberto Viola
f192cf6c75 Proform 225CSX resistance does not work #1903 2023-12-26 08:47:33 +01:00
Roberto Viola
c7efde82b6 Rower Distance is Not Included in Strava #1912 2023-12-26 08:42:47 +01:00
Roberto Viola
f32dcb4e1e Floating window: adding total remaining time after the elapsed time (Issue #1863) 2023-12-25 17:32:11 +00:00
Roberto Viola
6157bfdcc8 Proform 225CSX resistance does not work (Issue #1903) 2023-12-25 12:54:40 +01:00
Roberto Viola
9ff91faf00 Support for Pro-Form Cycle Trainer 300 ci (Issue #1892) 2023-12-25 12:30:25 +01:00
Roberto Viola
3b88bc2f1f Wahoo Dircon characteristics incompatibilities #1910 2023-12-25 11:42:08 +01:00
Roberto Viola
4f7cfbbfc1 Floating window: adding total remaining time after the elapsed time (Issue #1863) 2023-12-24 12:35:24 +01:00
Al Udell
56c112ed4c Update python scripts to use AI.Server for OCR (#1847)
* Update python scripts to use AI.Server for OCR

* change python from 3.10 to 3.7

* Update main.yml

* renaming script files

* resuming here the old script as well

* adding new build for ai server in CI

* renaming python scripts for AI.server

* Update homeform.cpp

* Update homeform.cpp

* fixing secrets on PR

* restoring...

* Update main.yml

* Update homeform.cpp

* Update main.yml

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_workout_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_workout_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_workout_paddleocr_thread.cpp

* Update main.yml

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_workout_paddleocr_thread.cpp

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update main.yml

* Update main.yml

* Update windows_zwift_incline_paddleocr_thread.cpp

* Update windows_zwift_workout_paddleocr_thread.cpp

* Update main.yml

* removing nopython build for ai.server

* Update main.yml

---------

Co-authored-by: Roberto Viola <cagnulein@gmail.com>
2023-12-23 10:02:10 +01:00
Roberto Viola
f6b8984575 Floating window: adding total remaining time after the elapsed time (Issue #1863) 2023-12-23 09:54:15 +01:00
Roberto Viola
52579e3efc Support for Pro-Form Cycle Trainer 300 ci #1892 2023-12-21 16:42:57 +01:00
Roberto Viola
fef5d8f4a7 Octane Fitness 37xi connection to QZ app #1876 2023-12-21 15:03:52 +01:00
Roberto Viola
6c93d85932 Floating window: adding total remaining time after the elapsed time #1863 2023-12-21 10:54:02 +01:00
Roberto Viola
b7763154a3 Update octaneelliptical.cpp 2023-12-21 09:29:43 +01:00
Roberto Viola
fe393bd70d Octane Fitness 37xi connection to QZ app #1876 2023-12-21 09:19:00 +01:00
Roberto Viola
3330cc516e Kingsmith G1 Walking Pad (Issue #1833 2023-12-20 14:57:32 +01:00
Roberto Viola
49e288ec01 Update octaneelliptical.cpp 2023-12-20 14:32:31 +01:00
Roberto Viola
f1e14d1652 Octane Fitness 37xi connection to QZ app #1876 2023-12-20 14:29:11 +01:00
Roberto Viola
2b541c220b Support for Pro-Form Cycle Trainer 300 ci (Issue #1892) 2023-12-20 12:16:27 +01:00
Roberto Viola
372d57368c adding inclination calculation to the stryd 2023-12-19 10:15:41 +01:00
Roberto Viola
21f977ad44 Any hints / tips for using the following a GPX feature? #1893 2023-12-18 14:28:57 +01:00
Roberto Viola
a152a0edb9 version 2.16.28 2023-12-17 19:12:19 +01:00
Roberto Viola
effb3cdfe5 Octane Fitness 37xi connection to QZ app #1876 2023-12-17 18:16:56 +01:00
Roberto Viola
e0144445a3 adding distance to virtual treadmill ftms to accomodate the new FTMS peloton function 2023-12-16 20:22:07 +01:00
Roberto Viola
8042989552 Octane Fitness 37xi connection to QZ app #1876 2023-12-15 09:29:43 +01:00
Roberto Viola
d5ba21da2e Octane Fitness 37xi connection to QZ app #1876 2023-12-14 19:43:35 +01:00
Roberto Viola
9d970fbca7 Octane Fitness 37xi connection to QZ app #1876 2023-12-14 13:33:10 +01:00
Roberto Viola
600d679e21 Freemotion Coachbike b22.7: Making adjustments on the bike through the app does not work. Manually or Automatically (Issue #1877) 2023-12-14 11:21:43 +01:00
Roberto Viola
e860224908 qz not detecting HR from garmin forerunner 265 w companion app #1883 2023-12-14 10:50:44 +01:00
Roberto Viola
081d778405 Making adjustments on the bike through the app does not work. Manually or Automatically (Issue #1877) 2023-12-13 19:30:30 +01:00
Roberto Viola
5f18b4daf1 fixing msvc compatibility #1876 2023-12-13 15:48:49 +01:00
Roberto Viola
7b0065d49a Octane Fitness 37xi connection to QZ app #1876 2023-12-13 15:23:35 +01:00
Roberto Viola
65c45d5f3a Toorx3000[BUG] #1865 2023-12-13 15:15:10 +01:00
Roberto Viola
12d3f469de Slight difference between front end pace vs .fit file pace? #1838 2023-12-13 07:57:31 +01:00
Roberto Viola
707fe68301 increased verbosity for currentdistance1s 2023-12-12 22:11:31 +01:00
Roberto Viola
69511f8c38 adding debug log for currentDistance1s 2023-12-12 21:32:38 +01:00
Roberto Viola
37ea50fca9 Update metric.cpp 2023-12-12 17:48:32 +01:00
Roberto Viola
c91a24cf23 fixing crash on powerpeak func 2023-12-12 17:48:10 +01:00
Roberto Viola
ea48d30f73 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-12-12 13:27:55 +01:00
Roberto Viola
117c3749ea Revert "Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804)"
This reverts commit 6376e45a13.
2023-12-12 13:27:02 +01:00
Roberto Viola
6376e45a13 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-12-12 11:42:28 +01:00
Roberto Viola
e1068bc1e5 Octane Fitness 37xi connection to QZ app #1876 2023-12-12 11:02:06 +01:00
Roberto Viola
c501466372 Echelon Row Sport Freezes (Issue #1875) 2023-12-12 08:03:59 +01:00
Roberto Viola
d8dea4f69d Echelon Row Sport Freezes (Issue #1875) 2023-12-11 20:26:01 +01:00
Al Udell
d44fcd72f7 Python scripts update (#1860)
* Python update

Update python scripts to support Windows DPI Aware and Zwift Windowed mode

* downgrading python to 3.7.9

* adding python path

---------

Co-authored-by: Roberto Viola <Cagnulein@gmail.com>
2023-12-11 16:24:12 +01:00
Roberto Viola
2a19918889 Advance metrics Stryd and correct Wattage #1873 2023-12-11 15:58:57 +01:00
Roberto Viola
420e263a9c Advance metrics Stryd and correct Wattage (Issue #1873) 2023-12-11 11:08:10 +01:00
Roberto Viola
8a71567a46 adding power and speed for running and walking on apple health 2023-12-11 10:59:17 +01:00
Roberto Viola
5a5d9fb4b4 Advance setting Watt offset #1714 2023-12-11 10:58:56 +01:00
Roberto Viola
f5484768b8 Update ypooelliptical.cpp 2023-12-11 09:48:07 +01:00
Roberto Viola
5beb890d85 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-12-11 09:38:15 +01:00
Roberto Viola
edf8a33b87 concept 2 rower metric missing #1856 2023-12-11 09:11:12 +01:00
Roberto Viola
03ddac15da Slight difference between front end pace vs .fit file pace? #1838 2023-12-08 11:57:39 +01:00
Roberto Viola
e41c60fbeb Slight difference between front end pace vs .fit file pace? #1838 (#1855) 2023-12-07 22:51:11 +01:00
Roberto Viola
2a435b6b65 adding also the qz screenshot at the end of the ride to the file to be removed after sending the email 2023-12-07 19:08:44 +01:00
Roberto Viola
da31761c35 Update mainwindow.cpp 2023-12-07 17:18:55 +01:00
Roberto Viola
6908bb5d43 2.16.26 2023-12-07 16:59:59 +01:00
Roberto Viola
cf35fccca7 fixing gpx default files opening if the speed forcing setting for treadmill was enabled 2023-12-07 16:59:08 +01:00
Roberto Viola
d4a8322160 other folders fixed on android 2023-12-07 16:37:37 +01:00
Roberto Viola
dc5f835445 2.16.25 2023-12-07 14:13:53 +01:00
Roberto Viola
85c7a70053 removing chart images after sending the email 2023-12-07 14:11:13 +01:00
Roberto Viola
b615bd008e adding ContentHelper to Android (#1868)
* adding ContentHelper

* android.permission.READ_EXTERNAL_STORAGE

* removing android.permission.READ_EXTERNAL_STORAGE

* copyAndroidContentsURI

* copyAndroidContentsURI

* Update homeform.cpp

* seems ok on android 14 for training workout

* works on android 14

* checked also iOS
2023-12-07 12:40:37 +01:00
Roberto Viola
2ea7337a0f fs5i nordick track more realistic ride when running with Rouvy or Zwift (Discussion #1345) 2023-12-06 09:27:55 +01:00
Roberto Viola
34f34312ce Floating window: adding total remaining time after the elapsed time #1863 2023-12-06 09:14:09 +01:00
Roberto Viola
d4818cc96e Lynx Konect 2.0 from MoovYoo #1861 (#1862)
* Lynx Konect 2.0 from MoovYoo #1861

* Revert "Lynx Konect 2.0 from MoovYoo #1861"

This reverts commit 80468184af.

* "Lynx Konect 2.0 from MoovYoo #1861"

* "Lynx Konect 2.0 from MoovYoo #1861"
2023-12-06 08:33:05 +01:00
Roberto Viola
d801bc7984 Volume of QZ incline swipes overwhelming treadmill [BUG] Issue #1816 (#1822) 2023-12-04 18:13:00 +01:00
Roberto Viola
0efe227318 Nordictrack S20 Treadmill - No speed or incline control (Issue #1850) 2023-12-04 14:36:58 +01:00
Roberto Viola
40549f16e8 Nordictrack S20 Treadmill - No speed or incline control (Issue #1850) 2023-12-04 13:39:57 +01:00
Roberto Viola
7295ebbf62 Nordictrack S20 Treadmill - No speed or incline control (Issue #1850) (… 2023-12-04 10:41:17 +01:00
Roberto Viola
95db80a249 Fix Jitter Distance Calculation (#1854)
* Allow auto-laps to be recorded by the app #1815 (#1830)

* trying to close gracefully the adbthread

* Update apexbike.cpp
2023-12-04 10:12:20 +01:00
Roberto Viola
534de944d1 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-12-04 09:44:21 +01:00
Roberto Viola
d4c9f2b1dd Update horizontreadmill.cpp 2023-12-04 09:44:03 +01:00
Roberto Viola
ffb2a6e072 Nordictrack S20 Treadmill - No speed or incline control (Issue #1850) (#1858) 2023-12-04 09:36:44 +01:00
Roberto Viola
2dc7ad7fd9 Move Files instead of copying it from android private folder (#1846) 2023-12-03 21:22:15 +01:00
Roberto Viola
35d2eb23aa Hammer Q.Vadis (10.0) #1824 2023-12-02 10:56:33 +01:00
Roberto Viola
4ab4e17f03 Allow auto-laps to be recorded by the app #1815 (#1830) 2023-12-01 22:01:54 +01:00
Roberto Viola
acd80b1105 Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-12-01 14:13:43 +01:00
Roberto Viola
01ab6841fc Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-12-01 14:02:38 +01:00
Roberto Viola
e547924350 Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-12-01 13:53:27 +01:00
Roberto Viola
30b0bd51ca Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-12-01 08:51:22 +01:00
Roberto Viola
a3a2bca715 Update build.gradle (#1843) 2023-11-30 11:28:15 +01:00
Roberto Viola
bf14b9a8e0 Hammer Q.Vadis (10.0) (Issue #1824) 2023-11-30 06:41:32 +01:00
Roberto Viola
a53e7a6e44 Kingsmith G1 Walking Pad #1833 2023-11-28 14:35:08 +01:00
Roberto Viola
5eefb07e9e XTERRA TRX3500 no control from QZ App (Issue #1835) 2023-11-28 13:53:32 +01:00
Roberto Viola
55b0689d1d XTERRA TRX3500 no control from QZ App (Issue #1835) 2023-11-28 13:28:26 +01:00
Roberto Viola
beb0a8a80c adding elite fan file to iOS project 2023-11-28 11:31:27 +01:00
Roberto Viola
65f57d111a Elite Qubo Digital Smart B+ #1834 2023-11-28 09:05:41 +01:00
Roberto Viola
7de08d223f Kingsmith G1 Walking Pad #1833 2023-11-28 08:59:03 +01:00
Roberto Viola
2b69f2dcd0 Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:57:58 +01:00
Roberto Viola
27da18fed9 Elite Aria #1803 2023-11-27 11:54:40 +01:00
Roberto Viola
22fd82cdff Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:49:55 +01:00
Roberto Viola
d9202218dd Debug Log- Lifespan X41 elliptical cross trainer #1804 2023-11-27 11:25:58 +01:00
Roberto Viola
baf9e24640 Update ypooelliptical.cpp 2023-11-27 11:11:50 +01:00
Roberto Viola
9c0ab9b9f6 Rogue echo bike V3.0 #1828 2023-11-27 09:29:49 +01:00
Roberto Viola
49e0506815 Update label to include NordicTrack info #1817 2023-11-26 11:46:06 +01:00
Roberto Viola
80c528f370 Update technogymmyruntreadmillrfcomm.cpp 2023-11-24 09:14:17 +01:00
Roberto Viola
54fa926596 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-11-24 08:33:13 +01:00
Roberto Viola
9f80a7b679 Android OpenSSL in CI (#1809)
* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* qthttpserver added
2023-11-23 16:08:05 +01:00
Roberto Viola
7639d19f5e Elite Aria #1803 2023-11-23 09:15:01 +01:00
Roberto Viola
9870626695 Elite Aria #1803 2023-11-23 08:50:22 +01:00
Roberto Viola
5be30f8f6c Elite Aria #1803 2023-11-23 08:38:30 +01:00
Roberto Viola
751f9c4f84 Elite Aria #1803 2023-11-22 15:07:25 +01:00
Roberto Viola
dd61b8e2ca Elite Aria #1803 2023-11-22 14:26:12 +01:00
Roberto Viola
7dfc994888 Elite Aria #1803 2023-11-22 14:18:05 +01:00
Roberto Viola
c662b84bc2 Debug Log- Lifespan X41 elliptical cross trainer (Issue #1804) 2023-11-22 13:39:00 +01:00
Roberto Viola
7b0ec35303 Elite Aria #1803 2023-11-22 11:15:41 +01:00
Roberto Viola
5d235eaab7 Elite Aria #1803 2023-11-22 10:45:33 +01:00
Roberto Viola
4e55e2b085 Elite Aria #1803 2023-11-22 09:27:13 +01:00
Roberto Viola
e221e4bf7e Update README.md 2023-11-22 08:26:20 +01:00
Roberto Viola
67f0be7c06 Update README.md 2023-11-22 08:25:10 +01:00
Roberto Viola
415025e43d Impossible to connect to treadmill BH S7Ti #1800 2023-11-21 14:07:32 +01:00
Roberto Viola
ba1854659a Impossible to connect to treadmill BH S7Ti #1800 2023-11-21 13:47:36 +01:00
Roberto Viola
f622a3e3f7 Life-Span X-41 Elliptical Cross trainer #1799 2023-11-20 12:05:09 +01:00
Roberto Viola
aad888cdab Adding fit hi way 2023-11-19 17:37:41 +01:00
Roberto Viola
b3d45319ae Adding FIT HI WAY 2023-11-19 17:36:10 +01:00
Roberto Viola
0ecff51b14 Android 14 QZ on Documents Folder (#1794)
* Update homeform.cpp

* implemented in all the 4 places where the user can open files

* Update homeform.h

* Update homeform.cpp

* profiles correctly copied

* change path only for android 14 or greater

* Update homeform.cpp
2023-11-17 22:39:07 +01:00
Roberto Viola
face819d09 Update trxappgateusbtreadmill.cpp 2023-11-17 18:45:29 +01:00
Roberto Viola
f8b9c65dab Focus Fitness Senator 54 iplus #1790 2023-11-17 17:36:16 +01:00
Roberto Viola
0b0081a506 version 2.16.24 2023-11-17 15:21:04 +01:00
Roberto Viola
94c63f5b77 Focus Fitness Senator 54 iplus #1790 2023-11-17 15:18:50 +01:00
Roberto Viola
87725cdd1d Focus Fitness Senator 54 iplus #1790 2023-11-17 13:54:37 +01:00
Roberto Viola
0ddeb747bf Focus Fitness Senator 54 iplus #1790 2023-11-17 13:05:05 +01:00
Roberto Viola
63a7476d14 Focus Fitness Senator 54 iplus #1790 2023-11-17 11:33:37 +01:00
Roberto Viola
28701625ac copying old elements on android from the private folder 2023-11-16 22:44:13 +01:00
Roberto Viola
e0bc9071c5 Update nordictrackifitadbtreadmill.cpp 2023-11-16 14:11:24 +01:00
Roberto Viola
5bd16f8200 No connection to x22i (Issue #65)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65#issuecomment-1813990744
2023-11-16 14:01:10 +01:00
Roberto Viola
d2b844ffb9 technogym myrun recommends trying full discovery 2023-11-14 21:09:54 +01:00
Roberto Viola
4018d2947c Cannot get Nordic Track Studio Bike - NTEX71021 to work w/ QZ #1785 2023-11-14 15:29:36 +01:00
Roberto Viola
f030c319ac pen Cannot get Nordic Track Studio Bike - NTEX71021 to work w/ QZ #1785 2023-11-14 15:24:50 +01:00
Roberto Viola
ccd4a9896e Bowflex Max Trainer #1789 2023-11-14 15:17:26 +01:00
Roberto Viola
246bf944b2 Update homeform.cpp (#1788) 2023-11-14 08:49:39 +01:00
Roberto Viola
0b7ded9020 Update bluetooth.cpp 2023-11-14 08:43:59 +01:00
Roberto Viola
8bf64c64be technogymrfcomm fix on discovery 2023-11-14 08:20:34 +01:00
Roberto Viola
dbd0227722 fs5i nordick track more realistic ride when running with Rouvy or Zwift #1345 2023-11-13 16:00:17 +01:00
Roberto Viola
7e04ab4f92 No connection to x22i (Issue #65)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65#issuecomment-1807917029
2023-11-13 15:07:58 +01:00
Roberto Viola
9b7e1d873c Old Treadmill/Android 2 compatibility #62
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/62
2023-11-13 14:54:33 +01:00
Roberto Viola
60ee86476c Revert "trying wahoo kickrhead connectless #1281"
This reverts commit f729260cbd.
2023-11-13 14:48:45 +01:00
Roberto Viola
430378b441 No connection to x22i (Issue #65)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65#issuecomment-1807917029
2023-11-13 14:48:12 +01:00
Roberto Viola
b9537ff042 Cannot get Nordic Track Studio Bike - NTEX71021 to work w/ QZ (Issue #1785) 2023-11-13 14:46:19 +01:00
Roberto Viola
c0bba3943b No connection to x22i (Issue #65)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65#issuecomment-1807917029
2023-11-13 14:45:56 +01:00
Roberto Viola
f729260cbd trying wahoo kickrhead connectless #1281 2023-11-11 18:28:31 +01:00
Roberto Viola
bff00001ab No connection to x22i #65
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65
2023-11-10 18:23:23 +01:00
Roberto Viola
91e5a1f14e No connection to x22i #65
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65
2023-11-10 18:20:08 +01:00
Roberto Viola
b928be6a33 fixing ios build on CI 2023-11-10 15:56:12 +01:00
Roberto Viola
2e0dde6aa4 Incorrect distance on QZ with Sole S77 treadmill #1781 2023-11-10 08:38:21 +01:00
Roberto Viola
7b8c8ea2b8 Broken link in Macos install instructions #1779 2023-11-09 10:47:40 +01:00
Roberto Viola
afd6e5ce26 ADB on iOS (#1732)
* Create libadb.a

* adding files to project

* code implemented and works locally
2023-11-09 08:06:33 +01:00
Roberto Viola
2b8b005762 proformwifibike: external gear change also target power on ERG mode 2023-11-08 16:42:20 +01:00
Roberto Viola
1e639cacd1 fs5i nordick track more realistic ride when running with Rouvy or Zwift #1345 2023-11-08 16:37:44 +01:00
Roberto Viola
16611c7e49 openssl added for windows (#1777) 2023-11-08 11:55:30 +01:00
Roberto Viola
757febf35a Update main.yml 2023-11-06 09:42:31 +01:00
Roberto Viola
2c46293612 No connection to x22i #65
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/65
2023-11-06 09:21:51 +01:00
Roberto Viola
9a1f9ee980 QZ on iOS does not receive data from the bike (Issue #1770) 2023-11-06 08:28:04 +01:00
Roberto Viola
96c80d5bb6 Use user determined power gain values with Stryd and Incline #1765
reverting #1741
2023-11-02 15:25:23 +01:00
Roberto Viola
98fbd64b89 moc removed 2023-11-02 15:03:52 +01:00
Roberto Viola
23131f8fb8 removing moc causing issue on iOS build on Mac 2023-11-02 14:49:50 +01:00
Roberto Viola
ec966d6036 2.16.22 2023-11-02 14:23:45 +01:00
Roberto Viola
11f0d6786b Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-11-02 14:04:53 +01:00
Roberto Viola
0f2619233c Update main.yml 2023-11-02 14:04:38 +01:00
cagnulein
a2ba4cefb5 moc files added 2023-11-02 12:49:13 +00:00
Roberto Viola
a83e5951aa Update main.yml 2023-11-02 13:23:46 +01:00
Roberto Viola
9fe09e7b4b adding moc files to CI (#1559)
* adding moc to CI

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml
2023-11-02 12:07:13 +01:00
Roberto Viola
0ea2269a67 Update main.yml 2023-11-02 09:31:58 +01:00
Roberto Viola
fdba749812 Connectivity issue with Saris/ Cycleops Hammer trainer #1724 2023-11-02 08:56:37 +01:00
Roberto Viola
ac257b13ed Update wahookickrheadwind.cpp 2023-11-01 22:33:43 +01:00
Roberto Viola
0890bf8096 Update proformtreadmill.cpp #1755 2023-11-01 11:49:38 +01:00
Roberto Viola
ea75a04ad5 Update main.yml 2023-11-01 11:21:44 +01:00
Roberto Viola
c3a4238618 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-10-31 17:30:43 +01:00
Roberto Viola
8017bf64cd Speed/Incline support for ProForm Pro 1000 Treadmill #1755 2023-10-31 17:30:33 +01:00
Roberto Viola
04633c6296 CI Releases (#1754)
* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Revert "Update main.yml"

This reverts commit b7c85b0bb4.

* Revert "Update main.yml"

This reverts commit d8a310de03.

* Update main.yml
2023-10-31 17:11:52 +01:00
Roberto Viola
513b59c106 Old Treadmill/Android 2 compatibility #62
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/62
2023-10-31 15:17:38 +01:00
Roberto Viola
04c3cc329c Windows PATH batch (#1736)
* Create setup.bat

* Update main.yml

* Update setup.bat

* Update setup.bat

* Update setup.bat
2023-10-31 11:55:00 +01:00
Roberto Viola
e99d7279f4 version 2.16.21 2023-10-31 11:53:55 +01:00
Roberto Viola
716f917e2a odometer keeps going when stopped echelon row #1750 2023-10-31 08:43:27 +01:00
Roberto Viola
0147498c69 Assault bike #1746 2023-10-31 08:29:24 +01:00
Vaibhav
22b3cef9d2 Fixes errors in 30_usage.md file (#1749) 2023-10-30 18:55:55 +01:00
Vaibhav
81e4e95272 Update README.md (#1747) 2023-10-30 17:55:01 +01:00
Roberto Viola
12cc5d0221 Wahoo Kickr Headwind #1281 2023-10-30 16:14:29 +01:00
Roberto Viola
5b1f85a8cb Urevo T2S bike #1744 2023-10-30 14:18:51 +01:00
Vivek Kumar
9a9d5fa95e Update 10_Installation.md (#1742) 2023-10-30 09:47:36 +01:00
Roberto Viola
8f335894e7 fr30z support/multiuser xp (Issue #1735) 2023-10-30 09:32:11 +01:00
Roberto Viola
3292af09ec Stryd power + Inclination power from the treadmill #1741 2023-10-30 09:16:18 +01:00
Roberto Viola
747acae290 fr30z support/multiuser xp (Issue #1735) 2023-10-30 08:53:54 +01:00
Roberto Viola
04c13f2842 Stryd power + Inclination power from the treadmill #1741 2023-10-30 08:33:07 +01:00
Roberto Viola
b77ed2af6a Peloton Resistance is not following the Targeted Peloton Resistance when using the "lower" option #1668 2023-10-30 08:10:42 +01:00
Roberto Viola
5f186204d6 Estimated FTP in email #1739 2023-10-30 08:07:44 +01:00
Roberto Viola
6e9a1ef58a Power Profile Peloton #1733 2023-10-27 11:18:34 +02:00
Roberto Viola
180a246902 Power Profile Peloton #1733 2023-10-27 10:53:51 +02:00
Roberto Viola
09ab5b142c Tacx Vortex #1734 2023-10-27 10:16:43 +02:00
Roberto Viola
50249a5236 Sole F63 doesn't work with qz #597 2023-10-27 09:37:43 +02:00
Roberto Viola
4da2ac2fe1 Sole F63 doesn't work with qz #597 2023-10-27 09:22:17 +02:00
Roberto Viola
ba9253209c Sole F63 doesn't work with qz #597 2023-10-27 09:19:25 +02:00
Roberto Viola
e8031f5a19 Sole F63 doesn't work with qz #597 2023-10-26 10:23:37 +02:00
Roberto Viola
b649e5e0f5 Sole F63 doesn't work with qz #597 2023-10-26 10:16:17 +02:00
Roberto Viola
da9c570029 Connectivity issue with Saris/ Cycleops Hammer trainer #1724 2023-10-26 09:43:05 +02:00
Roberto Viola
f79eae87a9 horizon treadmill with stryd 2023-10-25 14:46:34 +02:00
Roberto Viola
44beb23212 Connectivity issue with Saris/ Cycleops Hammer trainer #1724 2023-10-25 09:31:02 +02:00
Roberto Viola
ed35ece439 With Direct Connect, cadence value is not accurate on Zwift #1688 2023-10-25 09:23:53 +02:00
Roberto Viola
37b513904c Support for Domyos EL540 (Domyos-EL-1350) #1725 2023-10-25 08:23:52 +02:00
Roberto Viola
639397d594 version 2.16.20 2023-10-23 11:48:04 +02:00
Roberto Viola
de51899ca3 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-10-23 11:45:57 +02:00
Roberto Viola
ff40d68223 resistance not working on proform studio bike with companion #1690 2023-10-23 11:45:40 +02:00
Roberto Viola
1385ea8b88 Fixing Windows CI 2023-10-23 11:40:34 +02:00
Roberto Viola
13c88474c6 Win64 msvc2019 64 (#1467)
* first test

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* trying to fix build

* compiler dump

* Update metric.h

* minmax issue

* Revert "compiler dump"

This reverts commit 7a6917f872.

* Update qdomyos-zwift.pri

* Revert "Update qdomyos-zwift.pri"

This reverts commit 54715413f0.

* debug enabled

* Revert "debug enabled"

This reverts commit 25c16c94ca.

* msvc 2015

* Revert "msvc 2015"

This reverts commit cded1bfac2.

* msvc 2015

* Update main.yml

* Revert "Update main.yml"

This reverts commit 78d88cc15d.

* Revert "msvc 2015"

This reverts commit a6e5252a5a.

* runtime error fixed!

* adding back the rest of the CI  + qhttpserver build

* enable always the log to debug

* msvc library rebuilt

* Update main.yml

* Create ucrtbased.dll

* finalizing

* Update horizontreadmill.cpp

* Update horizontreadmill.cpp

* Update horizontreadmill.cpp

https://bugreports.qt.io/browse/QTBUG-78488

* Update horizontreadmill.cpp

* Windows QZ crashing upon startup (Issue #1670)

* trying to patch qt directly

* Speed and Incline not working on QZ Desktop app [BUG] #1675

* Revert "Speed and Incline not working on QZ Desktop app [BUG] #1675"

This reverts commit 9f0111e919.

* Update proformtreadmill.cpp

* Speed and Incline not working on QZ Desktop app [BUG] #1675
2023-10-23 10:49:36 +02:00
Roberto Viola
e55577d835 Colours for speed tile on Echelon Stride aren't correct? #1718 2023-10-23 10:37:56 +02:00
Roberto Viola
6409eab90c Fixing Windows CI 2023-10-23 09:32:02 +02:00
Roberto Viola
a314dfe325 Tacx Flux #1720 2023-10-23 08:27:11 +02:00
Roberto Viola
9b41e2494d Renpho AI bike lacking any change in resistance when inclination changes or when power-ups are used in Zwift (Issue #1712) 2023-10-21 14:22:10 +02:00
Roberto Viola
8672240d72 Advance setting Watt offset (Discussion #1714) 2023-10-19 08:30:24 +02:00
Roberto Viola
8e83967036 2.16.18 2023-10-18 13:53:59 +02:00
Roberto Viola
8d8b1c141a Garmin Cadence Sensor not sending data (Issue #1704) 2023-10-18 10:07:15 +02:00
Roberto Viola
64d40b5d79 Revert "Garmin Cadence Sensor not sending data (Issue #1704)"
This reverts commit 42c2c52eef.
2023-10-18 09:04:37 +02:00
Roberto Viola
91811b6b0e Revert "Garmin Cadence Sensor not sending data (Issue #1704)"
This reverts commit 67398fce4b.
2023-10-18 09:04:32 +02:00
Roberto Viola
3e4c898d9f Conversion gain setting and conversion offset setting do not work with Tacx Neo 2 and Peloton. + resistance crashes when adjusted manually #1707 2023-10-17 14:22:28 +02:00
Roberto Viola
67398fce4b Garmin Cadence Sensor not sending data (Issue #1704) 2023-10-17 11:34:14 +02:00
Roberto Viola
42c2c52eef Garmin Cadence Sensor not sending data (Issue #1704) 2023-10-17 09:53:08 +02:00
Roberto Viola
94cfede7ac Conversion gain setting and conversion offset setting do not work with Tacx Neo 2 and Peloton. + resistance crashes when adjusted manually #1707 2023-10-17 09:29:22 +02:00
Roberto Viola
e8e6068aa2 resistance not working on proform studio bike with companion #1690 2023-10-17 08:34:05 +02:00
Roberto Viola
056a903cf2 Tacx Neo power lag #1431 2023-10-16 09:18:38 +02:00
Roberto Viola
911e66041f Adidas Treadmill #1705 2023-10-16 09:05:24 +02:00
Roberto Viola
dbc3c2abf2 Rowing Concept2 Pm3, Pm4 (Bluetooth) (Issue #1486) 2023-10-14 07:43:00 +02:00
Roberto Viola
6f37799e56 Rowing Concept2 Pm3, Pm4 (Bluetooth) (Issue #1486) 2023-10-14 07:37:19 +02:00
Roberto Viola
00bfd0707c Adidas Treadmill #1705 2023-10-14 07:36:32 +02:00
Roberto Viola
6bc003db9b tdf4 change gears without manual workout #1697 2023-10-12 14:32:07 +02:00
Roberto Viola
7b1044a634 tdf4 change gears without manual workout #1697 2023-10-12 14:18:24 +02:00
Cameron Horton
3686b77405 ERG setting high resistance on Merach S09 (Issue #1683) (#1701) 2023-10-12 14:16:44 +02:00
Roberto Viola
ba3bb3b724 tdf4 change gears without manual workout #1697 2023-10-11 11:54:34 +02:00
Roberto Viola
495168c204 ERG setting high resistance on Merach S09 (Issue #1683) 2023-10-10 08:37:52 +02:00
Roberto Viola
8a9230942b Echelon EX3 resistance not adjusting during ERG Mode #1692 2023-10-09 14:41:15 +02:00
Roberto Viola
100eaccc99 Resistance preset for rowers 2023-10-07 17:45:38 +01:00
Roberto Viola
24c0df5075 fixing "holes" in apple health for cadence 2023-10-06 08:48:38 +02:00
Roberto Viola
9116a46b27 New Peloton Rower Paces #1611 2023-10-03 11:37:29 +02:00
Roberto Viola
64059f2db5 on proform RL with Holofit, strokes and hr reported but not pace or calories #1681 2023-10-02 11:53:47 +02:00
Roberto Viola
2e386ef2a1 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-10-02 09:31:53 +02:00
Roberto Viola
1ecee4cd3e Rowing With Zwift Avatar Keeps Moving When You Stop #1671 2023-10-02 09:31:42 +02:00
Roberto Viola
fd523000c0 on proform RL with Holofit, strokes and hr reported but not pace or calories #1681 2023-09-30 17:23:46 +02:00
Roberto Viola
47c272d3d0 on proform RL with Holofit, strokes and hr reported but not pace or calories #1681 2023-09-30 15:26:18 +02:00
Roberto Viola
4eec504016 Elite smart trainer #1682 2023-09-30 15:17:11 +02:00
Roberto Viola
da08e3335e Update main.yml 2023-09-30 12:36:10 +02:00
Roberto Viola
0100da67cf on proform RL with Holofit, strokes and hr reported but not pace or calories (Issue #1681) 2023-09-30 11:57:31 +02:00
Roberto Viola
12e03510f6 Update main.yml 2023-09-30 11:51:27 +02:00
Roberto Viola
c8a17ad70e Update main.yml 2023-09-29 13:49:45 +02:00
Roberto Viola
9748558f1b releases only on master 2023-09-29 13:37:28 +02:00
Roberto Viola
c807291389 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2023-09-29 13:18:06 +02:00
Roberto Viola
29c4e323b0 adding trial apk to releases 2023-09-29 13:17:57 +02:00
Roberto Viola
4b1408401f HR from Apple Watch to Windows build (#1677)
* let's try

* Update homeform.cpp
2023-09-29 13:04:26 +02:00
Roberto Viola
39e2ef50cb Proform Rower (Proform Sport RL) cannot change resistance from app #1643 2023-09-28 14:46:28 +02:00
Roberto Viola
880ac67fbc creating releases automatically 2023-09-28 10:15:34 +02:00
Roberto Viola
b21695bc14 Proform Rower (Proform Sport RL) cannot change resistance from app #1643 2023-09-28 09:52:10 +02:00
Roberto Viola
eab058f401 Proform Rower (Proform Sport RL) cannot change resistance from app #1643 2023-09-27 14:25:50 +02:00
Roberto Viola
58f62303be Proform CX Carbon: some resistance levels missing #1674 2023-09-27 14:21:20 +02:00
Roberto Viola
22f935c827 Proform Rower (Proform Sport RL) cannot change resistance from app (Issue #1643) 2023-09-27 11:44:04 +02:00
Roberto Viola
dce2ba7067 Proform Rower (Proform Sport RL) cannot change resistance from app (Issue #1643) 2023-09-26 17:55:36 +02:00
Roberto Viola
51665c76b4 Video Playback seem's to be broken with last iOS Build (Issue #1665) 2023-09-26 15:27:43 +02:00
Roberto Viola
a76cf602d6 Proform Rower (Proform Sport RL) cannot change resistance from app #1643 2023-09-26 11:12:02 +02:00
Roberto Viola
a3f6bb8d0c Proform CX Carbon: some resistance levels missing #1674 2023-09-26 10:55:33 +02:00
Roberto Viola
3f83e4a999 Walkingpad C2 #1672 2023-09-25 21:59:57 +02:00
Roberto Viola
14546d4ed3 Wahoo Blue SC without a heart rate monitor. #1651 2023-09-18 10:39:18 +02:00
Roberto Viola
1afff6a79d power zone max on the floating window 2023-09-18 08:26:40 +02:00
Roberto Viola
e95bf77c9d Yesoul C1H Resistance Value is jumping in iOS (Issue #1641) 2023-09-15 21:34:29 +02:00
Roberto Viola
76ebceccbc Add date tag to generic activities (Ride Row etc) #1648 2023-09-15 10:46:08 +02:00
Roberto Viola
f54b900c18 Holofit connection issue #1623 2023-09-15 06:44:57 +02:00
Roberto Viola
42e7749856 New Peloton Rower Paces (Issue #1611) 2023-09-14 20:54:20 +02:00
Roberto Viola
13fb8ae05a New Peloton Rower Paces (Issue #1611) 2023-09-14 10:47:27 +02:00
Roberto Viola
ee27386d11 HRM from Nordictrack ADB bikes (#1636) 2023-09-14 07:57:13 +02:00
Roberto Viola
5073ed766f Proform Rower (Proform Sport RL) cannot change resistance from app #1643 2023-09-13 13:57:58 +02:00
Roberto Viola
c571a98775 2.16.11 2023-09-12 15:59:21 +02:00
Roberto Viola
1e66fb10b9 QZ not working with Echelon Stride (Issue #1644) 2023-09-12 15:58:37 +02:00
Roberto Viola
c9bcfb201e Focus fitness fox 5 iplus (Issue #1642) 2023-09-12 14:26:00 +02:00
Roberto Viola
d701d01f13 2.16.10 2023-09-12 14:02:59 +02:00
Roberto Viola
f3ae244587 WearOS Companion App (#1635)
* receiving HR

* Update build.gradle

* Revert "Update build.gradle"

This reverts commit 6876191cbb.

* Revert "receiving HR"

This reverts commit a4f7acffa8.

* builds!

* wearable controller added

* is it required?

* Update build.gradle

* heart rate works!
2023-09-12 11:46:32 +02:00
Roberto Viola
755e98d49a HRM from Nordictrack ADB bikes (#1636) 2023-09-12 06:56:54 +02:00
Roberto Viola
006c365fee Yesoul C1H Resistance Value is jumping in iOS #1641 2023-09-11 17:17:44 +02:00
Roberto Viola
2d9110c251 HRM from Nordictrack ADB bikes (#1636)
* trying to get HRM

https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/59

* Update nordictrackifitadbbike.cpp
2023-09-11 14:30:25 +02:00
Roberto Viola
30e945f609 QZ PowerZone Real Time Chart Not working on QZ V2.16.8 for uBike FTMS [BUG] #1639 2023-09-11 08:21:37 +02:00
Roberto Viola
d3289fa5d1 2.16.8 2023-09-07 07:33:11 +02:00
Roberto Viola
1dd8846c2e Web Live Charts (#1637)
* starting

* Update dochartlivewatt.js

* Update dochartlivewatt.js

* Update dochartlivewatt.js

* adding to qml

* adding compatibility with windows

* Update Home.qml

* file names renamed

* working point!

* icon works too

* conditional qml is working on iOS

* responsive
2023-09-07 07:29:35 +02:00
Roberto Viola
9a3dc093a2 qtHttpServer Lib for Android CI (#1638)
* Update main.yml

* Update main.yml
2023-09-06 15:09:08 +02:00
Roberto Viola
5674c75f4f [WINDOWS] Incline and resistance not controlling s22i bike #1627 (#1629) 2023-09-02 03:08:38 +02:00
Roberto Viola
f97e11b1d2 [WINDOWS] Incline and resistance not controlling s22i bike #1627 (#1629) 2023-09-01 19:55:21 +02:00
Roberto Viola
68a5bf7f44 iOS17 cycling power,speed and cadence (#1515) 2023-09-01 13:33:19 +02:00
Roberto Viola
d644aa4523 saris M2 control resistance from qz #1634 2023-09-01 07:23:19 +02:00
Roberto Viola
d04a1a9f01 version 2.16.7 2023-09-01 06:28:09 +02:00
Roberto Viola
e907170ba2 Separate Gain & Offset tables for Bluetooth & ANT+ (Issue #1633) 2023-08-31 18:11:29 +02:00
Roberto Viola
8a55ac1088 fixing ios build 2023-08-31 14:42:18 +02:00
Roberto Viola
f83c7565cc version 2.16.6 2023-08-31 14:28:45 +02:00
Roberto Viola
7de500503e Separate Gain & Offset tables for Bluetooth & ANT+ (Issue #1633) 2023-08-31 14:27:11 +02:00
Roberto Viola
61b15254e1 qdebug from objective c is possible 2023-08-31 14:15:38 +02:00
Roberto Viola
2503835a59 saris m2 #1625 2023-08-30 16:28:39 +02:00
Roberto Viola
00ceee2ee0 Home trainee elite zumo no resistance #1626 2023-08-30 11:17:18 +02:00
Roberto Viola
c93cf781fb fixing build #1622 #1497 2023-08-30 09:19:31 +02:00
Roberto Viola
a17da417c2 some buggy TDF1 bikes send spurious wattage at the end with cadence = 0 2023-08-30 08:27:22 +02:00
Roberto Viola
306ce699d4 Custom made broadcoaster connected to Nordicktrack s15 bike not sending resistance to QZ [BUG] #1622 #1497 2023-08-30 08:10:41 +02:00
Roberto Viola
fbbd40a0f8 DKN Endurun Treadmill #492 2023-08-29 08:52:12 +02:00
Roberto Viola
5398b09643 Nordictrack Z1300i #1618 2023-08-23 12:22:22 +02:00
Roberto Viola
94046b18fe Nordictrack Z1300i #1618 2023-08-23 08:53:43 +02:00
Roberto Viola
4e9a03d931 Domyos Rower incorrect speed #1594 2023-08-22 09:11:29 +02:00
Roberto Viola
2392251992 Horizon Paragon X #637 2023-08-22 08:10:07 +02:00
Roberto Viola
973ff3a586 Horizon Paragon X #637 2023-08-21 10:04:08 +02:00
Roberto Viola
87e5a1905d version 2.16.2 2023-08-18 11:47:47 +02:00
Roberto Viola
f39ca765d8 Andorid App Connection Failing - NoblePro Elite E8i #1609 2023-08-18 11:37:58 +02:00
Roberto Viola
b33ce3233d Support for Flowfitness speedbike DSB600i (Iconsole+/FITHIWAY0038) #1610 2023-08-18 11:32:43 +02:00
Roberto Viola
4209f6f653 [BUG] - Andorid App Connection Failing - NoblePro Elite E8i #1609 2023-08-17 11:27:19 +02:00
Roberto Viola
7d36b4082b Runmaxx 9.1 Threadmill on Win 10 (Issue #1581) 2023-08-17 10:15:39 +02:00
Roberto Viola
a3015858f9 Runmaxx 9.1 Threadmill on Win 10 (Issue #1581) 2023-08-15 10:24:28 +02:00
Roberto Viola
12515752f6 Peloton Resistance green tolerance for echelon bikes #1608 2023-08-15 10:21:09 +02:00
Roberto Viola
9334c6b472 Heart rate drops to zero intermittently (Issue #1607) 2023-08-15 09:52:15 +02:00
Roberto Viola
884bea8352 BH Mycron T200 Treadmill #1606 2023-08-14 17:35:26 +02:00
Roberto Viola
8b4c579539 Renpho AI Bike new model #1605 2023-08-14 17:33:22 +02:00
Roberto Viola
c9f3a3a092 2.16 build 628 2023-08-12 10:43:12 +02:00
Roberto Viola
547bc8a5c9 HR on iPad issue #1529 2023-08-12 10:32:06 +02:00
Roberto Viola
9c600dbc00 fs5i nordick track more realistic ride when running with Rouvy or Zwift (Discussion #1345) 2023-08-12 09:16:13 +02:00
Roberto Viola
f29d9fd1e6 Update .gitignore 2023-08-11 09:13:41 +02:00
Roberto Viola
8d1e98e81f version 2.16.0 2023-08-11 09:10:32 +02:00
Roberto Viola
94ac9d1bdd Treadmill Peloton issue #1603 2023-08-11 09:08:50 +02:00
Roberto Viola
9e74918b65 Domyos Rower incorrect speed #1594 2023-08-08 15:22:09 +02:00
Roberto Viola
3be43c14e1 "Recovery" tag on PowerZone peloton workout #1596 2023-08-08 14:44:27 +02:00
Roberto Viola
e1b951e664 next row fixed for power zone 2023-08-07 17:34:59 +02:00
Roberto Viola
53126b16d3 Watts not matching on Sole E95 Elliptical (Issue #1595) 2023-08-07 17:33:14 +02:00
Roberto Viola
0fb8e12d31 Watts not matching on Sole E95 Elliptical (Issue #1595) 2023-08-07 15:16:59 +02:00
Roberto Viola
679300c930 version 2.15.4 2023-08-07 14:10:02 +02:00
Roberto Viola
4008d588d7 "Recovery" tag on PowerZone peloton workout #1596 2023-08-07 14:02:56 +02:00
Roberto Viola
fda528babc Domyos Rower incorrect speed #1594 2023-08-06 14:52:11 +02:00
Roberto Viola
0e9bac6168 Watts not matching on Sole E95 Elliptical #1595 2023-08-06 14:49:14 +02:00
Roberto Viola
f8c1330862 version 2.15.1 for iOS 2023-08-06 10:39:41 +02:00
Roberto Viola
8e81df67d0 HR on iPad issue #1529 2023-08-06 10:12:32 +02:00
Roberto Viola
1a3c13eac9 Domyos Rower incorrect speed #1594 2023-08-05 11:05:17 +02:00
Roberto Viola
15b16c7e5c Domyos Rower incorrect speed #1594 2023-08-05 10:00:35 +02:00
Roberto Viola
b5ceb0a0f1 Costaway folding treadmill #1350 2023-08-02 17:11:24 +02:00
Roberto Viola
00939c56dd Costaway folding treadmill #1350 2023-08-02 15:18:41 +02:00
Roberto Viola
5f1ebc439e ANT+ Running speed sensor #1586 (#1589)
* first version (not tested)

* Update ChannelService.java

* Update SDMChannelController.java

* Update SDMChannelController.java

* Update bluetooth.cpp
2023-08-01 23:15:44 +02:00
Roberto Viola
81db615692 Runmaxx 9.1 Threadmill on Win 10 (Issue #1581) 2023-08-01 13:53:02 +02:00
Roberto Viola
65d12f00f4 fixing crash on ios 12 or lower 2023-07-31 21:37:58 +02:00
Roberto Viola
adcb87b3c4 Rowing Concept2 Pm3, Pm4 (Bluetooth) #1486 2023-07-31 11:55:41 +02:00
Roberto Viola
90900b786a fs5i nordick track more realistic ride when running with Rouvy or Zwift #1345 2023-07-27 08:40:49 +02:00
Roberto Viola
b495e833bd Inxide XS08 #1583 2023-07-27 08:19:34 +02:00
Roberto Viola
fb01341dd1 Release Android for Fdroid (#1582)
* Update main.yml

* fixing build error

* Update main.yml
2023-07-25 22:43:50 +02:00
Roberto Viola
867143e43e android 2.14.1 2023-07-24 21:01:32 +02:00
Roberto Viola
b85d7d72f5 2023 Sole F80 integration #1576 2023-07-24 11:00:17 +02:00
Roberto Viola
32cb3b9e37 2023 Sole F80 integration #1576 2023-07-24 10:19:24 +02:00
Roberto Viola
3ff8938b41 datetime tile now follow the locale of the user's system 2023-07-24 09:26:31 +02:00
Roberto Viola
5156cb38f0 adding virtual peloton bike to nordictrackadbbike 2023-07-24 06:26:19 +02:00
Roberto Viola
dd4a5dbc54 fixing hr on ios on ftmsbike 2023-07-24 06:25:58 +02:00
Roberto Viola
cf37b8705e fixing hr on ios on ftmsbike 2023-07-21 20:28:16 +02:00
Roberto Viola
bc8302c761 adding virtual peloton bike to nordictrackadbbike 2023-07-21 09:06:38 +02:00
Roberto Viola
76ecf5c66e adding virtual peloton bike to nordictrackadbbike 2023-07-21 09:03:31 +02:00
Roberto Viola
3693c2d1b2 v 2.14.0 2023-07-20 23:25:46 +02:00
Roberto Viola
3905bd8ba7 fixed peloton xml path 2023-07-20 23:12:21 +02:00
Roberto Viola
92cac7485e HR on iPad issue #1529 2023-07-20 16:34:15 +02:00
Roberto Viola
458c44758e Android QT Jars patched (#1575)
* Add files via upload

* adding only bluetooth one
2023-07-20 11:45:30 +02:00
Roberto Viola
be12859343 No charts when using floating window #1565 2023-07-19 09:53:21 +02:00
Roberto Viola
d201919b55 HR on iPad issue #1529 2023-07-19 09:38:07 +02:00
Roberto Viola
138a42c2e6 fixing CI build 2023-07-18 16:29:08 +02:00
Roberto Viola
bbe69f3f60 fixing build error 2023-07-18 16:02:50 +02:00
Roberto Viola
b95b3a5018 adding gears to wahookickrsnapbike as did for tacxneo
b443b03d49
2023-07-18 15:54:48 +02:00
Roberto Viola
fb0cbb74a5 adding smtp server info on the bottom of the email 2023-07-18 14:28:54 +02:00
Roberto Viola
deed6019ab adding restore purchase button for iOS guidelines 2023-07-18 14:28:33 +02:00
214 changed files with 14143 additions and 1624 deletions

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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>
![UI](docs/img/treadmill-bridge-schema.png)
[![Video](https://img.youtube.com/vi/GgG3dMhmo2Y/0.jpg)](https://www.youtube.com/watch?v=GgG3dMhmo2Y)
![UI](docs/img/ui.png)
![UI](docs/img/realtime-chart.png)
UI on Linux
![UI](docs/img/ui-mac.png)
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||
![First Success](docs/img/first_success.jpg)
### 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

View File

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

View File

@@ -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";

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

25
src/ChartFooter.qml Normal file
View 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;
}
}
}
}

View 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();
}
}
}
}

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

View File

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

View File

@@ -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: {

View File

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

View File

@@ -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();
}
}*/
}
}

View File

@@ -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: {

View File

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

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.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"/>

View File

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

View File

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

View File

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

View File

@@ -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");
}

View File

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

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

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

View 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();
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,6 +28,7 @@
#include "bike.h"
#include "virtualbike.h"
#include "virtualrower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"

View File

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

View File

@@ -76,6 +76,7 @@ class echelonrower : public rower {
bool noWriteResistance = false;
bool noHeartService = false;
metric speedRaw;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

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

View File

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

View File

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

View File

@@ -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()));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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!";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -88,6 +88,7 @@ class horizontreadmill : public treadmill {
bool mobvoi_treadmill = false;
bool kettler_treadmill = false;
bool anplus_treadmill = false;
void testProfileCRC();
void updateProfileCRC();

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

View File

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

View 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();
});

View File

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

View File

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

View File

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