Compare commits

...

184 Commits

Author SHA1 Message Date
Roberto Viola
89963c6b49 it's working so i need to add a setting for it in case 2025-11-14 12:49:54 +01:00
Roberto Viola
2ef0a3c5a7 kinomap and exr compatibility on android added 2025-11-14 10:45:48 +01:00
Roberto Viola
019b3c8abb Schwinn 411/510e cadence issue 2025-11-14 08:55:05 +01:00
Roberto Viola
317116f2d5 Update fakerower.cpp 2025-11-14 08:04:05 +01:00
Roberto Viola
fe005a2f00 SMARTBIKE added 2025-11-13 10:04:59 +01:00
Roberto Viola
08c1e26d3b mqtt settings in command line 2025-11-12 13:53:42 +01:00
Roberto Viola
e98820601a Update project.pbxproj 2025-11-12 11:02:21 +01:00
Roberto Viola
c499092460 Skandika wiry not working correct in qz app (Discussion #3860) 2025-11-12 08:18:17 +01:00
Roberto Viola
e3d50bda7c Add setting for Skandika X-2000 protocol selection
Introduces a new setting to enable or disable the X-2000 protocol for Skandika Wiri bikes. Updates the device discovery logic to respect this setting and adds a corresponding option in the settings UI, allowing users to select the appropriate protocol for their bike model.
2025-11-11 08:36:09 +01:00
Roberto Viola
c060e8b24a height setting fixed for miles units 2025-11-11 08:32:10 +01:00
Roberto Viola
f15f841860 Revert "Remove MLKit integration (#3859)"
This reverts commit 54a8b2619a.
2025-11-10 12:07:48 +01:00
Roberto Viola
15010b27dd Walking Tread Bootcamp (Issue #3856) 2025-11-10 10:03:03 +01:00
Roberto Viola
88c6091e21 Fix division by zero in speed calculation
Added a check to set Speed to 0 when instantPace is zero, preventing a division by zero error during speed calculation.
2025-11-10 08:27:59 +01:00
Roberto Viola
4a6df1c020 handleurl build fix for android 2025-11-09 12:04:34 +01:00
Roberto Viola
3d24e7c1a0 2.20.13 2025-11-09 11:52:18 +01:00
Roberto Viola
54a8b2619a Remove MLKit integration (#3859)
* Remove MLKit integration

- Remove OCR UI elements from settings.qml (Peloton and Zwift auto-sync features)
- Remove MLKit meta-data from AndroidManifest.xml
- Remove MLKit dependencies from build.gradle (both Amazon and Google Play versions)
- Keep underlying properties and qzsettings intact, only UI elements removed

* Restore Zwift OCR settings for Windows only

- Restored zwift_ocr, zwift_ocr_climb_portal, zwift_workout_ocr UI elements
- These settings are visible only on Windows (Qt.platform.os === "windows")
- Windows uses PaddleOCR for screen reading, not MLKit
- Android/iOS OCR features remain removed (they used MLKit)
- Updated labels to clarify these use PaddleOCR, not MLKit

* Remove MLKit OCR code from ScreenCaptureService

- Removed all MLKit imports (InputImage, Text, TextRecognition, etc.)
- Removed TextRecognizer field initialization
- Removed OCR processing code from onImageAvailable method
- Service kept for compatibility but OCR functionality disabled
- Fixes compilation errors after MLKit dependency removal

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-09 11:50:04 +01:00
Roberto Viola
038c4a6165 Update project.pbxproj 2025-11-07 08:14:27 +01:00
Roberto Viola
0831a4ed20 Comment out total distance calculation logic
email "debug qz file rower" from Martin B. on 6/11/2025

The code for calculating total distance from characteristic data has been commented out. Instead, distance is now incremented based on speed and elapsed time, possibly for testing or to address an issue with the original calculation.
2025-11-07 08:04:03 +01:00
Roberto Viola
74fc5f660c problem with Bkool smart bike 1.0 conection #3851 2025-11-07 07:59:22 +01:00
Roberto Viola
ae5dd54738 zwo message commands to TTS (Issue #3823) (#3824) 2025-11-05 14:46:38 +01:00
Roberto Viola
c0299b16ac Support for Proform Trainer 9.0 (PFTL69921-INT.4)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/144#issuecomment-3491211756
2025-11-05 14:41:48 +01:00
Roberto Viola
6401a66f4c Update project.pbxproj 2025-11-05 14:18:05 +01:00
Roberto Viola
ba064c2acd Support for Proform Trainer 9.0 (PFTL69921-INT.4)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/144#issuecomment-3490770427
2025-11-05 13:26:37 +01:00
Roberto Viola
9375f15207 Update AndroidManifest.xml 2025-11-05 10:40:17 +01:00
Roberto Viola
24183a4968 added all the android library aligned to 16k 2025-11-05 10:39:36 +01:00
Roberto Viola
9e1537caad 2.20.12 2025-11-05 10:07:38 +01:00
Roberto Viola
9fe72d13c0 Support for Proform Trainer 9.0 (PFTL69921-INT.4) (Issue #144)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/144#issuecomment-3490007042
2025-11-05 09:58:09 +01:00
Roberto Viola
df5e80a5be Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-11-05 09:39:30 +01:00
Roberto Viola
3b751d44e6 Treadmill incline multiplied on QZ output [BUG] #2511
BH Vanquish II no regula la velocidad automáticamente #3839
2025-11-05 09:39:24 +01:00
Roberto Viola
3815e45107 PitPat-T01 treadmill #3589 (#3737)
* PitPat-T01 treadmill #3589

* fixing

* wait for a packet and init from 0

* Update deerruntreadmill.cpp

* Update deerruntreadmill.cpp

* new xor

* stop command handled

* minspeedstep handled

* start and stop?

* start and stop

* Update deerruntreadmill.cpp
2025-11-05 08:25:12 +01:00
Roberto Viola
580eb3f092 Create libc++_shared.so 2025-11-04 17:02:41 +01:00
Roberto Viola
aba59cd136 Update peloton.h 2025-11-04 17:02:20 +01:00
Roberto Viola
369fbc4bc0 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-11-04 17:01:42 +01:00
Roberto Viola
871e704852 resistance for elliptical in the summary must be not the peloton resistance 2025-11-04 14:44:03 +01:00
Roberto Viola
b574e86804 cadence for elliptical in apple health must divide by 2 2025-11-04 14:43:44 +01:00
Roberto Viola
91735a714b Update project.pbxproj 2025-11-04 12:48:51 +01:00
Roberto Viola
da3b5b168e Trying to figure out how to best use this, monitor vs control (Discussion #3834) 2025-11-04 12:47:47 +01:00
Roberto Viola
d339cd461d Cadence and Wattage no responding the right way in Zwift (using a Elite Drivo 2) (Issue #3767) 2025-11-04 12:05:03 +01:00
Roberto Viola
f5ac438905 Update project.pbxproj 2025-11-04 11:42:42 +01:00
Roberto Viola
073b331535 Trying to figure out how to best use this, monitor vs control (Discussion #3834) 2025-11-04 11:40:49 +01:00
Roberto Viola
184c99ff6c Bkool smart bike (Issue #3774) 2025-11-04 11:35:06 +01:00
Roberto Viola
b25f7acf20 Support for Proform Trainer 9.0 (PFTL69921-INT.4)
https://github.com/cagnulein/QZCompanionNordictrackTreadmill/issues/144#issuecomment-3482194626
2025-11-04 11:11:33 +01:00
Roberto Viola
cb0df3ae27 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-11-04 10:56:20 +01:00
Roberto Viola
7f9ffb7e0e heart rate monitors battery levels (Issue #3833) 2025-11-04 10:55:49 +01:00
Roberto Viola
dd104da06d Update project.pbxproj 2025-11-04 10:37:54 +01:00
Roberto Viola
9440089d05 Trying to figure out how to best use this, monitor vs control (Discussion #3834) 2025-11-04 10:35:51 +01:00
Roberto Viola
39b6ad7463 Peloton and Yesoul resistance numbers the same #3729 2025-11-04 09:46:13 +01:00
Roberto Viola
fdc548fda7 Matrix FTMS Bikes not showing any data (Issue #3837) 2025-11-04 09:40:57 +01:00
Roberto Viola
cc85cbba4f Update project.pbxproj 2025-11-03 16:03:23 +01:00
Roberto Viola
7576a77cd8 Add sanity check for cadence calculation
Cadence is now only updated if the 5-second average is below 200, preventing unrealistic values from being set due to erroneous stride data.
2025-11-03 15:53:48 +01:00
Roberto Viola
61c633474a iOS Apple Health: elliptical as cycling speed and distance 2025-11-03 14:44:29 +01:00
Roberto Viola
69aefc0b30 Domyos 900 + garmin fenix = ANT+ FTMS profile ( smart trainer bike) (Issue #3835) 2025-11-03 14:01:32 +01:00
Roberto Viola
19ca844968 Update qdomyos-zwift.pri 2025-11-03 13:40:40 +01:00
Roberto Viola
5bb3a808a1 ftmselliptical: Refactor cadence calculation logic
Introduces an instantCadence metric to store the immediate cadence value and updates Cadence to use a 5-second average. This improves the accuracy and stability of cadence reporting in ypooelliptical.
2025-11-03 13:15:06 +01:00
Roberto Viola
63cedd457d Update project.pbxproj 2025-11-03 12:11:31 +01:00
Roberto Viola
d9925ac780 SCH_411_510E cadence overflow 2025-11-03 12:11:03 +01:00
Roberto Viola
e4a71e2940 Improve cadence calculation using stride count
Email: Schwinn 411/510e compatibility 03/11/2025

Cadence is now calculated from stride count differences when no external cadence sensor is present, providing more accurate RPM values. Added tracking for last stride count and timestamp to support this calculation.
2025-11-03 12:06:22 +01:00
Roberto Viola
d66d2fd915 Add support for FS-YK Bluetooth bike model
Recognizes devices with names starting with 'FS-YK-' and sets up model-specific flags and behavior. FS-YK bikes are now detected and marked as not supporting ERG mode natively.
2025-11-03 11:37:17 +01:00
Roberto Viola
d20f651672 Add option to force virtual treadmill mode
Introduces a new setting 'virtual_device_force_treadmill' allowing rower devices to be presented as treadmills to client apps. Updates the settings UI, QZSettings class, and device logic to support this feature.
2025-11-03 10:03:18 +01:00
Roberto Viola
19a564d832 Domyos bike 900 disconnecting[BUG] (Issue #3829) 2025-11-03 08:40:15 +01:00
Roberto Viola
5e100f8857 Enable manual workflow dispatch for nordictrack-build job
Modified the nordictrack-build job condition to allow manual triggering
in addition to scheduled runs.

Changes:
- Updated job condition from 'schedule' only to 'schedule || workflow_dispatch'
- This allows users to manually trigger the nordictrack build from GitHub Actions UI

Now the nordictrack-build job can be triggered:
1. Automatically: Every night at midnight (cron schedule)
2. Manually: Via GitHub Actions "Run workflow" button

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 16:16:46 +01:00
Roberto Viola
5491007a2a Add NordicTrack Rower variant to GitHub Actions workflow
Extended the nordictrack-build job to include a third variant for rower
devices, in addition to existing treadmill and bike variants.

Changes:
- Added rower variant to matrix strategy with proform_rower_ip setting
- Updated artifact list to include nordictrack-rower-android-trial APK
- Added rower description in release notes section

The workflow will now build three specialized APKs:
- android-debug-nordictrack-treadmill.apk (nordictrack_2950_ip)
- android-debug-nordictrack-bike.apk (tdf_10_ip)
- android-debug-nordictrack-rower.apk (proform_rower_ip) [NEW]

All three variants use the same nordictrack-build-grpc branch (PR #3478)
and set their respective IP settings to "localhost" for gRPC communication.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 16:12:57 +01:00
Giuseppe Macario
028b5b4c4a typos and grammar (#3768)
* Update HomeForm.ui.qml

grammar

* Update settings.qml

typos
2025-11-02 15:19:33 +01:00
Roberto Viola
8eb0083897 Add SCH_411_510E to power divisor exception list
SCH_411_510E devices now use a divisor of 1.0 for instant power calculation, aligning with other listed models. This change ensures correct power readings for SCH_411_510E ellipticals.
2025-11-02 14:52:52 +01:00
Roberto Viola
89ed87ecb1 Update project.pbxproj 2025-11-02 14:19:15 +01:00
Roberto Viola
eac18d7a51 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-11-02 14:18:15 +01:00
Roberto Viola
d9a50973cd Improve cadence and power calculation for SCH_411_510E
Cadence is now calculated from speed for SCH_411_510E devices when step count is unavailable and the cadence sensor is disabled. Instant power calculation logic is updated to always process when the flag is set, and SCH_411_510E is excluded from the DOMYOS power calculation branch.
2025-11-02 14:18:11 +01:00
Roberto Viola
39c33f3ebc Make SCH_290R behave like hammer_racer_s in FTMS bike
Add SCH_290R device support to FTMS bike with same behavior as hammer_racer_s.
The device will subscribe only to FTMS service and use the same initialization logic.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 12:15:28 +01:00
Roberto Viola
2858468a04 Update project.pbxproj 2025-11-01 21:16:52 +01:00
Roberto Viola
1bbbda4efb Technogym ride 2025-11-01 21:16:21 +01:00
Roberto Viola
49fc672538 Technogym ride 2025-11-01 21:08:18 +01:00
Roberto Viola
d93107e286 Update project.pbxproj 2025-11-01 20:00:38 +01:00
Roberto Viola
08af7d61a5 rouvy dircon fixed! 2025-11-01 19:07:54 +01:00
Roberto Viola
e25dfc2354 Add debug logging and update slope change handling
Added debug output for requested slope changes in BLEPeripheralManagerTreadmillZwift and corrected indentation. In virtualtreadmill.cpp, restored lastSlopeChanged assignment after heart rate update to ensure proper slope change timing.
2025-11-01 15:28:07 +01:00
Roberto Viola
a4a4d1b9c5 App closes after Bluetooth connection (Issue #3807) 2025-10-31 20:37:34 +01:00
Roberto Viola
5761865916 Only able to control resistance or incline at once (Issue #3798) (#3813) 2025-10-31 13:22:23 +01:00
Roberto Viola
5be7b8530e Add SCH_290R to recognized Bluetooth devices
Included support for devices with names starting with SCH_290R in the device discovery logic to ensure proper recognition and handling.
2025-10-31 10:40:29 +01:00
Roberto Viola
fe44490ad9 Update project.pbxproj 2025-10-31 08:19:52 +01:00
Roberto Viola
97138d8492 Add FTMS bike filter to device discovery
Device discovery now checks if the FTMS bike list contains the default FTMS bike before proceeding. This adds an additional filter to improve device selection accuracy.
2025-10-31 08:14:12 +01:00
Roberto Viola
76845d5507 Toputure TEB1 (Issue #3814)
Updated forceResistance and deviceDiscovered methods to handle SPORT01 devices. Resistance is now initialized to 1 for SPORT01, ensuring correct behavior on device discovery and resistance setting.
2025-10-30 14:20:23 +01:00
Roberto Viola
28bc5670c4 Update project.pbxproj 2025-10-30 13:24:55 +01:00
Roberto Viola
e0b84cb4a3 Check SCH_411_510E before processing instant power
Added a condition to ensure instant power is only processed when SCH_411_510E is not set. This prevents unintended behavior for devices with SCH_411_510E.
2025-10-30 13:23:27 +01:00
Roberto Viola
4cdf23d544 Toputure TEB1 #3814 2025-10-30 11:22:34 +01:00
Roberto Viola
7beb1aed6f set watchos to 6.0
https://developer.apple.com/forums/thread/805538
2025-10-30 09:00:59 +01:00
Roberto Viola
91841a1ff7 Find minimum start offset in target metrics
mail from Christian P. 29/10/2025 "qdomyos-zwift"

Replaces logic that assumed the first element was chronologically first with a search for the minimum 'start' offset among all target metrics. This ensures the correct offset is used even if the data is unordered.
2025-10-30 08:54:07 +01:00
Roberto Viola
886310497c Update project.pbxproj 2025-10-29 15:36:01 +01:00
Roberto Viola
5e96d3cff3 Update ypooelliptical.cpp 2025-10-29 13:59:20 +01:00
Roberto Viola
d66af32eed Update project.pbxproj 2025-10-29 13:55:41 +01:00
Roberto Viola
982318326f Elliptical FlowFitness 2Xi (Issue #3811) 2025-10-29 13:54:37 +01:00
Roberto Viola
2d680b9c4c Update project.pbxproj 2025-10-29 09:47:19 +01:00
Roberto Viola
17e2d211c1 Update watt calculation for SCH_411_510E device
Modified the power calculation logic to exclude SCH_411_510E from the divisor adjustment and to handle watt calculation for SCH_411_510E similarly to DOMYOS. Also moved the debug emit for current watt outside conditional blocks for consistent logging.
2025-10-29 09:41:14 +01:00
Roberto Viola
e1020be250 Bkool smart bike #3774 2025-10-29 09:17:22 +01:00
Roberto Viola
ce219a790a Sportstech sbike lite autoresistance in Rouvy #3803 2025-10-28 14:51:41 +01:00
Roberto Viola
7e55ded95d QZ Android Sends Data Even Without Pedaling (Issue #3763) 2025-10-28 12:31:45 +01:00
Roberto Viola
f70c6e8feb Add FIT_BK to 2-byte resistance protocol in ftmsbike
This ensures FIT_BK bikes receive resistance values multiplied by 10 using the 2-byte protocol, consistent with other similar bikes (JFBK5_0, DIRETO_XR, YPBM).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 19:53:36 +01:00
Roberto Viola
bdfaaecc83 Update WorkoutTracking.swift 2025-10-27 15:34:42 +01:00
Roberto Viola
4622fd6df2 Update project.pbxproj 2025-10-27 15:24:43 +01:00
Roberto Viola
fa5691fa2a Fix cadence handling for SCH_411_510E 2025-10-27 15:12:54 +01:00
Roberto Viola
2b995f8396 adding rowing, elliptical and walking on ios apple health 2025-10-27 14:55:34 +01:00
Roberto Viola
e044dc69bc fixing github ios CI 2025-10-27 11:45:25 +01:00
Roberto Viola
26bf095f19 Update project.pbxproj 2025-10-27 10:41:21 +01:00
Roberto Viola
2ba66d9625 writing ios heart rate without using apple watch and air pods 3 2025-10-27 10:40:45 +01:00
Roberto Viola
621440f981 writing ios heart rate without using apple watch and air pods 3 2025-10-27 09:47:56 +01:00
Roberto Viola
a0c1efce9c right device type in apple health from ios directly 2025-10-26 15:53:22 +01:00
Roberto Viola
861f916eb4 Update Windows build job to windows-2022
Changed the GitHub Actions workflow to use 'windows-2022' instead of 'windows-latest' for the window-build job. Also updated the build step to use Windows path separators and removed the explicit msys2 shell specification.
2025-10-26 14:23:28 +01:00
Roberto Viola
8ae1c59b41 Update project.pbxproj 2025-10-26 13:51:13 +01:00
Roberto Viola
d213b5dffe Add support for SCH411/510E elliptical device 2025-10-26 13:49:03 +01:00
Roberto Viola
099531be72 Después de cargar el archivo gpx, no se visualiza el mapa #3795 2025-10-26 13:40:54 +01:00
Roberto Viola
b7e92ab33c Fix Windows MinGW build after GitHub Actions runner update
GitHub migrated windows-latest from Windows Server 2022 to 2025, which
changed how PowerShell inherits PATH from MSYS2. The qthttpserver build
step now explicitly uses the msys2 shell to access MinGW tools (g++, make).

Changes:
- Add shell: msys2 {0} to Build qthttpserver step
- Fix path separator from backslash to forward slash for MSYS2 compatibility

Fixes: "Project ERROR: Cannot run compiler 'g++'"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 13:30:28 +01:00
Roberto Viola
0720d431db ios github build fixed 2025-10-26 13:16:39 +01:00
Roberto Viola
62c7b7b9df Add FTMS auto-detection for Domyos-Bike
- Check ftms_bike is disabled before connecting to domyosbike in bluetooth.cpp
- Auto-switch to FTMS bike when main service not found but FTMS service available
- Show toast notification to restart app when FTMS service detected

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 08:38:43 +02:00
Roberto Viola
4eed4be958 Update project.pbxproj 2025-10-24 17:55:13 +02:00
Roberto Viola
eb0dc48d24 Add support for SCH411/510E elliptical device
Added SCH411/510E device support to ypooelliptical class following the same FTMS pattern used for other devices like SCH_590E, E35, and KETTLER.

Changes:
- Added SCH_411_510E boolean flag to ypooelliptical.h
- Added device detection in bluetooth.cpp
- Updated all FTMS conditional checks to include SCH_411_510E

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 17:27:16 +02:00
Roberto Viola
4ff8e8335a GTABikeV ios compatibility 2025-10-24 08:42:05 +02:00
Roberto Viola
ec263a402d Update project.pbxproj 2025-10-23 21:14:47 +02:00
Roberto Viola
0ffb06cc79 Bkool smart bike #3774 (#3792) 2025-10-23 21:08:53 +02:00
Roberto Viola
302526000f Enable continuous gear shifting by decoupling from system volume limits on Android (Issue #3775) (#3779) 2025-10-23 13:34:33 +02:00
Roberto Viola
76891d41e2 Request : add support for Proform Sport 7.0 treadmill #2635
mail Re: I'm having problems with a Proform 7.0 Sport treadmill. The data is refreshed every 5 seconds, so it becomes impossible to uy application... in fact, with Kinomaps it ends up taking me out of the training due to many disconnections... for Kinomaps, when it stays at 0 km/h it pauses... thank you very much in advance and congratulations for this magnificent application 21/10/2025
2025-10-22 09:03:09 +02:00
Roberto Viola
bb3f9fe216 GTABikeV ios compatibility 2025-10-22 08:17:47 +02:00
Roberto Viola
dd0ce73260 Update trxappgateusbbike.cpp 2025-10-21 17:06:57 +02:00
Roberto Viola
206fa06049 Adjust packet length check for TOORX_SRX_500
Changed the minimum packet length for TOORX_SRX_500 from 21 to 19 in characteristicChanged. This ensures short packets are correctly ignored for this bike type.
2025-10-21 08:51:44 +02:00
Roberto Viola
3985eecfe6 Update trxappgateusbbike.cpp 2025-10-21 08:50:40 +02:00
Roberto Viola
97a7b5c27c Think A102-0063521 2025-10-20 15:05:24 +02:00
Roberto Viola
02c7063655 Update project.pbxproj 2025-10-20 10:59:18 +02:00
Roberto Viola
0067a728a4 iOS live activity continues after app closes (Issue #3783) 2025-10-20 09:34:23 +02:00
Roberto Viola
3ec15253d0 issues connecting zwift play with thinkrider max 2 (Issue #3758) (#3759)
* issues connecting zwift play with thinkrider max 2 (Issue #3758)

* Revert "issues connecting zwift play with thinkrider max 2 (Issue #3758)"

This reverts commit c657127675.

* avoiding char 0xFFF4 for cadence increment

* Update tacxneo2.cpp

* Update tacxneo2.cpp
2025-10-16 15:53:07 +02:00
Roberto Viola
09772d0968 ICSE patching
mail from Jean - Connectivity from 16/10/2025
2025-10-16 15:37:57 +02:00
Roberto Viola
6496e0cf7c Update project.pbxproj 2025-10-16 13:43:30 +02:00
Roberto Viola
b6ef01c59a ICSE patching
mail from Jean - Connectivity from 16/10/2025
2025-10-16 13:41:20 +02:00
Roberto Viola
522ea54ff2 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-10-15 10:24:55 +02:00
Roberto Viola
ba4cc11196 Walking speed surges/spikes #3757 2025-10-15 10:24:49 +02:00
Roberto Viola
95622182c9 Update project.pbxproj 2025-10-13 17:08:55 +02:00
Roberto Viola
0349dfcbaf Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-10-13 17:08:13 +02:00
Roberto Viola
9fc9d9cfe9 patching ftmsbike for multiple connectedAndDiscovered.
email from Jean-Noel - Connectivity from 13/10/2025
2025-10-13 16:12:53 +02:00
Roberto Viola
5ea16d0869 fixing git for live activities 2025-10-13 09:15:25 +02:00
Roberto Viola
57b259fcba ICSE reset to 15 seconds
mail "Connectivity" from Jean 13/10/2025
2025-10-13 08:05:34 +02:00
Roberto Viola
b78cf1fca5 fixing crash on ios 2025-10-11 07:39:04 +02:00
Roberto Viola
77b71e56de Update project.pbxproj 2025-10-10 14:00:25 +02:00
Roberto Viola
c8e3d370a1 fixing build on ios 2025-10-10 13:54:27 +02:00
Roberto Viola
ab8a325da2 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-10-10 13:43:24 +02:00
Roberto Viola
a3158514af Update project.pbxproj 2025-10-10 13:42:54 +02:00
Roberto Viola
2804d4686a Proform Rower (Proform 750R) cannot change resistance from app (Issue #3746) 2025-10-10 13:39:06 +02:00
Roberto Viola
cf0dc2d00d nordictrack integration broken #3731 2025-10-10 10:12:08 +02:00
Roberto Viola
164aa38cb1 adding horizon treadmill XP
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-09 20:53:30 +02:00
Roberto Viola
14998d0f25 TOPUTURE TP1 2025-10-09 13:29:40 +02:00
Roberto Viola
ca74fe7ccd fixing wattage reset on the elliptical class 2025-10-09 09:55:38 +02:00
Roberto Viola
facba11bae miles on live activity 2025-10-09 08:00:17 +02:00
Giuseppe Macario
8b8302fb53 typos (#3744) 2025-10-09 04:59:43 +02:00
Roberto Viola
eaea4bf8b8 Recently I managed to decode the Rowing data from Merach NovaRow R50, are any guideline I can follow in order to add it to the app? #3593 2025-10-08 14:01:24 +02:00
Roberto Viola
3d9c3e4103 Recently I managed to decode the Rowing data from Merach NovaRow R50, are any guideline I can follow in order to add it to the app? (Discussion #3593) 2025-10-08 12:00:02 +02:00
Roberto Viola
e840d7b3e9 Update bkoolbike.cpp 2025-10-07 15:03:27 +02:00
Roberto Viola
3a248ad2c5 Update project.pbxproj 2025-10-07 15:01:35 +02:00
Roberto Viola
5912d7df2d Airpods pro 3 heart rate (#3718)
* Airpods Pro 3 Heart Rate

* Update project.pbxproj
2025-10-07 14:53:48 +02:00
Roberto Viola
94842114e6 BKOOL Bike V 1 2025-10-07 14:47:16 +02:00
Roberto Viola
d83df0ba5a Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-10-07 08:54:33 +02:00
Roberto Viola
0764fb50b2 Peloton Walking Pace Targets at bottom of range (Issue #3738) 2025-10-07 08:54:26 +02:00
Roberto Viola
bb5de868ab Live Actions iOS (#3735)
* Add iOS Live Activity support at startup

- Created LiveActivityManager.swift for managing fitness metrics Live Activities
- Added Objective-C++ bridge (ios_liveactivity.h/mm) for Qt integration
- Updated Info.plist with NSSupportsLiveActivities flag
- Initialized LiveActivityManager in AppDelegate on app startup (iOS 16.1+)
- Added new files to qdomyos-zwift.pri build configuration

Live Activities display real-time fitness metrics (speed, cadence, power, heart rate, distance, calories) on Lock Screen and Dynamic Island.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update project.pbxproj

* Update AppDelegate.swift

* fixing build error

* let's see

* qzwidget

* distance fixed in live activities

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-10-05 14:34:17 +02:00
Roberto Viola
2b8fe6c28d Schwinn CABLE concerns (Issue #3727) 2025-10-04 06:50:45 +02:00
Roberto Viola
0153e09f0d TOPUTURE TP1 treadmill 2025-10-03 15:19:09 +02:00
Roberto Viola
dc44433d7c Update project.pbxproj 2025-10-03 12:45:32 +02:00
Roberto Viola
8bdefdb331 Auto inclination not working when using an Android tablet (Issue #3730) 2025-10-03 12:44:01 +02:00
Roberto Viola
c7f5e320fc Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-10-02 15:35:10 +02:00
Roberto Viola
c1582cc763 When using Watt Gain < 1 , unable to fulfil the requested power in Workouts (ERG) (Issue #3728) 2025-10-02 15:34:58 +02:00
Roberto Viola
f2f0f7a793 Update project.pbxproj 2025-10-01 17:07:23 +02:00
Roberto Viola
3d665e397e BKOOL Bike V 1 2025-10-01 17:06:13 +02:00
Roberto Viola
194f8686f3 Update project.pbxproj 2025-10-01 15:49:33 +02:00
Roberto Viola
fb79d0ddd6 SportsTech sWalk Walking Pad 2025-10-01 15:39:55 +02:00
Roberto Viola
d7e0a4e441 stop workout confirmation 2025-10-01 09:54:44 +02:00
Roberto Viola
465123a156 KUBIsport BC91EK 2025-10-01 09:30:48 +02:00
Roberto Viola
88d01562b1 DMASUN Bikes 2025-09-30 15:34:15 +02:00
Roberto Viola
85421f41b8 KUBIsport BC91EK 2025-09-30 08:11:11 +02:00
Roberto Viola
a67cb10633 Update project.pbxproj 2025-09-29 13:35:41 +02:00
Roberto Viola
f00a161fc1 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-09-29 13:33:10 +02:00
Roberto Viola
c071c56eb7 Toorx BRX R100 ergo bicycle 2025-09-29 13:33:03 +02:00
Roberto Viola
b39f769423 Update project.pbxproj 2025-09-28 08:41:58 +02:00
Roberto Viola
dde526c059 Merach MRK-T25-EF79 (T25) discovered but no tiles report any activity (Issue #3720) 2025-09-28 08:38:39 +02:00
Roberto Viola
c223d6e81d ORLAUF ARES device added 2025-09-27 14:44:58 +02:00
Roberto Viola
d531a1d313 ios adb debug 2025-09-26 09:05:07 +02:00
Roberto Viola
b0722cc827 ios adb log 2025-09-26 08:46:06 +02:00
Roberto Viola
2e534abfbb Update lockscreen.mm 2025-09-24 16:23:48 +02:00
Roberto Viola
6d3ca9877a Update project.pbxproj 2025-09-24 16:08:07 +02:00
Roberto Viola
f477cb32ab Proform CSX210 2025-09-24 12:10:34 +02:00
Roberto Viola
51b79ed413 Wattage gain max value increase #3709 2025-09-24 09:32:56 +02:00
Roberto Viola
fa78f03f0a D500 trainer added 2025-09-22 08:16:39 +02:00
216 changed files with 6243 additions and 3069 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
//
// QZWidget.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import WidgetKit
import SwiftUI
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), emoji: "😀")
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), emoji: "😀")
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, emoji: "😀")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct SimpleEntry: TimelineEntry {
let date: Date
let emoji: String
}
struct QZWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack {
Text("Time:")
Text(entry.date, style: .time)
Text("Emoji:")
Text(entry.emoji)
}
}
}
struct QZWidget: Widget {
let kind: String = "QZWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
QZWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
QZWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
#Preview(as: .systemSmall) {
QZWidget()
} timeline: {
SimpleEntry(date: .now, emoji: "😀")
SimpleEntry(date: .now, emoji: "🤩")
}

View File

@@ -0,0 +1,18 @@
//
// QZWidgetBundle.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import WidgetKit
import SwiftUI
@main
struct QZWidgetBundle: WidgetBundle {
var body: some Widget {
QZWidget()
QZWidgetControl()
QZWidgetLiveActivity()
}
}

View File

@@ -0,0 +1,54 @@
//
// QZWidgetControl.swift
// QZWidget
//
// Created by Roberto Viola on 04/10/25.
//
import AppIntents
import SwiftUI
import WidgetKit
struct QZWidgetControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: "org.cagnulein.qdomyoszwift.QZWidget",
provider: Provider()
) { value in
ControlWidgetToggle(
"Start Timer",
isOn: value,
action: StartTimerIntent()
) { isRunning in
Label(isRunning ? "On" : "Off", systemImage: "timer")
}
}
.displayName("Timer")
.description("A an example control that runs a timer.")
}
}
extension QZWidgetControl {
struct Provider: ControlValueProvider {
var previewValue: Bool {
false
}
func currentValue() async throws -> Bool {
let isRunning = true // Check if the timer is running
return isRunning
}
}
}
struct StartTimerIntent: SetValueIntent {
static let title: LocalizedStringResource = "Start a timer"
@Parameter(title: "Timer is running")
var value: Bool
func perform() async throws -> some IntentResult {
// Start / stop the timer based on `value`.
return .result()
}
}

View File

@@ -0,0 +1,174 @@
//
// QZWidgetLiveActivity.swift
// QDomyos-Zwift Live Activity Widget
//
// Displays workout metrics on Dynamic Island and Lock Screen
//
import ActivityKit
import WidgetKit
import SwiftUI
// QZWorkoutAttributes is defined in QZWorkoutAttributes.swift (shared file)
// MARK: - Live Activity Widget
@available(iOS 16.1, *)
struct QZWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: QZWorkoutAttributes.self) { context in
// Lock screen/banner UI
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI
DynamicIslandExpandedRegion(.leading) {
VStack(alignment: .leading, spacing: 4) {
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
Label("\(Int(speed)) \(speedUnit)", systemImage: "speedometer")
.font(.caption)
Label("\(context.state.heartRate) bpm", systemImage: "heart.fill")
.font(.caption)
.foregroundColor(.red)
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack(alignment: .trailing, spacing: 4) {
Label("\(Int(context.state.power)) W", systemImage: "bolt.fill")
.font(.caption)
.foregroundColor(.yellow)
Label("\(Int(context.state.cadence)) rpm", systemImage: "arrow.clockwise")
.font(.caption)
}
}
DynamicIslandExpandedRegion(.center) {
// Empty or can add more info
}
DynamicIslandExpandedRegion(.bottom) {
HStack {
let distanceKm = context.state.distance / 1000.0
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
Label(String(format: "%.2f \(distanceUnit)", distance), systemImage: "map")
Spacer()
Label("\(Int(context.state.kcal)) kcal", systemImage: "flame.fill")
.foregroundColor(.orange)
}
.font(.caption)
.padding(.horizontal)
}
} compactLeading: {
// Compact leading (left side of Dynamic Island)
HStack(spacing: 2) {
Image(systemName: "heart.fill")
.foregroundColor(.red)
Text("\(context.state.heartRate)")
.font(.caption2)
}
} compactTrailing: {
// Compact trailing (right side of Dynamic Island)
HStack(spacing: 2) {
Image(systemName: "bolt.fill")
.foregroundColor(.yellow)
Text("\(Int(context.state.power))")
.font(.caption2)
}
} minimal: {
// Minimal view (when multiple activities)
Image(systemName: "figure.run")
}
}
}
}
// MARK: - Lock Screen View
@available(iOS 16.1, *)
struct LockScreenLiveActivityView: View {
let context: ActivityViewContext<QZWorkoutAttributes>
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "figure.indoor.cycle")
.foregroundColor(.blue)
Text(context.attributes.deviceName)
.font(.headline)
Spacer()
}
HStack(spacing: 16) {
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
MetricView(icon: "speedometer", value: String(format: "%.1f", speed), unit: speedUnit)
MetricView(icon: "heart.fill", value: "\(context.state.heartRate)", unit: "bpm", color: .red)
MetricView(icon: "bolt.fill", value: "\(Int(context.state.power))", unit: "W", color: .yellow)
}
HStack(spacing: 16) {
let distanceKm = context.state.distance / 1000.0
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
MetricView(icon: "arrow.clockwise", value: "\(Int(context.state.cadence))", unit: "rpm")
MetricView(icon: "map", value: String(format: "%.2f", distance), unit: distanceUnit)
MetricView(icon: "flame.fill", value: "\(Int(context.state.kcal))", unit: "kcal", color: .orange)
}
}
.padding()
}
}
// MARK: - Metric View Component
struct MetricView: View {
let icon: String
let value: String
let unit: String
var color: Color = .primary
var body: some View {
VStack(spacing: 2) {
Image(systemName: icon)
.foregroundColor(color)
.font(.caption)
Text(value)
.font(.system(.body, design: .rounded))
.fontWeight(.semibold)
Text(unit)
.font(.caption2)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
}
}
// MARK: - Preview
@available(iOS 16.1, *)
struct QZWidgetLiveActivity_Previews: PreviewProvider {
static let attributes = QZWorkoutAttributes(deviceName: "QZ Bike", useMiles: false)
static let contentState = QZWorkoutAttributes.ContentState(
speed: 25.5,
cadence: 85,
power: 200,
heartRate: 145,
distance: 12500, // meters (will be displayed as 12.50 km or 7.77 mi)
kcal: 320,
useMiles: false
)
static var previews: some View {
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.compact))
.previewDisplayName("Island Compact")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.expanded))
.previewDisplayName("Island Expanded")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.minimal))
.previewDisplayName("Minimal")
attributes
.previewContext(contentState, viewKind: .content)
.previewDisplayName("Lock Screen")
}
}

View File

@@ -0,0 +1,41 @@
//
// QZWorkoutAttributes.swift
// QDomyos-Zwift
//
// Shared attributes for Live Activities
// MUST be included in both qdomyoszwift and QZWidget targets
//
import Foundation
import ActivityKit
@available(iOS 16.1, *)
public struct QZWorkoutAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
public var speed: Double
public var cadence: Double
public var power: Double
public var heartRate: Int
public var distance: Double
public var kcal: Double
public var useMiles: Bool
public init(speed: Double, cadence: Double, power: Double, heartRate: Int, distance: Double, kcal: Double, useMiles: Bool) {
self.speed = speed
self.cadence = cadence
self.power = power
self.heartRate = heartRate
self.distance = distance
self.kcal = kcal
self.useMiles = useMiles
}
}
public var deviceName: String
public var useMiles: Bool
public init(deviceName: String, useMiles: Bool) {
self.deviceName = deviceName
self.useMiles = useMiles
}
}

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
@@ -130,6 +130,7 @@
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */; };
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB42CEF8FD200839641 /* technogymbike.cpp */; };
870C72652E91565E00DC8A84 /* ios_liveactivity.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870C72632E91565E00DC8A84 /* ios_liveactivity.mm */; };
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706B29C48AEA0094D0F3 /* handleurl.cpp */; };
8710706E29C48AF30094D0F3 /* moc_handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */; };
8710707329C4A5E70094D0F3 /* GarminConnect.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710707229C4A5E70094D0F3 /* GarminConnect.swift */; };
@@ -597,6 +598,12 @@
87EBB2AB2D39214E00348B15 /* moc_workoutmodel.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A12D39214E00348B15 /* moc_workoutmodel.cpp */; };
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */; };
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */; };
87EFC5662E918D35005BB573 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87EFC5652E918D35005BB573 /* WidgetKit.framework */; };
87EFC5672E918D35005BB573 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */; };
87EFC5762E918D38005BB573 /* QZWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
87EFC57D2E918DAA005BB573 /* LiveActivityBridge.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */; };
87EFC58F2E919DB7005BB573 /* QZWorkoutAttributes.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */; };
87EFC5902E919DB7005BB573 /* QZWorkoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */; };
87EFE45927A518F5006EA1C3 /* nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */; };
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */; };
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E3E29178523000DB52C /* octaneelliptical.cpp */; };
@@ -704,6 +711,13 @@
remoteGlobalIDString = 876E4E102594747F00BD5714;
remoteInfo = watchkit;
};
87EFC5742E918D38005BB573 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 87EFC5632E918D35005BB573;
remoteInfo = QZWidgetExtension;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -729,6 +743,17 @@
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
87EFC57B2E918D38005BB573 /* Embed Foundation Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
87EFC5762E918D38005BB573 /* QZWidgetExtension.appex in Embed Foundation Extensions */,
);
name = "Embed Foundation Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
99542592E9780B9225F24AA8 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -974,6 +999,8 @@
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymbike.cpp; sourceTree = "<group>"; };
870A5DB42CEF8FD200839641 /* technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = technogymbike.cpp; path = ../src/devices/technogymbike/technogymbike.cpp; sourceTree = SOURCE_ROOT; };
870C72622E91565E00DC8A84 /* ios_liveactivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_liveactivity.h; path = ../src/ios/ios_liveactivity.h; sourceTree = SOURCE_ROOT; };
870C72632E91565E00DC8A84 /* ios_liveactivity.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_liveactivity.mm; path = ../src/ios/ios_liveactivity.mm; sourceTree = SOURCE_ROOT; };
8710706A29C48AE90094D0F3 /* handleurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = handleurl.h; path = ../src/handleurl.h; sourceTree = "<group>"; };
8710706B29C48AEA0094D0F3 /* handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = handleurl.cpp; path = ../src/handleurl.cpp; sourceTree = "<group>"; };
8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_handleurl.cpp; sourceTree = "<group>"; };
@@ -1693,6 +1720,11 @@
87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformtreadmill.cpp; path = ../src/devices/proformtreadmill/proformtreadmill.cpp; sourceTree = "<group>"; };
87EFB56D25BD703C0039DD5A /* proformtreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformtreadmill.h; path = ../src/devices/proformtreadmill/proformtreadmill.h; sourceTree = "<group>"; };
87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtreadmill.cpp; sourceTree = "<group>"; };
87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QZWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
87EFC5652E918D35005BB573 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveActivityBridge.swift; path = ../src/ios/LiveActivityBridge.swift; sourceTree = SOURCE_ROOT; };
87EFC57E2E919C98005BB573 /* QZWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QZWidgetExtension.entitlements; sourceTree = "<group>"; };
87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = QZWorkoutAttributes.swift; path = QZWidget/QZWorkoutAttributes.swift; sourceTree = "<group>"; };
87EFE45727A518F5006EA1C3 /* nautiluselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautiluselliptical.h; path = ../src/devices/nautiluselliptical/nautiluselliptical.h; sourceTree = "<group>"; };
87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautiluselliptical.cpp; path = ../src/devices/nautiluselliptical/nautiluselliptical.cpp; sourceTree = "<group>"; };
87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nautiluselliptical.cpp; sourceTree = "<group>"; };
@@ -1929,6 +1961,20 @@
FF5BDAB0076F3391B219EA52 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = "<absolute>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
87EFC5772E918D38005BB573 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 87EFC5632E918D35005BB573 /* QZWidgetExtension */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
87EFC5682E918D35005BB573 /* QZWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (87EFC5772E918D38005BB573 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = QZWidget; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
876E4E172594748000BD5714 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@@ -1937,6 +1983,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
87EFC5612E918D35005BB573 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
87EFC5672E918D35005BB573 /* SwiftUI.framework in Frameworks */,
87EFC5662E918D35005BB573 /* WidgetKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D1C883685E82D5676953459A /* Link Binary With Libraries */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -2278,6 +2333,10 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */,
87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */,
870C72622E91565E00DC8A84 /* ios_liveactivity.h */,
870C72632E91565E00DC8A84 /* ios_liveactivity.mm */,
876C646E2E74139F00F1BEC0 /* fitbackupwriter.h */,
876C646F2E74139F00F1BEC0 /* fitbackupwriter.cpp */,
876C64702E74139F00F1BEC0 /* moc_fitbackupwriter.cpp */,
@@ -3131,6 +3190,7 @@
4D765E1B1EA6C757220C63E7 /* CoreFoundation.framework */,
FCC237CA5AD60B9BA4447615 /* Foundation.framework */,
344F66310C19536DB4886D8F /* qtpcre2 */,
87EFC5652E918D35005BB573 /* WidgetKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -3393,6 +3453,7 @@
E8C543AB96796ECAA2E65C57 /* qdomyoszwift */ = {
isa = PBXGroup;
children = (
87EFC57E2E919C98005BB573 /* QZWidgetExtension.entitlements */,
8768C8CE2BBC12170099DBE1 /* adb */,
87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */,
8745B2752AFCB4A300991A39 /* android */,
@@ -3403,6 +3464,7 @@
74B182DB50CB5611B5C1C297 /* Supporting Files */,
876E4E122594747F00BD5714 /* watchkit */,
876E4E1E2594748000BD5714 /* watchkit Extension */,
87EFC5682E918D35005BB573 /* QZWidget */,
AF39DD055C3EF8226FBE929D /* Frameworks */,
858FCAB0EB1F29CF8B07677C /* Bundle Data */,
FE0A091FDBFB3E9C31B7A1BD /* Products */,
@@ -3417,6 +3479,7 @@
040B10E2EF2CEF79F2205FE2 /* qdomyoszwift.app */,
876E4E112594747F00BD5714 /* watchkit.app */,
876E4E1A2594748000BD5714 /* watchkit Extension.appex */,
87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */,
);
name = Products;
sourceTree = "<group>";
@@ -3433,11 +3496,13 @@
30414803F31797EB689AE508 /* Copy Bundle Resources */,
99542592E9780B9225F24AA8 /* Embed Frameworks */,
876E4E332594748100BD5714 /* Embed Watch Content */,
87EFC57B2E918D38005BB573 /* Embed Foundation Extensions */,
);
buildRules = (
);
dependencies = (
876E4E312594748100BD5714 /* PBXTargetDependency */,
87EFC5752E918D38005BB573 /* PBXTargetDependency */,
);
name = qdomyoszwift;
packageProductDependencies = (
@@ -3483,6 +3548,28 @@
productReference = 876E4E1A2594748000BD5714 /* watchkit Extension.appex */;
productType = "com.apple.product-type.watchkit2-extension";
};
87EFC5632E918D35005BB573 /* QZWidgetExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 87EFC5782E918D38005BB573 /* Build configuration list for PBXNativeTarget "QZWidgetExtension" */;
buildPhases = (
87EFC5602E918D35005BB573 /* Sources */,
87EFC5612E918D35005BB573 /* Frameworks */,
87EFC5622E918D35005BB573 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
87EFC5682E918D35005BB573 /* QZWidget */,
);
name = QZWidgetExtension;
packageProductDependencies = (
);
productName = QZWidgetExtension;
productReference = 87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -3490,7 +3577,7 @@
isa = PBXProject;
attributes = {
DefaultBuildSystemTypeForWorkspace = Original;
LastSwiftUpdateCheck = 1220;
LastSwiftUpdateCheck = 2600;
TargetAttributes = {
799833E5566DEFFC37E4BF1E = {
DevelopmentTeam = 6335M7T29D;
@@ -3506,6 +3593,9 @@
DevelopmentTeam = 6335M7T29D;
ProvisioningStyle = Automatic;
};
87EFC5632E918D35005BB573 = {
CreatedOnToolsVersion = 26.0.1;
};
};
};
buildConfigurationList = DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */;
@@ -3527,6 +3617,7 @@
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
876E4E102594747F00BD5714 /* watchkit */,
876E4E192594748000BD5714 /* watchkit Extension */,
87EFC5632E918D35005BB573 /* QZWidgetExtension */,
);
};
/* End PBXProject section */
@@ -3573,6 +3664,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
87EFC5622E918D35005BB573 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -3590,10 +3688,19 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
87EFC5602E918D35005BB573 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
87EFC5902E919DB7005BB573 /* QZWorkoutAttributes.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
F7E50F631C51CD5B5DC0BC43 /* Compile Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
870C72652E91565E00DC8A84 /* ios_liveactivity.mm in Compile Sources */,
8738249627E646E3004F1B46 /* characteristicnotifier2acd.cpp in Compile Sources */,
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */,
@@ -3842,6 +3949,7 @@
87943AB429E0215D007575F2 /* localipaddress.cpp in Compile Sources */,
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */,
ACB47DC464A2BC9D39C544AD /* gpx.cpp in Compile Sources */,
87EFC57D2E918DAA005BB573 /* LiveActivityBridge.swift in Compile Sources */,
6361329E515248BB41640C07 /* homeform.cpp in Compile Sources */,
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */,
87C5F0D026285E7E0067A1B5 /* moc_smtpclient.cpp in Compile Sources */,
@@ -3953,6 +4061,7 @@
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
876C64712E74139F00F1BEC0 /* moc_fitbackupwriter.cpp in Compile Sources */,
87EFC58F2E919DB7005BB573 /* QZWorkoutAttributes.swift in Compile Sources */,
876C64722E74139F00F1BEC0 /* fitbackupwriter.cpp in Compile Sources */,
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */,
@@ -4132,6 +4241,11 @@
target = 876E4E102594747F00BD5714 /* watchkit */;
targetProxy = 876E4E302594748100BD5714 /* PBXContainerItemProxy */;
};
87EFC5752E918D38005BB573 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 87EFC5632E918D35005BB573 /* QZWidgetExtension */;
targetProxy = 87EFC5742E918D38005BB573 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -4455,7 +4569,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1209;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4633,6 +4747,7 @@
QMAKE_PKGINFO_TYPEINFO = "????";
QMAKE_SHORT_VERSION = 1.7;
QT_LIBRARY_SUFFIX = "";
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
@@ -4655,7 +4770,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1209;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -4836,6 +4951,7 @@
QMAKE_PKGINFO_TYPEINFO = "????";
QMAKE_SHORT_VERSION = 1.7;
QT_LIBRARY_SUFFIX = _debug;
REGISTER_APP_GROUPS = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = YES;
@@ -4891,7 +5007,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1209;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4987,7 +5103,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1209;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5079,7 +5195,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1166;
CURRENT_PROJECT_VERSION = 1209;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5195,7 +5311,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1166;
CURRENT_PROJECT_VERSION = 1209;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5267,6 +5383,184 @@
};
name = Release;
};
87EFC5792E918D38005BB573 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1209;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QZWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QZWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-framework",
SwiftUI,
"-framework",
WidgetKit,
);
PRODUCT_BUNDLE_IDENTIFIER = org.cagnulein.qdomyoszwift.QZWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
87EFC57A2E918D38005BB573 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
APPLICATION_EXTENSION_API_ONLY = YES;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1209;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QZWidget/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = QZWidget;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-framework",
SwiftUI,
"-framework",
WidgetKit,
);
PRODUCT_BUNDLE_IDENTIFIER = org.cagnulein.qdomyoszwift.QZWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -5297,6 +5591,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
87EFC5782E918D38005BB573 /* Build configuration list for PBXNativeTarget "QZWidgetExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
87EFC5792E918D38005BB573 /* Debug */,
87EFC57A2E918D38005BB573 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */ = {
isa = XCConfigurationList;
buildConfigurations = (

View File

@@ -92,7 +92,7 @@ class BluetoothHandler : public QObject
void onKeyPressed(int keyCode)
{
qDebug() << "Key pressed:" << keyCode;
if (m_bluetooth && m_bluetooth->device() && m_bluetooth->device()->deviceType() == bluetoothdevice::BIKE) {
if (m_bluetooth && m_bluetooth->device() && m_bluetooth->device()->deviceType() == BIKE) {
if (keyCode == 115) // up
((bike*)m_bluetooth->device())->setGears(((bike*)m_bluetooth->device())->gears() + 1);
else if (keyCode == 114) // down

View File

@@ -72,7 +72,19 @@ HomeForm {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("New lap started!")
}
}
}
}
MessageDialog {
id: stopConfirmationDialog
text: qsTr("Stop Workout")
informativeText: qsTr("Do you really want to stop the current workout?")
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {
close();
inner_stop();
}
onNoClicked: close()
}
Timer {
@@ -141,7 +153,11 @@ HomeForm {
start.onClicked: { start_clicked(); }
stop.onClicked: {
inner_stop();
if (rootItem.confirmStopEnabled()) {
stopConfirmationDialog.open();
} else {
inner_stop();
}
}
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }

View File

@@ -165,7 +165,7 @@ Page {
width: parent.width
anchors.top: row1.bottom
anchors.topMargin: 30
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) both Bluetooth and Bluetooth permissions MUST be enabled<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
wrapMode: Label.WordWrap
visible: rootItem.labelHelp
}

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.20.11" android:versionCode="1155" 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.20.13" android:versionCode="1208" 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 -->

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,9 +7,13 @@ import android.content.IntentFilter;
import android.media.AudioManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
public class MediaButtonReceiver extends BroadcastReceiver {
private static MediaButtonReceiver instance;
private static final int TARGET_VOLUME = 7; // Middle volume value for infinite gear changes
private static boolean restoringVolume = false; // Flag to prevent recursion
@Override
public void onReceive(Context context, Intent intent) {
@@ -21,8 +25,30 @@ public class MediaButtonReceiver extends BroadcastReceiver {
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
QLog.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
QLog.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Previous: " + previousVolume + ", Max: " + maxVolume + ", Restoring: " + restoringVolume);
// If we're restoring volume, skip processing and reset flag
if (restoringVolume) {
QLog.d("MediaButtonReceiver", "Volume restore completed");
restoringVolume = false;
return;
}
// Process the gear change
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
// Auto-restore volume to middle value after a short delay to enable infinite gear changes
if (currentVolume != TARGET_VOLUME) {
final AudioManager am = audioManager;
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
QLog.d("MediaButtonReceiver", "Auto-restoring volume to: " + TARGET_VOLUME);
restoringVolume = true;
am.setStreamVolume(AudioManager.STREAM_MUSIC, TARGET_VOLUME, 0);
}
}, 100); // 100ms delay to ensure gear change is processed first
}
}
}
@@ -54,7 +80,25 @@ public class MediaButtonReceiver extends BroadcastReceiver {
}
}
QLog.d("MediaButtonReceiver", "Receiver registered successfully");
// Initialize volume to target value for gear control
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager != null) {
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (currentVolume != TARGET_VOLUME) {
QLog.d("MediaButtonReceiver", "Initializing volume to: " + TARGET_VOLUME);
restoringVolume = true;
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, TARGET_VOLUME, 0);
// Reset flag after initialization
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
restoringVolume = false;
}
}, 200);
}
}
} catch (IllegalArgumentException e) {
QLog.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
} catch (Exception e) {

View File

@@ -0,0 +1,6 @@
#ifndef BLUETOOTHDEVICETYPE_H
#define BLUETOOTHDEVICETYPE_H
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE, STAIRCLIMBER };
#endif // BLUETOOTHDEVICETYPE_H

View File

@@ -5,7 +5,7 @@ CharacteristicNotifier2A53::CharacteristicNotifier2A53(bluetoothdevice *Bike, QO
: CharacteristicNotifier(0x2a53, parent), Bike(Bike) {}
int CharacteristicNotifier2A53::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
BLUETOOTH_TYPE dt = Bike->deviceType();
value.append(0x02); // total distance
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
uint32_t distance = Bike->odometer() * 10000.0;

View File

@@ -8,7 +8,7 @@ int CharacteristicNotifier2A63::notify(QByteArray &value) {
if (normalizeWattage < 0)
normalizeWattage = 0;
if (Bike->deviceType() == bluetoothdevice::BIKE) {
if (Bike->deviceType() == BIKE) {
/*
// set measurement
measurement[2] = power & 0xFF;

View File

@@ -7,8 +7,8 @@ CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QO
: CharacteristicNotifier(0x2acd, parent), Bike(Bike) {}
int CharacteristicNotifier2ACD::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == TREADMILL || dt == ELLIPTICAL) {
value.append(0x0C); // Inclination available and distance for peloton
//value.append((char)0x01); // heart rate available
value.append((char)0x05); // HeartRate(8) | ElapsedTime(10)
@@ -46,7 +46,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
inclination /= gain;
}
if (dt == bluetoothdevice::TREADMILL)
if (dt == TREADMILL)
normalizeIncline = (uint32_t)qRound(inclination * 10);
a = (normalizeIncline >> 8) & 0XFF;
b = normalizeIncline & 0XFF;
@@ -54,7 +54,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
inclineBytes.append(b);
inclineBytes.append(a);
double ramp = 0;
if (dt == bluetoothdevice::TREADMILL)
if (dt == TREADMILL)
ramp = qRadiansToDegrees(qAtan(inclination / 100));
int16_t normalizeRamp = (int32_t)qRound(ramp * 10);
a = (normalizeRamp >> 8) & 0XFF;

View File

@@ -8,12 +8,12 @@ CharacteristicNotifier2AD2::CharacteristicNotifier2AD2(bluetoothdevice *Bike, QO
: CharacteristicNotifier(0x2ad2, parent), Bike(Bike) {}
int CharacteristicNotifier2AD2::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
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;
bool rowerAsABike = !virtual_device_rower && dt == ROWING;
bool double_cadence = settings.value(QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double).toBool();
double cadence_multiplier = 2.0;
if (double_cadence)
@@ -24,7 +24,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
if (normalizeWattage < 0)
normalizeWattage = 0;
if (dt == bluetoothdevice::BIKE || rowerAsABike) {
if (dt == 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
@@ -44,7 +44,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 || dt == bluetoothdevice::ROWING) {
} else if (dt == TREADMILL || dt == ELLIPTICAL || dt == ROWING) {
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
@@ -53,11 +53,11 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
value.append((char)(normalizeSpeed >> 8) & 0xFF); // speed
uint16_t cadence = 0;
if (dt == bluetoothdevice::ELLIPTICAL)
if (dt == ELLIPTICAL)
cadence = ((elliptical *)Bike)->currentCadence().value();
else if (dt == bluetoothdevice::TREADMILL)
else if (dt == TREADMILL)
cadence = ((treadmill *)Bike)->currentCadence().value();
else if (dt == bluetoothdevice::ROWING)
else if (dt == ROWING)
cadence = ((rower *)Bike)->currentCadence().value();
value.append((char)((uint16_t)(cadence * cadence_multiplier) & 0xFF)); // cadence

View File

@@ -10,7 +10,7 @@ CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistance
void CharacteristicWriteProcessor::changePower(uint16_t power) { Bike->changePower(power); }
void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr, uint8_t cw) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
BLUETOOTH_TYPE dt = Bike->deviceType();
QSettings settings;
bool force_resistance =
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
@@ -64,7 +64,7 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
qDebug() << "changeSlope CRR = " << fCRR << CRR_offset << "CW = " << fCW;
if (dt == bluetoothdevice::BIKE) {
if (dt == BIKE) {
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
// from Zwift
@@ -82,9 +82,9 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
Bike->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset + 1 +
CRR_offset + CW_offset); // resistance start from 1
}
} else if (dt == bluetoothdevice::TREADMILL) {
} else if (dt == TREADMILL) {
emit changeInclination(grade, percentage);
} else if (dt == bluetoothdevice::ELLIPTICAL) {
} else if (dt == ELLIPTICAL) {
bool inclinationAvailableByHardware = ((elliptical *)Bike)->inclinationAvailableByHardware();
qDebug() << "inclinationAvailableByHardware" << inclinationAvailableByHardware << "erg_mode" << erg_mode;
emit changeInclination(grade, percentage);

View File

@@ -13,8 +13,8 @@ CharacteristicWriteProcessor2AD9::CharacteristicWriteProcessor2AD9(double bikeRe
int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
if (data.size()) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::BIKE || dt == bluetoothdevice::ROWING) {
BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == BIKE || dt == ROWING) {
QSettings settings;
bool force_resistance =
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
@@ -82,7 +82,7 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
reply.append((quint8)cmd);
reply.append((quint8)FTMS_NOT_SUPPORTED);
}
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
} else if (dt == TREADMILL || dt == ELLIPTICAL) {
char a, b;
if ((char)data.at(0) == 0x02) {
// Set Target Speed
@@ -91,7 +91,7 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
uint16_t uspeed = a + (((uint16_t)b) << 8);
double requestSpeed = (double)uspeed / 100.0;
if (dt == bluetoothdevice::TREADMILL) {
if (dt == TREADMILL) {
((treadmill *)Bike)->changeSpeed(requestSpeed);
}
qDebug() << QStringLiteral("new requested speed ") + QString::number(requestSpeed);
@@ -103,10 +103,10 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
int16_t sincline = a + (((int16_t)b) << 8);
double requestIncline = (double)sincline / 10.0;
if (dt == bluetoothdevice::TREADMILL)
if (dt == TREADMILL)
((treadmill *)Bike)->changeInclination(requestIncline, requestIncline);
// Resistance as incline on Sole E95s Elliptical #419
else if (dt == bluetoothdevice::ELLIPTICAL) {
else if (dt == ELLIPTICAL) {
if(((elliptical *)Bike)->inclinationAvailableByHardware())
((elliptical *)Bike)->changeInclination(requestIncline, requestIncline);
else

View File

@@ -13,8 +13,8 @@ CharacteristicWriteProcessorE005::CharacteristicWriteProcessorE005(double bikeRe
int CharacteristicWriteProcessorE005::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
if (data.size()) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::BIKE) {
BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == BIKE) {
char cmd = data.at(0);
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), data);
if (cmd == wahookickrsnapbike::_setSimMode && data.count() >= 7) {
@@ -35,7 +35,7 @@ int CharacteristicWriteProcessorE005::writeProcess(quint16 uuid, const QByteArra
qDebug() << "erg mode" << watts;
changePower(watts);
}
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
} else if (dt == TREADMILL || dt == ELLIPTICAL) {
}
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)data.at(0));

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
activiotreadmill::activiotreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
android_antbike::android_antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
antbike::antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

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

View File

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

View File

@@ -211,7 +211,7 @@ resistance_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 1
void bike::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power, false); }
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
BLUETOOTH_TYPE bike::deviceType() { return BIKE; }
void bike::clearStats() {
@@ -381,6 +381,8 @@ uint8_t bike::metrics_override_heartrate() {
bool bike::inclinationAvailableByHardware() { return false; }
bool bike::inclinationAvailableBySoftware() { return false; }
uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
QSettings settings;
double watt = 0;

View File

@@ -31,7 +31,7 @@ class bike : public bluetoothdevice {
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t powerFromResistanceRequest(resistance_t requestResistance);
virtual bool ergManagedBySS2K() { return false; }
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
BLUETOOTH_TYPE deviceType() override;
metric pelotonResistance();
void clearStats() override;
void setLap() override;
@@ -51,6 +51,7 @@ class bike : public bluetoothdevice {
*/
metric currentSteeringAngle() { return m_steeringAngle; }
virtual bool inclinationAvailableByHardware();
virtual bool inclinationAvailableBySoftware();
bool ergModeSupportedAvailableByHardware() { return ergModeSupported; }
virtual bool ergModeSupportedAvailableBySoftware() { return ergModeSupported; }

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
bkoolbike::bkoolbike(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
@@ -49,7 +49,12 @@ void bkoolbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer);
if (gattWriteCharCustomId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer);
}
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
@@ -61,19 +66,28 @@ void bkoolbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
void bkoolbike::changePower(int32_t power) {
RequestedPower = power;
/*
if (power < 0)
power = 0;
uint8_t p[] = {0xa4, 0x09, 0x4e, 0x05, 0x31, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x02, 0x00};
p[10] = (uint8_t)((power * 4) & 0xFF);
p[11] = (uint8_t)((power * 4) >> 8);
for (uint8_t i = 0; i < sizeof(p) - 1; i++) {
p[12] ^= p[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(p, sizeof(p), QStringLiteral("changePower"), false, false);*/
if (power < 0) {
power = 0;
}
qDebug() << QStringLiteral("Changepower not implemented");
forcePower(power);
}
void bkoolbike::forcePower(int32_t power) {
// FE-C "Set Target Power" command (page 0x31)
// Power is sent in 1/4 watt units (0.25W resolution)
// Bytes: [0x31][0x25][0xFF][0xFF][0xFF][0xFF][power_low][power_high]
uint16_t power_quarter_watts = (uint16_t)(power * 4);
uint8_t power_cmd[] = {0x31, 0x25, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00};
power_cmd[6] = (uint8_t)(power_quarter_watts & 0xFF); // Low byte
power_cmd[7] = (uint8_t)((power_quarter_watts >> 8) & 0xFF); // High byte
writeCharacteristic(power_cmd, sizeof(power_cmd),
QStringLiteral("forcePower ") + QString::number(power) + QStringLiteral("W"),
false, false);
}
void bkoolbike::forceInclination(double inclination) {
@@ -115,13 +129,27 @@ void bkoolbike::update() {
uint8_t init1[] = {0x30, 0x25, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
uint8_t init2[] = {0x32, 0x25, 0xff, 0xff, 0xff, 0x1e, 0x7f, 0x00};
uint8_t init3[] = {0x33, 0x25, 0xff, 0xff, 0xff, 0x20, 0x4e, 0x00};
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
uint8_t init5[] = {0x37, 0xee, 0x16, 0xff, 0x80, 0x0c, 0x46, 0x21};
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init1"), false, false);
writeCharacteristic(init2, sizeof(init2), QStringLiteral("init2"), false, false);
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init3"), false, false);
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
if (bkool_fitness_bike) {
// BKOOLFITNESSBIKE specific init packets
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
uint8_t init5[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
uint8_t init6[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
uint8_t init7[] = {0x32, 0x25, 0xff, 0xff, 0xff, 0x25, 0x7f, 0x00};
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
writeCharacteristic(init6, sizeof(init6), QStringLiteral("init6"), false, true);
writeCharacteristic(init7, sizeof(init7), QStringLiteral("init7"), false, false);
} else {
// BKOOLSMARTPRO init packets
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
uint8_t init5[] = {0x37, 0xee, 0x16, 0xff, 0x80, 0x0c, 0x46, 0x21};
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
}
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
@@ -137,6 +165,13 @@ void bkoolbike::update() {
// updateDisplay(elapsed);
}
// Send poll command for BKOOLFITNESSBIKE
/*
if (bkool_fitness_bike) {
uint8_t poll[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
writeCharacteristic(poll, sizeof(poll), QStringLiteral("poll"), false, false);
}*/
if (requestResistance != -1) {
if (requestResistance != currentResistance().value() || lastGearValue != gears()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
@@ -146,10 +181,15 @@ void bkoolbike::update() {
requestInclination = requestResistance / 10.0;
}
// forceResistance(requestResistance);;
}
lastGearValue = gears();
}
requestResistance = -1;
}
if(lastGearValue != gears() && requestInclination == -100) {
// if only gears changed, we need to update the inclination to match the gears
requestInclination = lastRawRequestedInclinationValue;
}
if (requestInclination != -100) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
@@ -157,6 +197,8 @@ void bkoolbike::update() {
requestInclination = -100;
}
lastGearValue = gears();
if (requestPower != -1) {
changePower(requestPower);
requestPower = -1;
@@ -677,6 +719,12 @@ void bkoolbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
// Check if this is BKOOLFITNESSBIKE model
if (device.name().toUpper().startsWith(QStringLiteral("BKOOLFITNESSBIKE"))) {
bkool_fitness_bike = true;
emit debug(QStringLiteral("BKOOLFITNESSBIKE model detected"));
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &bkoolbike::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &bkoolbike::serviceScanDone);

View File

@@ -45,6 +45,7 @@ class bkoolbike : public bike {
bool wait_for_response = false);
void startDiscover();
void forceInclination(double inclination);
void forcePower(int32_t power);
uint16_t watts() override;
double bikeResistanceToPeloton(double resistance);
@@ -70,6 +71,8 @@ class bkoolbike : public bike {
bool noWriteResistance = false;
bool noHeartService = false;
bool bkool_fitness_bike = false;
uint16_t pollCounter = 0;
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;

View File

@@ -685,7 +685,19 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
filter = (b.name().compare(filterDevice, Qt::CaseInsensitive) == 0);
}
if (b.name().startsWith(QStringLiteral("M3")) && !m3iBike && filter) {
const QString deviceName = b.name();
const QString upperDeviceName = deviceName.toUpper();
bool isTrxAppGateUsbBikeTC = false;
if (upperDeviceName.startsWith(QStringLiteral("TC")) && deviceName.length() == 5) {
isTrxAppGateUsbBikeTC = true;
for (int idx = 2; idx < deviceName.length(); ++idx) {
if (!deviceName.at(idx).isDigit()) {
isTrxAppGateUsbBikeTC = false;
break;
}
}
}
if (deviceName.startsWith(QStringLiteral("M3")) && !m3iBike && filter) {
if (m3ibike::isCorrectUnit(b)) {
this->setLastBluetoothDevice(b);
@@ -1001,7 +1013,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
}
this->signalBluetoothDeviceConnected(domyosRower);
} else if ((b.name().startsWith(QStringLiteral("Domyos-Bike")) && (!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool())) &&
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && filter) {
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance, bikeResistanceOffset,
@@ -1016,7 +1028,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(domyosBike);
} else if (b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower &&
} else if ((b.name().toUpper().startsWith(QStringLiteral("MRK-R11S-")) ||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower)) &&
!trxappgateusbRower && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1070,6 +1083,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(domyosElliptical);
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
b.name().toUpper().startsWith(QStringLiteral("SCH411/510E")) ||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
b.name().toUpper().startsWith(QStringLiteral("FEIER-EM-")) ||
b.name().toUpper().startsWith(QStringLiteral("MX-AS ")) ||
@@ -1079,7 +1093,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO")) ||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical) ||
!b.name().compare(ftms_elliptical, Qt::CaseInsensitive)) && !ypooElliptical && filter) {
!b.name().compare(ftms_elliptical, Qt::CaseInsensitive)) && !ypooElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
ypooElliptical =
@@ -1230,6 +1244,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(soleElliptical);
} else if (b.name().startsWith(QStringLiteral("Domyos")) &&
!b.name().startsWith(QStringLiteral("DomyosBr")) &&
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKE-")) &&
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKING-")) && !domyos && !domyosElliptical && b.name().compare(ftms_treadmill, Qt::CaseInsensitive) &&
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill &&
(!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) &&
@@ -1487,6 +1502,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("AB300S-")) ||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && !b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) || // FIT-1596 and sports tech f37s treadmill #2412
b.name().toUpper().startsWith(QStringLiteral("FIT-TM-")) || // FIT-TM- treadmill with real inclination
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
(b.name().toUpper().startsWith("SCHWINN 810")) ||
@@ -1496,6 +1512,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("ANPIUS-")) ||
b.name().toUpper().startsWith(QStringLiteral("KICKR RUN")) ||
b.name().toUpper().startsWith(QStringLiteral("SPERAX_RM-01")) ||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) ||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
(b.name().toUpper().startsWith(QStringLiteral("KS-AP-"))) || // Kingsmith WalkingPad R3 Hybrid+
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
@@ -1511,12 +1528,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("CITYSPORTS-LINKER")) ||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15 && ftms_bike.contains(QZSettings::default_ftms_bike)) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("S77")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TM XP_")) // FTMS
) &&
!horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
@@ -1710,6 +1728,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("KETTLERBLE")) ||
(b.name().toUpper().startsWith("JAS_C3")) ||
(b.name().toUpper().startsWith("SCH_190U")) ||
(b.name().toUpper().startsWith("SCH_290R")) ||
(b.name().toUpper().startsWith("RAVE WHITE")) ||
(b.name().toUpper().startsWith("DOMYOS-BIKING-")) ||
(b.name().startsWith(QStringLiteral("Domyos-Bike")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool()) ||
@@ -1754,21 +1773,27 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("MRK-S26S-")) ||
(b.name().toUpper().startsWith("MRK-S26C-")) ||
(b.name().toUpper().startsWith("ROBX")) ||
(b.name().toUpper().startsWith("ORLAUF_ARES")) ||
(b.name().toUpper().startsWith("SPEEDMAGPRO")) ||
(b.name().toUpper().startsWith("XCX-")) ||
(b.name().toUpper().startsWith("SMARTBIKE-")) ||
(b.name().toUpper().startsWith("D500V2")) ||
(b.name().toUpper().startsWith("NEO BIKE PLUS ")) ||
(b.name().toUpper().startsWith(QStringLiteral("PM5")) && !b.name().toUpper().endsWith(QStringLiteral("SKI")) && !b.name().toUpper().endsWith(QStringLiteral("ROW"))) ||
(b.name().toUpper().startsWith("L-") && b.name().length() == 11) ||
(b.name().toUpper().startsWith("DMASUN-") && b.name().toUpper().endsWith("-BIKE")) ||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
(b.name().toUpper().startsWith("FS-YK-")) ||
(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 X")) || (b.name().toUpper().startsWith("MERACH-667-")) ||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE")) ||
(b.name().toUpper().startsWith("YPBM") && b.name().length() == 10)) &&
ftms_rower.contains(QZSettings::default_ftms_rower) &&
!ftmsBike && !ftmsRower && !snodeBike && !fitPlusBike && !stagesBike && filter) {
this->setLastBluetoothDevice(b);
@@ -1852,6 +1877,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(QStringLiteral("RM")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DR")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
(b.name().toUpper().startsWith(QStringLiteral("THINK A")) && b.name().length() == 18) ||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
!stagesBike && !ftmsBike && filter) {
@@ -2064,7 +2090,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(apexBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
apexBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(apexBike);
} else if (b.name().toUpper().startsWith(QStringLiteral("BKOOLSMARTPRO")) && !bkoolBike && filter) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("BKOOLSMARTPRO")) ||
b.name().toUpper().startsWith(QStringLiteral("BKOOLFBIKE")) ||
b.name().toUpper().startsWith(QStringLiteral("BKOOLFITNESSBIKE"))) && !bkoolBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
bkoolBike = new bkoolbike(noWriteResistance, noHeartService);
@@ -2444,15 +2472,15 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(activioTreadmill, &activiotreadmill::debug, this, &bluetooth::debug);
activioTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(activioTreadmill);
} else if (((b.name().startsWith(QStringLiteral("TOORX"))) ||
(b.name().startsWith(QStringLiteral("V-RUN"))) ||
(b.name().toUpper().startsWith(QStringLiteral("K80_"))) ||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+"))) ||
(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")))) &&
} else if (((deviceName.startsWith(QStringLiteral("TOORX"))) ||
(deviceName.startsWith(QStringLiteral("V-RUN"))) ||
(upperDeviceName.startsWith(QStringLiteral("K80_"))) ||
(upperDeviceName.startsWith(QStringLiteral("I-CONSOLE+"))) ||
(upperDeviceName.startsWith(QStringLiteral("ICONSOLE+"))) ||
(upperDeviceName.startsWith(QStringLiteral("I-RUNNING"))) ||
(upperDeviceName.startsWith(QStringLiteral("DKN RUN"))) ||
(upperDeviceName.startsWith(QStringLiteral("ADIDAS "))) ||
(upperDeviceName.startsWith(QStringLiteral("REEBOK")))) &&
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical && !iconsole_rower && ftms_elliptical.contains(QZSettings::default_ftms_elliptical) &&
ftms_bike.contains(QZSettings::default_ftms_bike) &&
filter) {
@@ -2466,22 +2494,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(trxappgateusb, &trxappgateusbtreadmill::debug, this, &bluetooth::debug);
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().toUpper().startsWith(QStringLiteral("BIKZU_")) ||
b.name().toUpper().startsWith(QStringLiteral("PASYOU-")) ||
b.name().toUpper().startsWith(QStringLiteral("VIRTUFIT")) ||
b.name().toUpper().startsWith(QStringLiteral("IBIKING+")) ||
((b.name().startsWith(QStringLiteral("TOORX")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
b.name().toUpper().startsWith(QStringLiteral("ICONSOLE+")) ||
b.name().toUpper().startsWith(QStringLiteral("VIFHTR2.1")) ||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK"))) ||
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
(b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
b.name().toUpper().startsWith(QStringLiteral("DKN MOTION"))) &&
} else if (isTrxAppGateUsbBikeTC ||
(upperDeviceName.startsWith(QStringLiteral("TUN ")) ||
upperDeviceName.startsWith(QStringLiteral("FITHIWAY")) ||
upperDeviceName.startsWith(QStringLiteral("FIT HI WAY")) ||
upperDeviceName.startsWith(QStringLiteral("BIKZU_")) ||
upperDeviceName.startsWith(QStringLiteral("PASYOU-")) ||
upperDeviceName.startsWith(QStringLiteral("VIRTUFIT")) ||
upperDeviceName.startsWith(QStringLiteral("IBIKING+")) ||
((deviceName.startsWith(QStringLiteral("TOORX")) ||
upperDeviceName.startsWith(QStringLiteral("I-CONSOIE+")) ||
upperDeviceName.startsWith(QStringLiteral("I-CONSOLE+")) ||
upperDeviceName.startsWith(QStringLiteral("ICONSOLE+")) ||
upperDeviceName.startsWith(QStringLiteral("VIFHTR2.1")) ||
(upperDeviceName.startsWith(QStringLiteral("REEBOK"))) ||
upperDeviceName.contains(QStringLiteral("CR011R")) ||
(upperDeviceName.startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
upperDeviceName.startsWith(QStringLiteral("DKN MOTION"))) &&
(toorx_bike))) &&
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower && ftms_elliptical.contains(QZSettings::default_ftms_elliptical)) {
this->setLastBluetoothDevice(b);
@@ -2587,7 +2616,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (((b.name().startsWith(QStringLiteral("FS-")) && fitplus_bike) ||
(b.name().toUpper().startsWith("H9110 OSAKA")) ||
b.name().startsWith(QStringLiteral("MRK-"))) &&
!fitPlusBike && !ftmsBike && !ftmsRower && !snodeBike && filter) {
!fitPlusBike && !ftmsBike && !ftmsRower && !snodeBike && !horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
fitPlusBike =
@@ -2734,11 +2763,11 @@ void bluetooth::connectedAndDiscovered() {
settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
// only at the first very connection, setting the user default resistance
if (device() && firstConnected && device()->deviceType() == bluetoothdevice::BIKE &&
if (device() && firstConnected && device()->deviceType() == BIKE &&
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt() != 1) {
qobject_cast<bike *>(device())->changeResistance(
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt());
} else if (device() && firstConnected && device()->deviceType() == bluetoothdevice::ELLIPTICAL &&
} else if (device() && firstConnected && device()->deviceType() == ELLIPTICAL &&
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt() !=
1) {
qobject_cast<elliptical *>(device())->changeResistance(
@@ -2906,7 +2935,7 @@ void bluetooth::connectedAndDiscovered() {
#else
settings.setValue(QZSettings::power_sensor_address, b.deviceUuid().toString());
#endif
if (device() && device()->deviceType() == bluetoothdevice::BIKE) {
if (device() && device()->deviceType() == BIKE) {
powerSensor = new stagesbike(false, false, true);
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
@@ -2915,7 +2944,7 @@ void bluetooth::connectedAndDiscovered() {
connect(powerSensor, &bluetoothdevice::cadenceChanged, this->device(),
&bluetoothdevice::cadenceSensor);
powerSensor->deviceDiscovered(b);
} else if (device() && device()->deviceType() == bluetoothdevice::TREADMILL) {
} else if (device() && device()->deviceType() == TREADMILL) {
powerSensorRun = new strydrunpowersensor(false, false, true);
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
@@ -2972,7 +3001,7 @@ void bluetooth::connectedAndDiscovered() {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().startsWith(eliteSterzoSmartName))) && !eliteSterzoSmart &&
!eliteSterzoSmartName.startsWith(QStringLiteral("Disabled")) && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
this->device()->deviceType() == BIKE) {
settings.setValue(QZSettings::elite_sterzo_smart_lastdevice_name, b.name());
#ifndef Q_OS_IOS
@@ -2994,7 +3023,7 @@ void bluetooth::connectedAndDiscovered() {
if(settings.value(QZSettings::sram_axs_controller, QZSettings::default_sram_axs_controller).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("SRAM "))) && !sramAXSController && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
this->device()->deviceType() == BIKE) {
sramAXSController = new sramaxscontroller();
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
@@ -3013,7 +3042,7 @@ void bluetooth::connectedAndDiscovered() {
if(settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("ZWIFT CLICK"))) && !zwiftClickRemote && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
this->device()->deviceType() == BIKE) {
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 9. is it? " << int(b.manufacturerData(2378).at(0));
@@ -3038,7 +3067,7 @@ void bluetooth::connectedAndDiscovered() {
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("SQUARE"))) && !eliteSquareController && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
this->device()->deviceType() == BIKE) {
eliteSquareController = new elitesquarecontroller(this->device());
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
@@ -3058,7 +3087,7 @@ void bluetooth::connectedAndDiscovered() {
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
this->device()->deviceType() == BIKE) {
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
@@ -3101,8 +3130,8 @@ void bluetooth::connectedAndDiscovered() {
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(),
device()->deviceType() == bluetoothdevice::TREADMILL ||
device()->deviceType() == bluetoothdevice::ELLIPTICAL,
device()->deviceType() == TREADMILL ||
device()->deviceType() == ELLIPTICAL,
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool(),
settings.value(QZSettings::technogym_group_cycle, QZSettings::default_technogym_group_cycle).toBool(),
settings.value(QZSettings::ant_bike_device_number, QZSettings::default_ant_bike_device_number).toInt(),
@@ -4115,7 +4144,7 @@ void bluetooth::stateFileUpdate() {
if (!device()) {
return;
}
if (device()->deviceType() != bluetoothdevice::TREADMILL) {
if (device()->deviceType() != TREADMILL) {
return;
}

View File

@@ -20,7 +20,7 @@ bluetoothdevice::~bluetoothdevice() {
}
}
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
BLUETOOTH_TYPE bluetoothdevice::deviceType() { return UNKNOWN; }
void bluetoothdevice::start() { requestStart = 1; lastStart = QDateTime::currentMSecsSinceEpoch(); }
void bluetoothdevice::stop(bool pause) {
requestStop = 1;
@@ -177,7 +177,17 @@ bool bluetoothdevice::changeFanSpeed(uint8_t speed) {
}
bool bluetoothdevice::connected() { return false; }
metric bluetoothdevice::elevationGain() { return elevationAcc; }
void bluetoothdevice::heartRate(uint8_t heart) { Heart.setValue(heart); }
void bluetoothdevice::heartRate(uint8_t heart) {
Heart.setValue(heart);
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
// Write heart rate from Bluetooth to Apple Health during workout
lockscreen h;
if(heart > 0)
h.setHeartRate(heart);
#endif
#endif
}
void bluetoothdevice::coreBodyTemperature(double coreBodyTemperature) { CoreBodyTemperature.setValue(coreBodyTemperature); }
void bluetoothdevice::skinTemperature(double skinTemperature) { SkinTemperature.setValue(skinTemperature); }
void bluetoothdevice::heatStrainIndex(double heatStrainIndex) { HeatStrainIndex.setValue(heatStrainIndex); }
@@ -237,7 +247,7 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts, const b
!power_as_bike && !power_as_treadmill)
watt_calc = false;
if(deviceType() == bluetoothdevice::BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
if(deviceType() == BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
if (!_firstUpdate && !paused) {
@@ -332,7 +342,8 @@ void bluetoothdevice::update_hr_from_external() {
double kcal = calories().value();
if(kcal < 0)
kcal = 0;
h.workoutTrackingUpdate(Speed.value(), Cadence.value(), (uint16_t)m_watt.value(), kcal, StepCount.value(), deviceType(), odometer() * 1000.0, totalCalories().value());
bool useMiles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
h.workoutTrackingUpdate(Speed.value(), Cadence.value(), (uint16_t)m_watt.value(), kcal, StepCount.value(), deviceType(), odometer() * 1000.0, totalCalories().value(), useMiles);
#endif
#endif
}

View File

@@ -1,6 +1,7 @@
#ifndef BLUETOOTHDEVICE_H
#define BLUETOOTHDEVICE_H
#include "bluetoothdevicetype.h"
#include "definitions.h"
#include "metric.h"
#include "qzsettings.h"
@@ -451,7 +452,6 @@ class bluetoothdevice : public QObject {
*/
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE, STAIRCLIMBER };
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
/**

View File

@@ -23,7 +23,7 @@ bowflext216treadmill::bowflext216treadmill(uint32_t pollDeviceTime, bool noConso
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -23,7 +23,7 @@ bowflextreadmill::bowflextreadmill(uint32_t pollDeviceTime, bool noConsole, bool
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
//#include <QtBluetooth/private/qlowenergyserviceprivate_p.h>
chronobike::chronobike(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
t_timeout = new QTimer(this);

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
target_watts.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);

View File

@@ -20,7 +20,7 @@
using namespace std::chrono_literals;
concept2skierg::concept2skierg(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -23,7 +23,7 @@ crossrope::crossrope(uint32_t pollDeviceTime, bool noConsole, bool noHeartServic
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -4,7 +4,7 @@ using namespace std::chrono_literals;
csafeelliptical::csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice,
int8_t bikeResistanceOffset, double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -3,7 +3,7 @@
using namespace std::chrono_literals;
csaferower::csaferower(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
cscbike::cscbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
cycleopsphantombike::cycleopsphantombike(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
deerruntreadmill::deerruntreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;
@@ -75,6 +75,14 @@ void deerruntreadmill::writeCharacteristic(const QLowEnergyCharacteristic charac
}
}
void deerruntreadmill::waitForAPacket() {
QEventLoop loop;
QTimer timeout;
connect(this, &deerruntreadmill::packetReceived, &loop, &QEventLoop::quit);
timeout.singleShot(3000, &loop, SLOT(quit()));
loop.exec();
}
void deerruntreadmill::writeUnlockCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log) {
QEventLoop loop;
QTimer timeout;
@@ -122,18 +130,70 @@ uint8_t deerruntreadmill::calculateXOR(uint8_t arr[], size_t size) {
return result;
}
uint8_t deerruntreadmill::calculatePitPatChecksum(uint8_t arr[], size_t size) {
uint8_t result = 0;
if (size < 5) {
qDebug() << QStringLiteral("array too small for PitPat checksum");
return 0;
}
// For PitPat protocol:
// 1. XOR from byte 5 to byte (size - 3) for long messages (>= 7 bytes)
// or from byte 2 to byte (size - 3) for short messages (< 7 bytes)
// 2. XOR the result with byte 1
size_t startIdx = (size < 7) ? 2 : 5;
for (size_t i = startIdx; i <= size - 3; i++) {
result ^= arr[i];
}
// XOR with byte 1 (command byte)
result ^= arr[1];
return result;
}
void deerruntreadmill::forceSpeed(double requestSpeed) {
QSettings settings;
uint8_t writeSpeed[] = {0x4d, 0x00, 0xc9, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd8, 0x43};
writeSpeed[2] = pollCounter;
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
if (pitpat) {
// PitPat speed template
// Pattern: 6a 17 00 00 00 00 [speed_high] [speed_low] 01 00 8a 00 04 00 00 00 00 00 12 2e 0c [checksum] 43
// Speed encoding: speed value * 1000 (e.g., 2.0 km/h = 2000 = 0x07d0)
uint8_t writeSpeed[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6c, 0x01, 0x00, 0x8a, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x2e, 0x0c, 0xc3, 0x43};
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
uint16_t speed = (uint16_t)(requestSpeed * 1000.0);
writeSpeed[6] = (speed >> 8) & 0xFF; // High byte
writeSpeed[7] = speed & 0xFF; // Low byte
writeSpeed[21] = calculatePitPatChecksum(writeSpeed, sizeof(writeSpeed)); // Checksum at byte 21
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed PitPat speed=") + QString::number(requestSpeed), false, true);
} else if (superun_ba04) {
// Superun BA04 speed template
uint8_t writeSpeed[] = {0x4d, 0x00, 0x14, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb5, 0x7c, 0xdb, 0x43};
writeSpeed[2] = pollCounter;
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed BA04 speed=") + QString::number(requestSpeed), false, false);
} else {
// Default speed template
uint8_t writeSpeed[] = {0x4d, 0x00, 0xc9, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd8, 0x43};
writeSpeed[2] = pollCounter;
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
}
}
void deerruntreadmill::forceIncline(double requestIncline) {
@@ -216,8 +276,7 @@ void deerruntreadmill::update() {
}
if (pitpat) {
uint8_t startData[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x43};
writeCharacteristic(gattWriteCharacteristic, startData, sizeof(startData), QStringLiteral("pitpat start"), false, true);
forceSpeed(1.0);
} else {
// should be:
// 0x49 = inited
@@ -240,13 +299,16 @@ void deerruntreadmill::update() {
emit tapeStarted();
} else if (requestStop != -1) {
emit debug(QStringLiteral("stopping... ") + paused);
/*if (lastState == PAUSED) {
uint8_t pause[] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x07};
writeCharacteristic(gattWriteCharacteristic, pause, sizeof(pause), QStringLiteral("pause"), false,
true);
} else*/ {
if (pitpat) {
uint8_t stop[] = {
0x6a, 0x17, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x05, 0x00,
0x8a, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x12, 0x2e,
0x0c, 0xaa, 0x43};
writeCharacteristic(gattWriteCharacteristic, stop, sizeof(stop), QStringLiteral("stop"), false, true);
} else {
uint8_t stop[] = {0x4d, 0x00, 0x48, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x50, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd6, 0x43};
stop[2] = pollCounter;
@@ -392,15 +454,37 @@ void deerruntreadmill::btinit(bool startTape) {
// PitPat treadmill initialization sequence
uint8_t initData1[] = {0x6a, 0x05, 0xfd, 0xf8, 0x43};
writeCharacteristic(gattWriteCharacteristic, initData1, sizeof(initData1), QStringLiteral("pitpat init 1"), false, true);
uint8_t unlockData[] = {0x6b, 0x05, 0x9d, 0x98, 0x43};
writeUnlockCharacteristic(unlockData, sizeof(unlockData), QStringLiteral("pitpat unlock"), false);
uint8_t initData2[] = {0x6a, 0x05, 0xd7, 0xd2, 0x43};
writeCharacteristic(gattWriteCharacteristic, initData2, sizeof(initData2), QStringLiteral("pitpat init 2"), false, true);
uint8_t startData[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x43};
writeCharacteristic(gattWriteCharacteristic, startData, sizeof(startData), QStringLiteral("pitpat start"), false, true);
} else if (superun_ba04) {
// Superun BA04 treadmill initialization sequence
// Wait for initial packet from treadmill before sending init
emit debug(QStringLiteral("BA04: waiting for initial packet..."));
waitForAPacket();
// Init 1: pollCounter = 0
uint8_t initData1[] = {0x4d, 0x00, 0x00, 0x05, 0x6a, 0x05, 0xfd, 0xf8, 0x43};
initData1[2] = 0; // pollCounter = 0
writeCharacteristic(gattWriteCharacteristic, initData1, sizeof(initData1), QStringLiteral("BA04 init 1"), false, true);
uint8_t initData2[] = {0x4d, 0x00, 0x00, 0x05, 0x6a, 0x05, 0xfd, 0xf8, 0x43};
initData1[2] = 1; // pollCounter = 0
writeCharacteristic(gattWriteCharacteristic, initData2, sizeof(initData2), QStringLiteral("BA04 init 2"), false, true);
// Init 2: pollCounter = 1
uint8_t initData3[] = {0x4d, 0x00, 0x01, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x05, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb5, 0x7c, 0x7c, 0x43};
initData3[2] = 2; // pollCounter = 1
writeCharacteristic(gattWriteCharacteristic, initData3, sizeof(initData3), QStringLiteral("BA04 init 3"), false, true);
// Start pollCounter from 2 after init
pollCounter = 3;
}
initDone = true;
}
@@ -413,6 +497,8 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattNotifyCharacteristicId((quint16)0xfff2);
QBluetoothUuid _pitpatWriteCharacteristicId((quint16)0xfba1);
QBluetoothUuid _pitpatNotifyCharacteristicId((quint16)0xfba2);
QBluetoothUuid _superunWriteCharacteristicId((quint16)0xff01);
QBluetoothUuid _superunNotifyCharacteristicId((quint16)0xff02);
QBluetoothUuid _unlockCharacteristicId((quint16)0x2b2a);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
@@ -427,7 +513,7 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << QStringLiteral("unlock char uuid") << c.uuid() << QStringLiteral("handle") << c.handle()
<< c.properties();
}
unlock_characteristic = unlock_service->characteristic(_unlockCharacteristicId);
if (unlock_characteristic.isValid()) {
emit debug(QStringLiteral("unlock characteristic found"));
@@ -445,6 +531,9 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
if (pitpat) {
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_pitpatWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_pitpatNotifyCharacteristicId);
} else if (superun_ba04) {
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_superunWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_superunNotifyCharacteristicId);
} else {
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
@@ -488,6 +577,7 @@ void deerruntreadmill::characteristicWritten(const QLowEnergyCharacteristic &cha
void deerruntreadmill::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
QBluetoothUuid _pitpatServiceId((quint16)0xfba0);
QBluetoothUuid _superunServiceId((quint16)0xffff);
QBluetoothUuid _unlockServiceId((quint16)0x1801);
emit debug(QStringLiteral("serviceScanDone"));
@@ -503,6 +593,11 @@ void deerruntreadmill::serviceScanDone(void) {
emit debug(QStringLiteral("Detected pitpat treadmill variant"));
gattCommunicationChannelService = m_control->createServiceObject(_pitpatServiceId);
unlock_service = m_control->createServiceObject(_unlockServiceId);
} else if (services_list.contains(_superunServiceId)) {
superun_ba04 = true;
pitpat = false;
emit debug(QStringLiteral("Detected Superun BA04 treadmill variant"));
gattCommunicationChannelService = m_control->createServiceObject(_superunServiceId);
} else {
pitpat = false;
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);

View File

@@ -41,6 +41,7 @@ class deerruntreadmill : public treadmill {
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected() override;
double minStepInclination() override;
double minStepSpeed() override { return 0.1; }
private:
void forceSpeed(double requestSpeed);
@@ -49,8 +50,10 @@ class deerruntreadmill : public treadmill {
void writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len,
const QString &info, bool disable_log = false, bool wait_for_response = false);
void writeUnlockCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false);
void waitForAPacket();
void startDiscover();
uint8_t calculateXOR(uint8_t arr[], size_t size);
uint8_t calculatePitPatChecksum(uint8_t arr[], size_t size);
bool noConsole = false;
bool noHeartService = false;
uint32_t pollDeviceTime = 200;
@@ -70,8 +73,9 @@ class deerruntreadmill : public treadmill {
QLowEnergyService *unlock_service = nullptr;
QLowEnergyCharacteristic unlock_characteristic;
bool pitpat = false;
bool superun_ba04 = false;
bool initDone = false;
bool initRequest = false;

View File

@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
OP(CYCLING_POWER, 0x1818, WAHOO_KICKR, P1, P2, P3) \
OP(CYCLING_SPEED_AND_CADENCE, 0x1816, WAHOO_KICKR, P1, P2, P3) \
OP(RUNNING_SPEED_AND_CADENCE, 0x1814, WAHOO_TREADMILL, P1, P2, P3) \
OP(HEART_RATE, 0x180D, WAHOO_BLUEHR, P1, P2, P3) \
/* OP(HEART_RATE, 0x180D, WAHOO_BLUEHR, P1, P2, P3) */ \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3)
#define ZWIFT_ENABLED_OP(OP, DESC, UUID, MACHINE, P1, P2, P3) OP(DESC, UUID, MACHINE, P1, P2, P3)
@@ -23,7 +23,7 @@ using namespace std::chrono_literals;
#define DM_MACHINE_OP(OP, P1, P2, P3) \
OP(WAHOO_KICKR, "Wahoo KICKR $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) \
/* OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) */ \
OP(WAHOO_RPM_SPEED, "Wahoo SPEED $uuid_hex$", DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
OP(WAHOO_TREADMILL, "Wahoo TREAD $uuid_hex$", DM_MACHINE_TYPE_TREADMILL, P1, P2, P3)
@@ -43,7 +43,7 @@ using namespace std::chrono_literals;
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD6, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x0A\x00\x96\x00\x0A\x00"), \
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE | DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0xE005, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_E005, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD2, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD3, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x00\x01"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
@@ -51,7 +51,7 @@ using namespace std::chrono_literals;
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_TREADMILL, 0x2AD6, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x0A\x00\x96\x00\x0A\x00"), \
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_TREADMILL, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9T, P1, P2, \
OP(FITNESS_MACHINE_TREADMILL, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE | DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9T, P1, P2, \
P3) \
OP(FITNESS_MACHINE_TREADMILL, 0x2ACD, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, \
P3) \
@@ -77,7 +77,7 @@ using namespace std::chrono_literals;
P3) \
OP(RUNNING_SPEED_AND_CADENCE, 0x2A53, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, \
P3) \
OP(HEART_RATE, 0x2A37, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
/* OP(HEART_RATE, 0x2A37, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) */ \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0003, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_0003, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0002, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0004, DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x02\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3)
@@ -171,9 +171,9 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
QSettings settings;
DirconProcessorService *service;
QList<DirconProcessorService *> services, proc_services;
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
BLUETOOTH_TYPE dt = Bike->deviceType();
bt = Bike;
uint8_t type = dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
uint8_t type = dt == TREADMILL || dt == ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
: DM_MACHINE_TYPE_BIKE;
qDebug() << "Building Dircom Manager";
uint16_t server_base_port =
@@ -236,7 +236,7 @@ double DirconManager::currentGear() {
QSettings settings;
if(settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool() && writeP0003)
return writeP0003->currentGear();
else if(bt && bt->deviceType() == bluetoothdevice::BIKE)
else if(bt && bt->deviceType() == BIKE)
return ((bike*)bt)->gears();
return 0;
}

View File

@@ -96,11 +96,11 @@ int DirconPacket::parse(const QByteArray &buf, int last_seq_number) {
} else
return DPKT_PARSE_ERROR - rembuf;
} else if (this->Identifier == DPKT_MSGID_ENABLE_CHARACTERISTIC_NOTIFICATIONS) {
if (this->Length == 16 || this->Length == 17) {
if (this->Length >= 16) {
quint16 uuid = ((quint16)buf.at(DPKT_MESSAGE_HEADER_LENGTH + DPKT_POS_SH8)) << 8;
uuid |= ((quint16)buf.at(DPKT_MESSAGE_HEADER_LENGTH + DPKT_POS_SH0)) & 0x00FF;
this->uuid = uuid;
if (this->Length == 17) {
if (this->Length >= 17) {
this->isRequest = true;
this->additional_data = buf.mid(DPKT_MESSAGE_HEADER_LENGTH + 16, 1);
}
@@ -117,6 +117,12 @@ int DirconPacket::parse(const QByteArray &buf, int last_seq_number) {
return rembuf;
} else
return DPKT_PARSE_ERROR - rembuf;
} else if (this->Identifier == DPKT_MSGID_UNKNOWN_0x07) {
if (this->Length == 0) {
this->isRequest = this->checkIsRequest(last_seq_number);
return DPKT_MESSAGE_HEADER_LENGTH;
} else
return DPKT_PARSE_ERROR - rembuf;
} else
return DPKT_PARSE_ERROR - rembuf;
} else
@@ -182,6 +188,10 @@ QByteArray DirconPacket::encode(int last_seq_number) {
}
}
}
} else if (this->Identifier == DPKT_MSGID_UNKNOWN_0x07) {
// Unknown message 0x07 - always respond with empty payload
this->Length = 0;
byteout.append(2, 0);
} else if (this->Identifier == DPKT_MSGID_DISCOVER_CHARACTERISTICS && !this->isRequest) {
this->Length = 16 + this->uuids.size() * 17;
byteout.append((char)(this->Length >> 8)).append((char)(this->Length));

View File

@@ -25,6 +25,7 @@
#define DPKT_MSGID_WRITE_CHARACTERISTIC 0x04
#define DPKT_MSGID_ENABLE_CHARACTERISTIC_NOTIFICATIONS 0x05
#define DPKT_MSGID_UNSOLICITED_CHARACTERISTIC_NOTIFICATION 0x06
#define DPKT_MSGID_UNKNOWN_0x07 0x07
#define DPKT_RESPCODE_SUCCESS_REQUEST 0x00
#define DPKT_RESPCODE_UNKNOWN_MESSAGE_TYPE 0x01
#define DPKT_RESPCODE_UNEXPECTED_ERROR 0x02

View File

@@ -187,7 +187,7 @@ DirconPacket DirconProcessor::processPacket(DirconProcessorClient *client, const
foreach (cc, service->chars) {
if (cc->uuid == pkt.uuid) {
cfound = true;
if (cc->type & DPKT_CHAR_PROP_FLAG_NOTIFY) {
if (cc->type & (DPKT_CHAR_PROP_FLAG_NOTIFY | DPKT_CHAR_PROP_FLAG_INDICATE)) {
int idx;
char notif = pkt.additional_data.at(0);
out.uuid = pkt.uuid;
@@ -208,6 +208,9 @@ DirconPacket DirconProcessor::processPacket(DirconProcessorClient *client, const
}
if (!cfound)
out.ResponseCode = DPKT_RESPCODE_CHARACTERISTIC_NOT_FOUND;
} else if (pkt.Identifier == DPKT_MSGID_UNKNOWN_0x07) {
// Unknown message 0x07 - respond with success
out.ResponseCode = DPKT_RESPCODE_SUCCESS_REQUEST;
}
}
return out;
@@ -225,7 +228,7 @@ bool DirconProcessor::sendCharacteristicNotification(quint16 uuid, const QByteAr
pkt.uuid = uuid;
for (QHash<QTcpSocket *, DirconProcessorClient *>::iterator i = clientsMap.begin(); i != clientsMap.end(); ++i) {
client = i.value();
/*if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool())*/ {
if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool()) {
socket = i.key();
rvs = socket->write(pkt.encode(0)) < 0;
if (rvs)

View File

@@ -3,6 +3,8 @@
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include "homeform.h"
#include "qzsettings.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
@@ -15,7 +17,7 @@ using namespace std::chrono_literals;
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
@@ -593,6 +595,22 @@ void domyosbike::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if(!gattCommunicationChannelService) {
// Main service not found, check if FTMS service is available
QBluetoothUuid ftmsServiceId((quint16)0x1826);
QLowEnergyService *ftmsService = m_control->createServiceObject(ftmsServiceId);
if(ftmsService) {
QSettings settings;
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
qDebug() << "forcing FTMS bike since it has FTMS service but not the main domyos service";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change");
delete ftmsService;
}
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
@@ -601,6 +619,8 @@ void domyosbike::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("domyosbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
m_control->disconnectFromDevice();
}
void domyosbike::error(QLowEnergyController::Error err) {

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool n
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -23,7 +23,7 @@ echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartSer
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -23,7 +23,7 @@ echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, int8_t b
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
speedRaw.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);

View File

@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
echelonstairclimber::echelonstairclimber(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
echelonstride::echelonstride(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -16,7 +16,7 @@
using namespace std::chrono_literals;
eliterizer::eliterizer(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -15,7 +15,7 @@
using namespace std::chrono_literals;
elitesterzosmart::elitesterzosmart(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -22,11 +22,15 @@ void elliptical::update_metrics(bool watt_calc, const double watts) {
WeightLoss = metric::calculateWeightLoss(KCal.value());
WattKg = m_watt.value() / settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
} else if (m_watt.value() > 0) {
m_watt = 0;
if (watt_calc) {
m_watt = 0;
}
WattKg = 0;
}
} else if (m_watt.value() > 0) {
m_watt = 0;
if (watt_calc) {
m_watt = 0;
}
WattKg = 0;
}
@@ -137,7 +141,7 @@ metric elliptical::currentInclination() { return Inclination; }
uint8_t elliptical::fanSpeed() { return FanSpeed; }
bool elliptical::connected() { return false; }
bluetoothdevice::BLUETOOTH_TYPE elliptical::deviceType() { return bluetoothdevice::ELLIPTICAL; }
BLUETOOTH_TYPE elliptical::deviceType() { return ELLIPTICAL; }
void elliptical::clearStats() {
moving.clear(true);

View File

@@ -24,7 +24,7 @@ class elliptical : public bluetoothdevice {
virtual int pelotonToEllipticalResistance(int pelotonResistance);
virtual bool inclinationAvailableByHardware();
virtual bool inclinationSeparatedFromResistance();
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
BLUETOOTH_TYPE deviceType() override;
void clearStats() override;
void setPaused(bool p) override;
void setLap() override;

View File

@@ -71,7 +71,7 @@ class CRC8
eslinkertreadmill::eslinkertreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
fakebike::fakebike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -16,7 +16,7 @@
using namespace std::chrono_literals;
fakeelliptical::fakeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -18,7 +18,7 @@
using namespace std::chrono_literals;
fakerower::fakerower(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
@@ -36,6 +36,24 @@ void fakerower::update() {
update_metrics(false, watts());
if (RequestedPower.value() != -1) {
m_watt = (double)RequestedPower.value() * (1.0 + (((double)rand() / RAND_MAX) * 0.4 - 0.2));
if(RequestedPower.value())
Cadence = 50 + (static_cast<double>(rand()) / RAND_MAX) * 50;
else
Cadence = 0;
emit debug(QStringLiteral("writing power ") + QString::number(RequestedPower.value()));
//requestPower = -1;
// bepo70: Disregard the current inclination for calculating speed. When the video
// has a high inclination you have to give many power to get the desired playback speed,
// if inclination is very low little more power gives a quite high speed jump.
// Speed = metric::calculateSpeedFromPower(m_watt.value(), Inclination.value(),
// Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), speedLimit());
Speed = metric::calculateSpeedFromPower(
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), 0);
}
Distance += ((Speed.value() / (double)3600.0) /
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();

View File

@@ -17,7 +17,7 @@
using namespace std::chrono_literals;
faketreadmill::faketreadmill(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -30,7 +30,7 @@ fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bik
QZ_EnableDiscoveryCharsAndDescripttors = true;
}
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -19,7 +19,7 @@ fitshowtreadmill::fitshowtreadmill(uint32_t pollDeviceTime, bool noConsole, bool
double forceInitInclination) {
Q_UNUSED(noConsole)
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noHeartService = noHeartService;

View File

@@ -15,7 +15,7 @@
using namespace std::chrono_literals;
flywheelbike::flywheelbike(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -24,7 +24,7 @@ focustreadmill::focustreadmill(uint32_t pollDeviceTime, bool noConsole, bool noH
QZ_EnableDiscoveryCharsAndDescripttors = false;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -26,7 +26,7 @@ using namespace std::chrono_literals;
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
@@ -251,10 +251,16 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
if(SL010)
if(requestResistance < 0) {
qDebug() << "Negative resistance detected:" << requestResistance << "using fallback value 1";
requestResistance = 1;
}
if(SL010 || SPORT01)
Resistance = requestResistance;
if(JFBK5_0 || DIRETO_XR) {
if(JFBK5_0 || DIRETO_XR || YPBM || FIT_BK) {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00, 0x00};
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
write[2] = ((uint16_t)requestResistance * 10) >> 8;
@@ -271,6 +277,26 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
}
}
void ftmsbike::forceInclination(double requestInclination) {
// FTMS SET_INDOOR_BIKE_SIMULATION_PARAMS command
// Byte 0: OpCode
// Byte 1-2: Wind Speed (sint16, 0.001 m/s)
// Byte 3-4: Grade/Inclination (sint16, 0.01%)
// Byte 5-6: Coefficient of Rolling Resistance (uint8, 0.0001)
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
// Convert inclination to FTMS format (multiply by 100 for 0.01% units)
int16_t inclination = (int16_t)(requestInclination * 100.0);
// Pack Grade in bytes 3-4 as little-endian sint16
write[3] = ((uint16_t)inclination) & 0xFF;
write[4] = ((uint16_t)inclination) >> 8;
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceInclination ") + QString::number(requestInclination));
}
void ftmsbike::update() {
QSettings settings;
@@ -282,8 +308,6 @@ void ftmsbike::update() {
if (initRequest) {
zwiftPlayInit();
if(ICSE)
requestResistance = 1; // to force the engine to send every second a target inclination
// when we are emulating the zwift protocol, zwift doesn't senf the start simulation frames, so we have to send them
if(settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool())
@@ -340,10 +364,23 @@ void ftmsbike::update() {
forceResistance(rR);
}
}
if(!ICSE)
requestResistance = -1;
requestResistance = -1;
}
// gpx scenario for example
if(!virtualBike || !virtualBike->ftmsDeviceConnected()) {
if ((requestInclination != -100 || lastGearValue != gears())) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
requestInclination = -100;
} else if(lastGearValue != gears() && lastRawRequestedInclinationValue != -100) {
// in order to send the new gear value ASAP
forceInclination(lastRawRequestedInclinationValue + gears()); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
}
}
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
@@ -543,7 +580,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
};
// clean time in case for a long period we don't receive values
if(lastRefreshCharacteristicChanged2AD2.secsTo(now) > 5) {
if(lastRefreshCharacteristicChanged2AD2.secsTo(now) > secondsToResetTimer) {
qDebug() << "clearing lastRefreshCharacteristicChanged2AD2" << lastRefreshCharacteristicChanged2AD2 << now;
lastRefreshCharacteristicChanged2AD2 = now;
}
@@ -574,6 +611,13 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
100.0;
index += 2;
emit debug(QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed));
// Use average speed if instant speed is not available (moreData flag set)
if (Flags.moreData) {
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = avgSpeed;
emit debug(QStringLiteral("Current Speed (from average): ") + QString::number(Speed.value()));
}
}
}
if (Flags.instantCadence) {
@@ -595,6 +639,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
2.0;
index += 2;
emit debug(QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence));
// Use average cadence if instant cadence is not available
if (!Flags.instantCadence) {
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = avgCadence;
emit debug(QStringLiteral("Current Cadence (from average): ") + QString::number(Cadence.value()));
}
}
}
if (Flags.totDistance) {
@@ -622,7 +675,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
if(BIKE_)
d = d / 10.0;
// for this bike, i will use the resistance that I set directly because the bike sends a different ratio.
if(!SL010 && !TITAN_7000)
if(!SL010 && !TITAN_7000 && !SPORT01)
Resistance = d;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit resistanceRead(Resistance.value());
@@ -638,18 +691,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
double cr = 97.62165482;
if (Cadence.value() && m_watt.value()) {
if(YS_G1MPLUS) {
m_pelotonResistance = Resistance.value(); // 1:1 ratio
} else {
m_pelotonResistance =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
m_pelotonResistance =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
if (!resistance_received && !DU30_bike && !SL010) {
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
@@ -663,6 +713,31 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
if(DU30_bike) {
m_watt = wattsFromResistance(Resistance.value());
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
} else if (SPORT01) {
// Custom power calculation for SPORT01
// Resistance multipliers for levels 1-10
const double k[10] = {0.60, 0.75, 0.85, 0.95, 1.00, 1.18, 1.40, 1.70, 2.00, 2.40};
// Baseline power curve coefficients (MyWhoosh cadence-power at resistance 5)
double ac = 0.01243107769;
double bc = 1.145964912;
double cc = -23.50977444;
// Calculate baseline power from cadence (resistance level 5 baseline)
double baseline_watt = ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc;
// Get current resistance level (1-10) and apply multiplier
int resistance_level = (int)Resistance.value();
if(resistance_level < 1) resistance_level = 1;
if(resistance_level > 10) resistance_level = 10;
// Apply resistance multiplier
m_watt = baseline_watt * k[resistance_level - 1];
if(m_watt.value() < 0) m_watt = 0;
emit debug(QStringLiteral("Current Watt (SPORT01 formula - R%1 x%2): %3")
.arg(resistance_level).arg(k[resistance_level - 1]).arg(m_watt.value()));
} else if (MRK_S26C) {
m_watt = Cadence.value() * (Resistance.value() * 1.16);
emit debug(QStringLiteral("Current Watt (MRK-S26C formula): ") + QString::number(m_watt.value()));
@@ -693,6 +768,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
// Use average power if instant power is zero or not available
if ((!Flags.instantPower || m_watt.value() == 0) && avgPower > 0) {
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
m_watt = avgPower;
emit debug(QStringLiteral("Current Watt (from average): ") + QString::number(m_watt.value()));
}
}
}
if (Flags.expEnergy && newValue.length() > index + 1) {
@@ -1212,7 +1296,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
}
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || DOMYOS || SMB1 || FIT_BK) {
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || SCH_290R || DOMYOS || SMB1 || FIT_BK) {
QBluetoothUuid ftmsService((quint16)0x1826);
if (s->serviceUuid() != ftmsService) {
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
@@ -1292,7 +1376,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SMB1 || FIT_BK)) {
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_290R || SMB1 || FIT_BK)) {
init();
}
@@ -1432,6 +1516,20 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
} else if(b.at(0) == FTMS_SET_TARGET_POWER && ((zwiftPlayService != nullptr && gears_zwift_ratio) || !ergModeSupported)) {
qDebug() << "discarding";
return;
} else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
// handling watt gain and offset for erg mode
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
if (watt_gain != 1.0 || watt_offset != 0) {
uint16_t powerRequested = (((uint8_t)b.at(1)) + (b.at(2) << 8));
qDebug() << "applying watt_gain/watt_offset from" << powerRequested;
powerRequested = ((powerRequested / watt_gain) - watt_offset);
qDebug() << "to" << powerRequested;
b[1] = powerRequested & 0xFF;
b[2] = powerRequested >> 8;
}
}
// gears on erg mode is quite useless and it's confusing
/* else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
@@ -1453,10 +1551,14 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
}
void ftmsbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
static bool connectedAndDiscoveredOk = false;
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
if(!connectedAndDiscoveredOk) {
connectedAndDiscoveredOk = true;
emit connectedAndDiscovered();
}
}
void ftmsbike::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
@@ -1541,6 +1643,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((bluetoothDevice.name().toUpper().startsWith("ICSE") && bluetoothDevice.name().length() == 4)) {
qDebug() << QStringLiteral("ICSE found");
ICSE = true;
secondsToResetTimer = 15;
autoResistanceEnable = false; // Disable auto resistance for ICSE bikes
qDebug() << QStringLiteral("ICSE: autoResistance disabled by default");
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
resistance_lvl_mode = true;
@@ -1553,6 +1658,10 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("SCH_190U found");
SCH_190U = true;
max_resistance = 100;
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_290R"))) {
qDebug() << QStringLiteral("SCH_290R found");
SCH_290R = true;
max_resistance = 100;
} else if(bluetoothDevice.name().toUpper().startsWith("D2RIDE")) {
qDebug() << QStringLiteral("D2RIDE found");
D2RIDE = true;
@@ -1633,9 +1742,26 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if(device.name().toUpper().startsWith("HAMMER")) {
qDebug() << QStringLiteral("HAMMER found");
HAMMER = true;
} else if(device.name().toUpper().startsWith("YPBM") && device.name().length() == 10) {
qDebug() << QStringLiteral("YPBM found");
YPBM = true;
resistance_lvl_mode = true;
ergModeSupported = false;
max_resistance = 32;
} else if(device.name().toUpper().startsWith("SPORT01")) {
qDebug() << QStringLiteral("SPORT01 found");
SPORT01 = true;
resistance_lvl_mode = true;
ergModeSupported = false;
max_resistance = 10;
Resistance = 1; // Initialize resistance to 1 for SPORT01
} else if(device.name().toUpper().startsWith("FS-YK-")) {
qDebug() << QStringLiteral("FS-YK- found");
FS_YK = true;
ergModeSupported = false; // this bike doesn't have ERG mode natively
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
resistance_lvl_mode = true;
}

View File

@@ -81,6 +81,7 @@ class ftmsbike : public bike {
// true because or the bike supports it by hardware or because QZ is emulating this in this module
bool ergModeSupportedAvailableBySoftware() override { return true; }
bool inclinationAvailableBySoftware() override { return !resistance_lvl_mode; }
private:
bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
@@ -94,6 +95,7 @@ class ftmsbike : public bike {
void init();
void forceResistance(resistance_t requestResistance);
void forcePower(int16_t requestPower);
void forceInclination(double requestInclination);
uint16_t wattsFromResistance(double resistance);
QTimer *refresh;
@@ -138,6 +140,7 @@ class ftmsbike : public bike {
bool DOMYOS = false;
bool _3G_Cardio_RB = false;
bool SCH_190U = false;
bool SCH_290R = false;
bool D2RIDE = false;
bool WATTBIKE = false;
bool VFSPINBIKE = false;
@@ -161,6 +164,11 @@ class ftmsbike : public bike {
bool MAGNUS = false;
bool MRK_S26C = false;
bool HAMMER = false;
bool YPBM = false;
bool SPORT01 = false;
bool FS_YK = false;
uint8_t secondsToResetTimer = 5;
int16_t T2_lastGear = 0;

View File

@@ -1,6 +1,7 @@
#include "devices/ftmsrower/ftmsrower.h"
#include "devices/ftmsbike/ftmsbike.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
@@ -19,7 +20,7 @@
using namespace std::chrono_literals;
ftmsrower::ftmsrower(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
@@ -359,12 +360,12 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
}
if (Flags.totDistance) {
Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
/*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 {
}/* else */{
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
}
@@ -380,8 +381,10 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
emit debug(QStringLiteral("Current Pace: ") + QString::number(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
if(instantPace == 0)
Speed = 0;
else
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()));
}
@@ -620,6 +623,8 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
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();
bool virtual_device_force_treadmill =
settings.value(QZSettings::virtual_device_force_treadmill, QZSettings::default_virtual_device_force_treadmill).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
@@ -637,7 +642,13 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
#endif
{
if (virtual_device_enabled) {
if (!virtual_device_rower) {
if (virtual_device_force_treadmill) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadmill, &virtualtreadmill::debug, this, &ftmsrower::debug);
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else if (!virtual_device_rower) {
emit debug(QStringLiteral("creating virtual bike interface..."));
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);

View File

@@ -64,11 +64,25 @@ void heartratebelt::disconnectBluetooth() {
void heartratebelt::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
emit packetReceived();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
// Handle Battery Service
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) {
if(newValue.length() > 0) {
uint8_t battery = (uint8_t)newValue.at(0);
if(battery != battery_level) {
if(homeform::singleton())
homeform::singleton()->setToastRequested(bluetoothDevice.name() + QStringLiteral(" Battery Level ") + QString::number(battery) + " %");
}
battery_level = battery;
qDebug() << QStringLiteral("battery: ") << battery;
}
return;
}
// Handle Heart Rate Measurement
if (newValue.length() > 1) {
Heart = (uint8_t)newValue[1];
emit heartRate((uint8_t)Heart.value());
@@ -82,6 +96,35 @@ void heartratebelt::stateChanged(QLowEnergyService::ServiceState state) {
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if (state == QLowEnergyService::ServiceDiscovered) {
// Check if this is the Battery Service
QLowEnergyService* service = qobject_cast<QLowEnergyService*>(sender());
if (service && service == gattBatteryService) {
// Handle Battery Service
auto characteristics_list = gattBatteryService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
emit debug(QStringLiteral("battery characteristic ") + c.uuid().toString());
}
connect(gattBatteryService, &QLowEnergyService::characteristicChanged, this,
&heartratebelt::characteristicChanged);
connect(gattBatteryService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &heartratebelt::errorService);
// Enable notifications for battery level
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattBatteryService->writeDescriptor(
c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
return;
}
// Original code for Heart Rate Service
auto characteristics_list = gattCommunicationChannelService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
emit debug(QStringLiteral("characteristic ") + c.uuid().toString());
@@ -134,7 +177,13 @@ void heartratebelt::serviceScanDone(void) {
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&heartratebelt::stateChanged);
gattCommunicationChannelService->discoverDetails();
return;
}
else if (s == QBluetoothUuid::BatteryService) {
QBluetoothUuid _gattBatteryServiceId(QBluetoothUuid::BatteryService);
gattBatteryService = m_control->createServiceObject(_gattBatteryServiceId);
connect(gattBatteryService, &QLowEnergyService::stateChanged, this,
&heartratebelt::stateChanged);
gattBatteryService->discoverDetails();
}
}
}

View File

@@ -37,10 +37,12 @@ class heartratebelt : public treadmill {
private:
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyService *gattBatteryService = nullptr;
QLowEnergyCharacteristic gattNotifyCharacteristic;
QDateTime connectingTime; // Timestamp when entering connecting state
static const int CONNECTION_TIMEOUT = 10000; // 10 seconds in milliseconds
QTimer* updateTimer; // Timer for periodic updates
uint8_t battery_level = 0;
signals:
void disconnected();

View File

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

View File

@@ -27,7 +27,7 @@ horizontreadmill::horizontreadmill(bool noWriteResistance, bool noHeartService)
testProfileCRC();
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
@@ -948,7 +948,7 @@ void horizontreadmill::update() {
requestInclination = treadmillInclinationOverrideReverse(requestInclination);
// this treadmill doesn't send the incline, so i'm forcing it manually
if(schwinn_810_treadmill) {
if(schwinn_810_treadmill || FIT_TM) {
Inclination = requestInclination;
}
@@ -1624,6 +1624,18 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
Speed = 0;
horizonPaused = true;
qDebug() << "stop from the treadmill";
} else if (TP1 && characteristic.uuid() == QBluetoothUuid((quint16)0x2ADA) && newValue.length() == 2 &&
(uint8_t)newValue.at(0) == 0x02 && (uint8_t)newValue.at(1) == 0x01) {
// TP1 treadmill start command received
qDebug() << "TP1 treadmill: received start packet from treadmill, sending start command";
emit debug(QStringLiteral("TP1 treadmill: received start packet from treadmill, sending start command"));
if (gattFTMSService) {
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 TP1", false, false);
}
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
union flags {
struct {
@@ -2537,8 +2549,15 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
HORIZON_78AT_treadmill = true;
qDebug() << QStringLiteral("HORIZON_7.8AT workaround ON!");
} else if(device.name().toUpper().startsWith("T01_")) {
ICONCEPT_FTMS_treadmill = true;
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
QSettings settings;
iconcept_ftms_treadmill_inclination_table = settings.value(QZSettings::iconcept_ftms_treadmill_inclination_table, QZSettings::default_iconcept_ftms_treadmill_inclination_table).toBool();
if(iconcept_ftms_treadmill_inclination_table) {
ICONCEPT_FTMS_treadmill = true;
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested(QStringLiteral("T01_ device detected. If you see strange inclination values, enable 'IConcept FTMS Treadmill' in Treadmill Options settings."));
}
} else if ((device.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
DOMYOS = true;
@@ -2551,6 +2570,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("MX-TM "))) {
qDebug() << QStringLiteral("MX-TM found");
MX_TM = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-TM-"))) {
qDebug() << QStringLiteral("FIT-TM- found (real inclination)");
FIT_TM = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-"))) {
qDebug() << QStringLiteral("FIT- found");
FIT = true;
@@ -2560,6 +2582,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("3G ELITE "))) {
qDebug() << QStringLiteral("3G ELITE");
T3G_ELITE = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("TP1")) && device.name().length() == 3) {
qDebug() << QStringLiteral("TP1 treadmill found");
TP1 = true;
}
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {

View File

@@ -102,14 +102,17 @@ class horizontreadmill : public treadmill {
bool disableAutoPause = false;
bool HORIZON_78AT_treadmill = false;
bool ICONCEPT_FTMS_treadmill = false;
bool iconcept_ftms_treadmill_inclination_table = false;
bool DOMYOS = false;
bool SW_TREADMILL = false;
bool BOWFLEX_T9 = false;
bool YPOO_MINI_PRO = false;
bool MX_TM = false;
bool FIT = false;
bool FIT_TM = false;
bool T3G_PRO = false;
bool T3G_ELITE = false;
bool TP1 = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -11,7 +11,7 @@
using namespace std::chrono_literals;
iconceptbike::iconceptbike() {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
initDone = false;

View File

@@ -15,7 +15,7 @@ iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartServi
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
initDone = false;

View File

@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
//#include <QtBluetooth/private/qlowenergyserviceprivate_p.h>
inspirebike::inspirebike(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
t_timeout = new QTimer(this);

View File

@@ -10,7 +10,7 @@
jumprope::jumprope() {}
bool jumprope::connected() { return false; }
bluetoothdevice::BLUETOOTH_TYPE jumprope::deviceType() { return bluetoothdevice::JUMPROPE; }
BLUETOOTH_TYPE jumprope::deviceType() { return JUMPROPE; }
uint16_t jumprope::watts(double weight) {

View File

@@ -11,7 +11,7 @@ class jumprope : public bluetoothdevice {
jumprope();
bool connected() override;
virtual uint16_t watts(double weight);
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
BLUETOOTH_TYPE deviceType() override;
void clearStats() override;
void setLap() override;
void setPaused(bool p) override;

View File

@@ -23,7 +23,7 @@ keepbike::keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -23,7 +23,7 @@ kineticinroadbike::kineticinroadbike(bool noWriteResistance, bool noHeartService
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
kingsmithr1protreadmill::kingsmithr1protreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
kingsmithr2treadmill::kingsmithr2treadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -28,7 +28,7 @@ lifefitnesstreadmill::lifefitnesstreadmill(bool noWriteResistance, bool noHeartS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -12,7 +12,7 @@ using namespace std::chrono_literals;
lifespantreadmill::lifespantreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

@@ -260,7 +260,7 @@ m3ibike::m3ibike(bool noWriteResistance, bool noHeartService) {
heartRateBeltDisabled = settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name)
.toString()
.startsWith(QStringLiteral("Disabled"));
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;

View File

@@ -21,7 +21,7 @@ mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResista
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

@@ -21,7 +21,7 @@ mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bik
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ nautilustreadmill::nautilustreadmill(uint32_t pollDeviceTime, bool noConsole, bo
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;

View File

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

View File

@@ -114,7 +114,7 @@ nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHe
bool nordictrack_ifit_adb_remote =
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
.toBool();
m_watt.setType(metric::METRIC_WATT);
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
@@ -300,62 +300,54 @@ void nordictrackifitadbbike::processPendingDatagrams() {
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
.toBool();
double inclination_delay_seconds = settings.value(QZSettings::inclination_delay_seconds, QZSettings::default_inclination_delay_seconds).toDouble();
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
// only resistance
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
if (nordictrack_ifit_adb_remote) {
if (requestResistance != -1) {
if (requestResistance != currentResistance().value()) {
int x1 = 950;
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
// Handle resistance and inclination changes when ADB is enabled
if (nordictrack_ifit_adb_remote) {
// Handle resistance changes with delay
if (requestResistance != -1 && lastResistanceChanged.secsTo(now) > inclination_delay_seconds) {
if (requestResistance != currentResistance().value()) {
int x1 = 950;
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
if(proform_tdf_10_0) {
x1 = 1175;
y2 = (int)(590 - (15.91 * requestResistance));
y1Resistance = (int)(590 - (15.91 * currentResistance().value()));
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
}
else if(!proform_studio_NTEX71021) { // s22i default
x1 = 1920 - 75;
y2 = (int)(803 - (23.777 * requestResistance));
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
if(proform_tdf_10_0) {
x1 = 1175;
y2 = (int)(590 - (15.91 * requestResistance));
y1Resistance = (int)(590 - (15.91 * currentResistance().value()));
}
else if(!proform_studio_NTEX71021) { // s22i default
x1 = 1920 - 75;
y2 = (int)(803 - (23.777 * requestResistance));
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
}
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> RESISTANCE: " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
#elif defined(Q_OS_WIN)
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
}
lastResistanceChanged = now;
}
requestResistance = -1;
}
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
requestResistance = -1;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
// since the motor of the bike is slow, let's filter the inclination changes to more than 4 seconds
else if (lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
lastInclinationChanged = now;
if (nordictrack_ifit_adb_remote) {
}
// Handle inclination changes with delay
if (requestInclination != -100 && lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
if (requestInclination != -100 && erg_mode && requestResistance != -100) {
qDebug() << "forcing inclination based on the erg mode resistance request of" << requestResistance;
@@ -365,7 +357,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
if (requestInclination != -100) {
double inc = qRound(requestInclination / 0.5) * 0.5;
if (inc != currentInclination().value()) {
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
@@ -382,7 +374,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
qDebug() << " >> INCLINATION: " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
@@ -396,43 +388,28 @@ void nordictrackifitadbbike::processPendingDatagrams() {
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
// this bike has both inclination and resistance, let's try to handle both
// the Original Poster doesn't want anymore, but maybe it will be useful in the future
/*
if(freemotion_coachbike_b22_7) {
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
#elif defined(Q_OS_WIN)
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
}
*/
}
}
requestInclination = -100;
}
double r = currentResistance().value() + difficult() + gears(); // the inclination here is like the resistance for the other bikes
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
requestInclination = -100;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
} else {
// Non-ADB mode: handle via UDP socket as before
// only resistance
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
requestResistance = -1;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
// inclination
else if (lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
lastInclinationChanged = now;
double r = currentResistance().value() + difficult() + gears();
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
requestInclination = -100;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
}
if (watts())

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