mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
111 Commits
opencv_and
...
latest
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39e2ef50cb | ||
|
|
880ac67fbc | ||
|
|
b21695bc14 | ||
|
|
eab058f401 | ||
|
|
58f62303be | ||
|
|
22f935c827 | ||
|
|
dce2ba7067 | ||
|
|
51665c76b4 | ||
|
|
a76cf602d6 | ||
|
|
a3f6bb8d0c | ||
|
|
3f83e4a999 | ||
|
|
14546d4ed3 | ||
|
|
1afff6a79d | ||
|
|
e95bf77c9d | ||
|
|
76ebceccbc | ||
|
|
f54b900c18 | ||
|
|
42e7749856 | ||
|
|
13fb8ae05a | ||
|
|
ee27386d11 | ||
|
|
5073ed766f | ||
|
|
c571a98775 | ||
|
|
1e66fb10b9 | ||
|
|
c9bcfb201e | ||
|
|
d701d01f13 | ||
|
|
f3ae244587 | ||
|
|
755e98d49a | ||
|
|
006c365fee | ||
|
|
2d9110c251 | ||
|
|
30e945f609 | ||
|
|
d3289fa5d1 | ||
|
|
1dd8846c2e | ||
|
|
9a3dc093a2 | ||
|
|
5674c75f4f | ||
|
|
f97e11b1d2 | ||
|
|
68a5bf7f44 | ||
|
|
d644aa4523 | ||
|
|
d04a1a9f01 | ||
|
|
e907170ba2 | ||
|
|
8a55ac1088 | ||
|
|
f83c7565cc | ||
|
|
7de500503e | ||
|
|
61b15254e1 | ||
|
|
2503835a59 | ||
|
|
00ceee2ee0 | ||
|
|
c93cf781fb | ||
|
|
a17da417c2 | ||
|
|
306ce699d4 | ||
|
|
fbbd40a0f8 | ||
|
|
5398b09643 | ||
|
|
94046b18fe | ||
|
|
4e9a03d931 | ||
|
|
2392251992 | ||
|
|
973ff3a586 | ||
|
|
87e5a1905d | ||
|
|
f39ca765d8 | ||
|
|
b33ce3233d | ||
|
|
4209f6f653 | ||
|
|
7d36b4082b | ||
|
|
a3015858f9 | ||
|
|
12515752f6 | ||
|
|
9334c6b472 | ||
|
|
884bea8352 | ||
|
|
8b4c579539 | ||
|
|
c9f3a3a092 | ||
|
|
547bc8a5c9 | ||
|
|
9c600dbc00 | ||
|
|
f29d9fd1e6 | ||
|
|
8d1e98e81f | ||
|
|
94ac9d1bdd | ||
|
|
9e74918b65 | ||
|
|
3be43c14e1 | ||
|
|
e1b951e664 | ||
|
|
53126b16d3 | ||
|
|
0fb8e12d31 | ||
|
|
679300c930 | ||
|
|
4008d588d7 | ||
|
|
fda528babc | ||
|
|
0e9bac6168 | ||
|
|
f8c1330862 | ||
|
|
8e81df67d0 | ||
|
|
1a3c13eac9 | ||
|
|
15b16c7e5c | ||
|
|
b5ceb0a0f1 | ||
|
|
00939c56dd | ||
|
|
5f1ebc439e | ||
|
|
81db615692 | ||
|
|
65d12f00f4 | ||
|
|
adcb87b3c4 | ||
|
|
90900b786a | ||
|
|
b495e833bd | ||
|
|
fb01341dd1 | ||
|
|
867143e43e | ||
|
|
b85d7d72f5 | ||
|
|
32cb3b9e37 | ||
|
|
3ff8938b41 | ||
|
|
5156cb38f0 | ||
|
|
dd4a5dbc54 | ||
|
|
cf37b8705e | ||
|
|
bc8302c761 | ||
|
|
76ecf5c66e | ||
|
|
3693c2d1b2 | ||
|
|
3905bd8ba7 | ||
|
|
92cac7485e | ||
|
|
458c44758e | ||
|
|
be12859343 | ||
|
|
d201919b55 | ||
|
|
138a42c2e6 | ||
|
|
bbe69f3f60 | ||
|
|
b95b3a5018 | ||
|
|
fb0cbb74a5 | ||
|
|
deed6019ab |
85
.github/workflows/main.yml
vendored
85
.github/workflows/main.yml
vendored
@@ -176,6 +176,9 @@ jobs:
|
||||
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
|
||||
if: matrix.config.python == false
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/windows/5.15.2/binary/*.* ${{ github.workspace }}/src/debug/output/
|
||||
|
||||
- name: Zip artifact for deployment
|
||||
run: Compress-Archive src/debug/output release.zip
|
||||
|
||||
@@ -193,6 +196,28 @@ jobs:
|
||||
path: release.zip
|
||||
if: ${{ ! matrix.config.python }}
|
||||
|
||||
- name: upload windows artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: ${{ ! matrix.config.python && startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: release.zip
|
||||
asset_name: windows-binary-no-python.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: upload windows artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: ${{ matrix.config.python && startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: release.zip
|
||||
asset_name: windows-binary.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
# window-steam-build:
|
||||
# runs-on: windows-latest
|
||||
#
|
||||
@@ -277,6 +302,19 @@ jobs:
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- name: release
|
||||
uses: actions/create-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
id: create_release
|
||||
with:
|
||||
draft: false
|
||||
prerelease: false
|
||||
release_name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref }}
|
||||
body_path: CHANGELOG.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
# - name: Cache Qt Linux Desktop
|
||||
# id: cache-qt-linux-desktop
|
||||
# uses: actions/cache@v1
|
||||
@@ -478,6 +516,13 @@ jobs:
|
||||
path: "tst/googletest/"
|
||||
ref: "release-1.12.1"
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checkout qHttpServer
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: qt-labs/qthttpserver
|
||||
path: "src/qthttpserver"
|
||||
|
||||
- name: Install packages required to run QZ inside workflow
|
||||
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
|
||||
|
||||
@@ -513,7 +558,7 @@ jobs:
|
||||
- name: Install Qt Android
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
version: '5.15.0'
|
||||
host: 'linux'
|
||||
target: 'android'
|
||||
arch: 'android'
|
||||
@@ -527,6 +572,20 @@ jobs:
|
||||
with:
|
||||
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||
java-version: '11'
|
||||
|
||||
- name: patching qt for bluetooth
|
||||
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
|
||||
|
||||
- name: download 3rd party files for qthttpserver
|
||||
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
|
||||
|
||||
- name: Build qthttpserver
|
||||
run: |
|
||||
cd src/qthttpserver
|
||||
qmake
|
||||
make -j8
|
||||
make install
|
||||
cd ../..
|
||||
|
||||
- name: Set Android NDK 21 && build
|
||||
run: |
|
||||
@@ -538,6 +597,13 @@ jobs:
|
||||
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
|
||||
export ANDROID_NDK="${ANDROID_SDK_ROOT}/ndk-bundle"
|
||||
export ANDROID_NDK_ROOT="${ANDROID_NDK}"
|
||||
cd src
|
||||
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
|
||||
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
|
||||
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
|
||||
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
|
||||
echo "#define LICENSE" >> secret.h
|
||||
cd ..
|
||||
|
||||
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
|
||||
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
|
||||
@@ -546,6 +612,23 @@ jobs:
|
||||
- name: Build APK (not usable for production due to unpatched QT library)
|
||||
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
|
||||
|
||||
- name: Archive apk binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: fdroid-android-trial
|
||||
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
|
||||
|
||||
- name: upload windows artifact
|
||||
uses: actions/upload-release-asset@v1
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
|
||||
asset_name: fdroid-android-trial.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
ios-build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: macos-latest
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -49,3 +49,4 @@ src/inner_templates/googlemaps/cesium-key.js
|
||||
*.autosave
|
||||
.vscode/settings.json
|
||||
/tst/Devices/.vs
|
||||
src/inner_templates/googlemaps/cesium-key.js
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
@@ -3666,7 +3666,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -3741,7 +3741,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
"-g",
|
||||
@@ -3834,7 +3834,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -3911,7 +3911,7 @@
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/playlistformats,
|
||||
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = (
|
||||
"-pipe",
|
||||
@@ -4038,7 +4038,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4063,7 +4063,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4134,7 +4134,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4155,7 +4155,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4226,7 +4226,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4271,7 +4271,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
@@ -4340,7 +4340,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 612;
|
||||
CURRENT_PROJECT_VERSION = 645;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4381,7 +4381,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.13;
|
||||
MARKETING_VERSION = 2.16;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
|
||||
|
||||
@@ -104,6 +104,9 @@ extension MainController: WorkoutTrackingDelegate {
|
||||
"\(heartRate)" as AnyObject])
|
||||
WorkoutTracking.distance = WatchKitConnection.distance
|
||||
WorkoutTracking.kcal = WatchKitConnection.kcal
|
||||
WorkoutTracking.speed = WatchKitConnection.speed
|
||||
WorkoutTracking.power = WatchKitConnection.power
|
||||
WorkoutTracking.cadence = WatchKitConnection.cadence
|
||||
|
||||
if Locale.current.measurementSystem != "Metric" {
|
||||
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")
|
||||
|
||||
@@ -24,6 +24,9 @@ class WatchKitConnection: NSObject {
|
||||
public static var distance = 0.0
|
||||
public static var kcal = 0.0
|
||||
public static var stepCadence = 0
|
||||
public static var speed = 0.0
|
||||
public static var cadence = 0.0
|
||||
public static var power = 0.0
|
||||
weak var delegate: WatchKitConnectionDelegate?
|
||||
|
||||
private override init() {
|
||||
@@ -66,6 +69,13 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
|
||||
WatchKitConnection.distance = dDistance
|
||||
let dKcal = Double(result["kcal"] as! Double)
|
||||
WatchKitConnection.kcal = dKcal
|
||||
|
||||
let dSpeed = Double(result["speed"] as! Double)
|
||||
WatchKitConnection.speed = dSpeed
|
||||
let dPower = Double(result["power"] as! Double)
|
||||
WatchKitConnection.power = dPower
|
||||
let dCadence = Double(result["cadence"] as! Double)
|
||||
WatchKitConnection.cadence = dCadence
|
||||
}, errorHandler: { (error) in
|
||||
print(error)
|
||||
})
|
||||
|
||||
@@ -31,6 +31,10 @@ class WorkoutTracking: NSObject {
|
||||
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
|
||||
public static var cadenceLastSteps = Double()
|
||||
public static var cadenceSteps = 0
|
||||
public static var speed = Double()
|
||||
public static var power = Double()
|
||||
public static var cadence = Double()
|
||||
public static var lastDateMetric = Date()
|
||||
var sport: Int = 0
|
||||
let healthStore = HKHealthStore()
|
||||
let configuration = HKWorkoutConfiguration()
|
||||
@@ -146,14 +150,31 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
|
||||
let infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
var infoToShare: Set<HKSampleType> = []
|
||||
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingPower)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingSpeed)!,
|
||||
HKSampleType.quantityType(forIdentifier: .cyclingCadence)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
infoToShare = Set([
|
||||
HKSampleType.quantityType(forIdentifier: .stepCount)!,
|
||||
HKSampleType.quantityType(forIdentifier: .heartRate)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceCycling)!,
|
||||
HKSampleType.quantityType(forIdentifier: .distanceWalkingRunning)!,
|
||||
HKSampleType.quantityType(forIdentifier: .activeEnergyBurned)!,
|
||||
HKSampleType.workoutType()
|
||||
])
|
||||
}
|
||||
|
||||
HKHealthStore().requestAuthorization(toShare: infoToShare, read: infoToRead) { (success, error) in
|
||||
if success {
|
||||
@@ -168,6 +189,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
|
||||
}
|
||||
|
||||
func startWorkOut() {
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
print("Start workout")
|
||||
configWorkout()
|
||||
workoutSession.startActivity(with: Date())
|
||||
@@ -312,6 +334,68 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
|
||||
handleSendStatisticsData(statistics)
|
||||
}
|
||||
}
|
||||
|
||||
if #available(watchOSApplicationExtension 10.0, *) {
|
||||
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
|
||||
doubleValue: WorkoutTracking.power)
|
||||
|
||||
if(WorkoutTracking.lastDateMetric.distance(to: Date()) < 1) {
|
||||
return
|
||||
}
|
||||
|
||||
guard let powerType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingPower) else {
|
||||
return
|
||||
}
|
||||
let wattPerIntervalSample = HKQuantitySample(type: powerType,
|
||||
quantity: wattPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([wattPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let cadencePerInterval = HKQuantity(unit: HKUnit.count().unitDivided(by: HKUnit.minute()),
|
||||
doubleValue: WorkoutTracking.cadence)
|
||||
|
||||
guard let cadenceType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingCadence) else {
|
||||
return
|
||||
}
|
||||
let cadencePerIntervalSample = HKQuantitySample(type: cadenceType,
|
||||
quantity: cadencePerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([cadencePerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
|
||||
doubleValue: WorkoutTracking.speed * 0.277778)
|
||||
|
||||
guard let speedType = HKQuantityType.quantityType(
|
||||
forIdentifier: .cyclingSpeed) else {
|
||||
return
|
||||
}
|
||||
let speedPerIntervalSample = HKQuantitySample(type: speedType,
|
||||
quantity: speedPerInterval,
|
||||
start: WorkoutTracking.lastDateMetric,
|
||||
end: Date())
|
||||
workoutBuilder.add([speedPerIntervalSample]) {(success, error) in
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
}
|
||||
|
||||
WorkoutTracking.lastDateMetric = Date()
|
||||
}
|
||||
|
||||
func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
|
||||
|
||||
BIN
qt-patches/android/5.15.0/jar/QtAndroidBluetooth.jar
Normal file
BIN
qt-patches/android/5.15.0/jar/QtAndroidBluetooth.jar
Normal file
Binary file not shown.
BIN
qt-patches/windows/5.15.2/binary/Qt5Bluetooth.dll
Normal file
BIN
qt-patches/windows/5.15.2/binary/Qt5Bluetooth.dll
Normal file
Binary file not shown.
1357
qt-patches/windows/5.15.2/qlowenergycontroller_win.cpp
Normal file
1357
qt-patches/windows/5.15.2/qlowenergycontroller_win.cpp
Normal file
File diff suppressed because it is too large
Load Diff
25
src/ChartFooter.qml
Normal file
25
src/ChartFooter.qml
Normal file
@@ -0,0 +1,25 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Loader {
|
||||
id: chartFooterLoader
|
||||
sourceComponent: ChartFooterInnerJS
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: CHARTJS ? "ChartFooterInnerJS.qml":"ChartFooterInnerNoJS.qml"
|
||||
onLoaded: {
|
||||
if(CHARTJS) {
|
||||
chartFooterLoader.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/ChartFooterInnerJS.qml
Normal file
31
src/ChartFooterInnerJS.qml
Normal file
@@ -0,0 +1,31 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
import QtWebView 1.1
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
WebView {
|
||||
id: webView
|
||||
anchors.fill: parent
|
||||
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chartlive.htm"
|
||||
visible: rootItem.chartFooterVisible
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.errorString) {
|
||||
console.error(loadRequest.errorString);
|
||||
console.error("port " + settings.value("template_inner_QZWS_port"));
|
||||
}
|
||||
}
|
||||
onVisibleChanged: {
|
||||
console.log("onVisibleChanged" + visible)
|
||||
if(visible === true) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/ChartFooterInnerNoJS.qml
Normal file
12
src/ChartFooterInnerNoJS.qml
Normal file
@@ -0,0 +1,12 @@
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Controls.Material 2.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Settings {
|
||||
id: settings
|
||||
}
|
||||
}
|
||||
100
src/Home.qml
100
src/Home.qml
@@ -291,59 +291,75 @@ HomeForm{
|
||||
}
|
||||
|
||||
footer:
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.top: gridView.bottom
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height / 2
|
||||
// Removed Timer, Play/Pause/Resume is now done via Homeform.cpp
|
||||
/*
|
||||
Timer {
|
||||
id: pauseTimer
|
||||
interval: 1000; running: true; repeat: true
|
||||
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
|
||||
videoPlaybackHalf.play() :
|
||||
videoPlaybackHalf.pause()) } }
|
||||
height: (rootItem.chartFooterVisible ? parent.height / 4 : parent.height / 2)
|
||||
anchors.top: gridView.bottom
|
||||
visible: rootItem.chartFooterVisible || rootItem.videoVisible
|
||||
|
||||
Rectangle {
|
||||
id: chartFooterRectangle
|
||||
visible: rootItem.chartFooterVisible
|
||||
anchors.fill: parent
|
||||
ChartFooter {
|
||||
anchors.fill: parent
|
||||
visible: rootItem.chartFooterVisible
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
//videoPlaybackHalf.playbackRate = rootItem.videoRate
|
||||
Rectangle {
|
||||
objectName: "footerrectangle"
|
||||
visible: rootItem.videoVisible
|
||||
anchors.fill: parent
|
||||
// Removed Timer, Play/Pause/Resume is now done via Homeform.cpp
|
||||
/*
|
||||
Timer {
|
||||
id: pauseTimer
|
||||
interval: 1000; running: true; repeat: true
|
||||
onTriggered: { if(visible == true) { (rootItem.currentSpeed > 0 ?
|
||||
videoPlaybackHalf.play() :
|
||||
videoPlaybackHalf.pause()) } }
|
||||
}
|
||||
*/
|
||||
|
||||
onVisibleChanged: {
|
||||
if(visible === true) {
|
||||
console.log("mediaPlayer onCompleted: " + rootItem.videoPath)
|
||||
console.log("videoRate: " + rootItem.videoRate)
|
||||
videoPlaybackHalf.source = rootItem.videoPath
|
||||
//videoPlaybackHalf.playbackRate = rootItem.videoRate
|
||||
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
|
||||
videoPlaybackHalf.seek(rootItem.videoPosition)
|
||||
videoPlaybackHalf.play()
|
||||
videoPlaybackHalf.muted = rootItem.currentCoordinateValid
|
||||
} else {
|
||||
videoPlaybackHalf.stop()
|
||||
}
|
||||
|
||||
}
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
MediaPlayer {
|
||||
id: videoPlaybackHalf
|
||||
objectName: "videoplaybackhalf"
|
||||
autoPlay: false
|
||||
playbackRate: rootItem.videoRate
|
||||
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
onError: {
|
||||
if (videoPlaybackHalf.NoError !== error) {
|
||||
console.log("[qmlvideo] VideoItem.onError error " + error + " errorString " + errorString)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id:videoPlayer
|
||||
anchors.fill: parent
|
||||
source: videoPlaybackHalf
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
property int currentId: -1 // Original position in model
|
||||
|
||||
@@ -93,7 +93,7 @@ Item {
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
/*Button {
|
||||
Button {
|
||||
id: restoreButton
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -101,7 +101,8 @@ Item {
|
||||
text: "Restore Purchases"
|
||||
onClicked: {
|
||||
console.log("restoring...");
|
||||
toast.show("Restoring...");
|
||||
iapStore.restorePurchases();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.13.98" android:versionCode="614" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.16.15" android:versionCode="644" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
@@ -79,6 +79,19 @@
|
||||
android:name=".ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
<service
|
||||
android:name=".WearableMessageListenerService"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
|
||||
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
|
||||
<data
|
||||
android:host="*"
|
||||
android:pathPrefix="/qz"
|
||||
android:scheme="wear" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".ChannelService"></service>
|
||||
<service android:name=".FloatingWindowGFG" android:enabled="true" android:exported="true"/>
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.6'
|
||||
androidTestImplementation "com.android.support:support-annotations:28.0.0"
|
||||
implementation 'com.google.android.gms:play-services-wearable:+'
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -35,11 +35,13 @@ public class Ant {
|
||||
static boolean speedRequest = false;
|
||||
static boolean heartRequest = false;
|
||||
static boolean garminKey = false;
|
||||
static boolean treadmill = false;
|
||||
|
||||
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey) {
|
||||
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill) {
|
||||
Log.v(TAG, "antStart");
|
||||
speedRequest = SpeedRequest;
|
||||
heartRequest = HeartRequest;
|
||||
treadmill = Treadmill;
|
||||
garminKey = GarminKey;
|
||||
|
||||
activity = a;
|
||||
|
||||
@@ -50,6 +50,7 @@ public class ChannelService extends Service {
|
||||
HeartChannelController heartChannelController = null;
|
||||
PowerChannelController powerChannelController = null;
|
||||
SpeedChannelController speedChannelController = null;
|
||||
SDMChannelController sdmChannelController = null;
|
||||
|
||||
private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
@@ -104,6 +105,9 @@ public class ChannelService extends Service {
|
||||
if (null != speedChannelController) {
|
||||
speedChannelController.speed = speed;
|
||||
}
|
||||
if (null != sdmChannelController) {
|
||||
sdmChannelController.speed = speed;
|
||||
}
|
||||
}
|
||||
|
||||
void setPower(int power) {
|
||||
@@ -119,6 +123,9 @@ public class ChannelService extends Service {
|
||||
if (null != speedChannelController) {
|
||||
speedChannelController.cadence = cadence;
|
||||
}
|
||||
if (null != sdmChannelController) {
|
||||
sdmChannelController.cadence = cadence;
|
||||
}
|
||||
}
|
||||
|
||||
int getHeart() {
|
||||
@@ -141,8 +148,12 @@ public class ChannelService extends Service {
|
||||
heartChannelController = new HeartChannelController(acquireChannel());
|
||||
|
||||
if (Ant.speedRequest) {
|
||||
powerChannelController = new PowerChannelController(acquireChannel());
|
||||
speedChannelController = new SpeedChannelController(acquireChannel());
|
||||
if(Ant.treadmill) {
|
||||
sdmChannelController = new SDMChannelController(acquireChannel());
|
||||
} else {
|
||||
powerChannelController = new PowerChannelController(acquireChannel());
|
||||
speedChannelController = new SpeedChannelController(acquireChannel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +164,12 @@ public class ChannelService extends Service {
|
||||
powerChannelController.close();
|
||||
if (speedChannelController != null)
|
||||
speedChannelController.close();
|
||||
if (sdmChannelController != null)
|
||||
sdmChannelController.close();
|
||||
heartChannelController = null;
|
||||
powerChannelController = null;
|
||||
speedChannelController = null;
|
||||
sdmChannelController = null;
|
||||
}
|
||||
|
||||
AntChannel acquireChannel() throws ChannelNotAvailableException {
|
||||
|
||||
308
src/android/src/SDMChannelController.java
Normal file
308
src/android/src/SDMChannelController.java
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2012 Dynastream Innovations Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.dsi.ant.channel.AntChannel;
|
||||
import com.dsi.ant.channel.AntCommandFailedException;
|
||||
import com.dsi.ant.channel.IAntChannelEventHandler;
|
||||
import com.dsi.ant.message.ChannelId;
|
||||
import com.dsi.ant.message.ChannelType;
|
||||
import com.dsi.ant.message.EventCode;
|
||||
import com.dsi.ant.message.fromant.ChannelEventMessage;
|
||||
import com.dsi.ant.message.fromant.MessageFromAntType;
|
||||
import com.dsi.ant.message.ipc.AntMessageParcel;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class SDMChannelController {
|
||||
// The device type and transmission type to be part of the channel ID message
|
||||
private static final int CHANNEL_SPEED_DEVICE_TYPE = 0x7C;
|
||||
private static final int CHANNEL_SPEED_TRANSMISSION_TYPE = 1;
|
||||
|
||||
// The period and frequency values the channel will be configured to
|
||||
private static final int CHANNEL_SPEED_PERIOD = 8134; // 1 Hz
|
||||
private static final int CHANNEL_SPEED_FREQUENCY = 57;
|
||||
|
||||
private static final String TAG = SDMChannelController.class.getSimpleName();
|
||||
public static final int SPEED_SENSOR_ID = 0x9e3d4b99;
|
||||
|
||||
private static final double MILLISECOND_TO_1_1024_CONVERSION = 0.9765625;
|
||||
|
||||
private AntChannel mAntChannel;
|
||||
|
||||
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
|
||||
|
||||
private boolean mIsOpen;
|
||||
double speed = 0.0;
|
||||
int cadence = 0;
|
||||
byte stride_count = 0;
|
||||
|
||||
public SDMChannelController(AntChannel antChannel) {
|
||||
mAntChannel = antChannel;
|
||||
openChannel();
|
||||
}
|
||||
|
||||
boolean openChannel() {
|
||||
if (null != mAntChannel) {
|
||||
if (mIsOpen) {
|
||||
Log.w(TAG, "Channel was already open");
|
||||
} else {
|
||||
// Channel ID message contains device number, type and transmission type. In
|
||||
// order for master (TX) channels and slave (RX) channels to connect, they
|
||||
// must have the same channel ID, or wildcard (0) is used.
|
||||
ChannelId channelId = new ChannelId(SPEED_SENSOR_ID & 0xFFFF,
|
||||
CHANNEL_SPEED_DEVICE_TYPE, CHANNEL_SPEED_TRANSMISSION_TYPE);
|
||||
|
||||
try {
|
||||
// Setting the channel event handler so that we can receive messages from ANT
|
||||
mAntChannel.setChannelEventHandler(mChannelEventCallback);
|
||||
|
||||
// Performs channel assignment by assigning the type to the channel. Additional
|
||||
// features (such as, background scanning and frequency agility) can be enabled
|
||||
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
|
||||
mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
|
||||
|
||||
/*
|
||||
* Configures the channel ID, messaging period and rf frequency after assigning,
|
||||
* then opening the channel.
|
||||
*
|
||||
* For any additional ANT features such as proximity search or background scanning, refer to
|
||||
* the ANT Protocol Doc found at:
|
||||
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
|
||||
*/
|
||||
mAntChannel.setChannelId(channelId);
|
||||
mAntChannel.setPeriod(CHANNEL_SPEED_PERIOD);
|
||||
mAntChannel.setRfFrequency(CHANNEL_SPEED_FREQUENCY);
|
||||
mAntChannel.open();
|
||||
mIsOpen = true;
|
||||
|
||||
Log.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
} catch (AntCommandFailedException e) {
|
||||
// This will release, and therefore unassign if required
|
||||
channelError("Open failed", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "No channel available");
|
||||
}
|
||||
|
||||
return mIsOpen;
|
||||
}
|
||||
|
||||
void channelError(RemoteException e) {
|
||||
String logString = "Remote service communication failed.";
|
||||
|
||||
Log.e(TAG, logString);
|
||||
}
|
||||
|
||||
void channelError(String error, AntCommandFailedException e) {
|
||||
StringBuilder logString;
|
||||
|
||||
if (e.getResponseMessage() != null) {
|
||||
String initiatingMessageId = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getInitiatingMessageId());
|
||||
String rawResponseCode = "0x" + Integer.toHexString(
|
||||
e.getResponseMessage().getRawResponseCode());
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(initiatingMessageId)
|
||||
.append(" failed with code ")
|
||||
.append(rawResponseCode);
|
||||
} else {
|
||||
String attemptedMessageId = "0x" + Integer.toHexString(
|
||||
e.getAttemptedMessageType().getMessageId());
|
||||
String failureReason = e.getFailureReason().toString();
|
||||
|
||||
logString = new StringBuilder(error)
|
||||
.append(". Command ")
|
||||
.append(attemptedMessageId)
|
||||
.append(" failed with reason ")
|
||||
.append(failureReason);
|
||||
}
|
||||
|
||||
Log.e(TAG, logString.toString());
|
||||
|
||||
mAntChannel.release();
|
||||
|
||||
Log.e(TAG, "ANT Command Failed");
|
||||
}
|
||||
|
||||
public void close() {
|
||||
// TODO kill all our resources
|
||||
if (null != mAntChannel) {
|
||||
mIsOpen = false;
|
||||
|
||||
// Releasing the channel to make it available for others.
|
||||
// After releasing, the AntChannel instance cannot be reused.
|
||||
mAntChannel.release();
|
||||
mAntChannel = null;
|
||||
}
|
||||
|
||||
Log.e(TAG, "Channel Closed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Channel Event Handler Interface so that messages can be
|
||||
* received and channel death events can be handled.
|
||||
*/
|
||||
public class ChannelEventCallback implements IAntChannelEventHandler {
|
||||
long lastTime = 0;
|
||||
double totalWay = 0.0;
|
||||
double totalRotations = 0.0;
|
||||
long lastSpeedEventTime = 0;
|
||||
long lastCadenceEventTime = 0;
|
||||
long elapsedMillis = 0;
|
||||
int rotations;
|
||||
int rev;
|
||||
double wheel = 0.1;
|
||||
Timer carousalTimer = null;
|
||||
|
||||
@Override
|
||||
public void onChannelDeath() {
|
||||
// Display channel death message when channel dies
|
||||
Log.e(TAG, "Channel Death");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
|
||||
Log.d(TAG, "Rx: " + antParcel);
|
||||
Log.d(TAG, "Message Type: " + messageType);
|
||||
|
||||
if(carousalTimer == null) {
|
||||
carousalTimer = new Timer(); // At this line a new Thread will be created
|
||||
carousalTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "Tx Unsollicited");
|
||||
long realtimeMillis = SystemClock.elapsedRealtime();
|
||||
double speedM_s = speed / 3.6;
|
||||
long deltaTime = (realtimeMillis - lastTime);
|
||||
lastTime = realtimeMillis;
|
||||
|
||||
byte[] payload = new byte[8];
|
||||
|
||||
payload[0] = (byte) 0x01;
|
||||
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
|
||||
payload[2] = (byte) ((lastTime % 256000) / 1000);
|
||||
payload[3] = (byte) 0x00;
|
||||
payload[4] = (byte) speedM_s;
|
||||
payload[5] = (byte) ((speedM_s - (double)((int)speedM_s)) / (1.0/256.0));
|
||||
payload[6] = (byte) stride_count++; // bad but it works on zwift
|
||||
payload[7] = (byte) ((double)deltaTime * 0.03125);
|
||||
|
||||
if (mIsOpen) {
|
||||
try {
|
||||
// Setting the data to be broadcast on the next channel period
|
||||
mAntChannel.setBroadcastData(payload);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 250); // delay
|
||||
}
|
||||
|
||||
// Switching on message type to handle different types of messages
|
||||
switch (messageType) {
|
||||
// If data message, construct from parcel and update channel data
|
||||
case BROADCAST_DATA:
|
||||
// Rx Data
|
||||
//updateData(new BroadcastDataMessage(antParcel).getPayload());
|
||||
break;
|
||||
case ACKNOWLEDGED_DATA:
|
||||
// Rx Data
|
||||
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
|
||||
break;
|
||||
case CHANNEL_EVENT:
|
||||
// Constructing channel event message from parcel
|
||||
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
|
||||
EventCode code = eventMessage.getEventCode();
|
||||
Log.d(TAG, "Event Code: " + code);
|
||||
|
||||
// Switching on event code to handle the different types of channel events
|
||||
switch (code) {
|
||||
case TX:
|
||||
long realtimeMillis = SystemClock.elapsedRealtime();
|
||||
double speedM_s = speed / 3.6;
|
||||
long deltaTime = (realtimeMillis - lastTime);
|
||||
// in case the treadmill doesn't provide cadence, I have to force it. ANT+ requires cadence
|
||||
lastTime = realtimeMillis;
|
||||
|
||||
byte[] payload = new byte[8];
|
||||
|
||||
payload[0] = (byte) 0x01;
|
||||
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
|
||||
payload[2] = (byte) ((lastTime % 256000) / 1000);
|
||||
payload[3] = (byte) 0x00;
|
||||
payload[4] = (byte) speedM_s;
|
||||
payload[5] = (byte) ((speedM_s - (double)((int)speedM_s)) / (1.0/256.0));
|
||||
payload[6] = (byte) stride_count;
|
||||
payload[7] = (byte) ((double)deltaTime * 0.03125);
|
||||
|
||||
if (mIsOpen) {
|
||||
try {
|
||||
// Setting the data to be broadcast on the next channel period
|
||||
mAntChannel.setBroadcastData(payload);
|
||||
} catch (RemoteException e) {
|
||||
channelError(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CHANNEL_COLLISION:
|
||||
break;
|
||||
case RX_SEARCH_TIMEOUT:
|
||||
// TODO May want to keep searching
|
||||
Log.e(TAG, "No Device Found");
|
||||
break;
|
||||
case CHANNEL_CLOSED:
|
||||
case RX_FAIL:
|
||||
case RX_FAIL_GO_TO_SEARCH:
|
||||
case TRANSFER_RX_FAILED:
|
||||
case TRANSFER_TX_COMPLETED:
|
||||
case TRANSFER_TX_FAILED:
|
||||
case TRANSFER_TX_START:
|
||||
case UNKNOWN:
|
||||
// TODO More complex communication will need to handle these events
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ANT_VERSION:
|
||||
case BURST_TRANSFER_DATA:
|
||||
case CAPABILITIES:
|
||||
case CHANNEL_ID:
|
||||
case CHANNEL_RESPONSE:
|
||||
case CHANNEL_STATUS:
|
||||
case SERIAL_NUMBER:
|
||||
case OTHER:
|
||||
// TODO More complex communication will need to handle these message types
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/android/src/WearableController.java
Normal file
42
src/android/src/WearableController.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class WearableController {
|
||||
static Context _context;
|
||||
static Intent _intent = null;
|
||||
|
||||
public static void start(Context context) {
|
||||
_context = context;
|
||||
|
||||
if(_intent == null)
|
||||
_intent = new Intent(context, WearableMessageListenerService.class);
|
||||
// FloatingWindowGFG service is started
|
||||
context.startService(_intent);
|
||||
Log.v("WearableController", "started");
|
||||
}
|
||||
|
||||
public static int getHeart() {
|
||||
return WearableMessageListenerService.getHeart();
|
||||
}
|
||||
}
|
||||
131
src/android/src/WearableMessageListenerService.java
Normal file
131
src/android/src/WearableMessageListenerService.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import com.google.android.gms.common.api.GoogleApiClient;
|
||||
import com.google.android.gms.common.api.PendingResult;
|
||||
import com.google.android.gms.common.api.ResultCallback;
|
||||
import com.google.android.gms.wearable.MessageClient;
|
||||
import com.google.android.gms.wearable.DataClient;
|
||||
import com.google.android.gms.wearable.DataEvent;
|
||||
import com.google.android.gms.wearable.DataEventBuffer;
|
||||
import com.google.android.gms.wearable.MessageEvent;
|
||||
import com.google.android.gms.wearable.Wearable;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.wearable.DataItemBuffer;
|
||||
import com.google.android.gms.wearable.DataMap;
|
||||
import android.util.Log;
|
||||
import android.os.Bundle;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class WearableMessageListenerService extends Service implements
|
||||
MessageClient.OnMessageReceivedListener, GoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener,DataClient.OnDataChangedListener {
|
||||
|
||||
private GoogleApiClient googleApiClient;
|
||||
private MessageClient mWearableClient;
|
||||
private String TAG = "WearableMessageListenerService";
|
||||
private static int heart_rate = 0;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.v("WearableMessageListenerService","onCreate");
|
||||
}
|
||||
|
||||
public static int getHeart() {
|
||||
return heart_rate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// Your service logic here
|
||||
|
||||
googleApiClient = new GoogleApiClient.Builder(this)
|
||||
.addApi(Wearable.API)
|
||||
.addConnectionCallbacks (this)
|
||||
.addOnConnectionFailedListener(this)
|
||||
.build();
|
||||
|
||||
googleApiClient.connect();
|
||||
|
||||
// Register the MessageClient.OnMessageReceivedListener
|
||||
mWearableClient = Wearable.getMessageClient(this);
|
||||
mWearableClient.addListener(this);
|
||||
Wearable.getDataClient(this).addListener(this);
|
||||
|
||||
Log.v("WearableMessageListenerService","onStartCommand");
|
||||
|
||||
// Return START_STICKY to restart the service if it's killed by the system
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged(DataEventBuffer dataEvents) {
|
||||
for (DataEvent event : dataEvents) {
|
||||
if (event.getType() == DataEvent.TYPE_DELETED) {
|
||||
Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
|
||||
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
|
||||
Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri() + " " + event.getDataItem().getUri().getPath());
|
||||
if(event.getDataItem().getUri().getPath().equals("/qz")) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DataItemBuffer result = Wearable.DataApi.getDataItems(googleApiClient).await();
|
||||
if (result.getStatus().isSuccess()) {
|
||||
if (result.getCount() == 1) {
|
||||
heart_rate = DataMap.fromByteArray(result.get(0).getData())
|
||||
.getInt("heart_rate", 0);
|
||||
} else {
|
||||
Log.e(TAG, "Unexpected number of DataItems found.\n"
|
||||
+ "\tExpected: 1\n"
|
||||
+ "\tActual: " + result.getCount());
|
||||
}
|
||||
} else if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "onHandleIntent: failed to get current alarm state");
|
||||
}
|
||||
Log.d(TAG, "Heart: " + heart_rate);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onConnected(Bundle bundle) {
|
||||
Log.v("WearableMessageListenerService","onConnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended(int i) {
|
||||
Log.v("WearableMessageListenerService","onConnectionSuspended");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed(ConnectionResult connectionResult) {
|
||||
Log.v("WearableMessageListenerService","onConnectionFailed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(final MessageEvent messageEvent) {
|
||||
String path = messageEvent.getPath();
|
||||
byte[] data = messageEvent.getData();
|
||||
|
||||
// Handle the received message data here
|
||||
String messageData = new String(data); // Assuming it's a simple string message
|
||||
|
||||
Log.v("Wearable", path);
|
||||
Log.v("Wearable", messageData);
|
||||
|
||||
// You can then perform actions or update data in your service based on the received message
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
// This service does not support binding
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -288,6 +288,8 @@ uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
|
||||
} else {
|
||||
watt = 0;
|
||||
}
|
||||
} else {
|
||||
watt = currentCadence().value() * 1.2; // random value cloned from Zwift when HR is not available
|
||||
}
|
||||
return watt;
|
||||
}
|
||||
|
||||
@@ -109,8 +109,9 @@ void bluetooth::finished() {
|
||||
QSettings settings;
|
||||
QString nordictrack_2950_ip =
|
||||
settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString();
|
||||
QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
|
||||
// wifi devices on windows
|
||||
if (!nordictrack_2950_ip.isEmpty()) {
|
||||
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty()) {
|
||||
// faking a bluetooth device
|
||||
qDebug() << "faking a bluetooth device for nordictrack_2950_ip";
|
||||
deviceDiscovered(QBluetoothDeviceInfo());
|
||||
@@ -422,6 +423,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
bool sole_inclination =
|
||||
settings.value(QZSettings::sole_treadmill_inclination, QZSettings::default_sole_treadmill_inclination).toBool();
|
||||
QString ftms_rower = settings.value(QZSettings::ftms_rower, QZSettings::default_ftms_rower).toString();
|
||||
QString ftms_bike = settings.value(QZSettings::ftms_bike, QZSettings::default_ftms_bike).toString();
|
||||
QString ftms_treadmill = settings.value(QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill).toString();
|
||||
|
||||
if (!heartRateBeltFound) {
|
||||
|
||||
@@ -711,7 +714,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(nordictrackifitadbTreadmill);
|
||||
} else if (!tdf_10_ip.isEmpty() && !nordictrackifitadbBike) {
|
||||
this->stopDiscovery();
|
||||
nordictrackifitadbBike = new nordictrackifitadbbike(noWriteResistance, noHeartService);
|
||||
nordictrackifitadbBike = new nordictrackifitadbbike(noWriteResistance, noHeartService,
|
||||
bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(nordictrackifitadbBike, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
@@ -1025,6 +1029,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("WALKINGPAD")) ||
|
||||
!b.name().toUpper().compare(QStringLiteral("RE")) || // just "RE"
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-H")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KS-BLC")) || // Walkingpad C2 #1672
|
||||
b.name().toUpper().startsWith(
|
||||
QStringLiteral("KS-BLR"))) && // Treadmill KingSmith WalkingPad R2 Pro KS-HCR1AA
|
||||
!kingsmithR1ProTreadmill &&
|
||||
@@ -1089,7 +1094,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive())
|
||||
emit searchingStop();
|
||||
this->signalBluetoothDeviceConnected(trueTreadmill);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("F80")) ||
|
||||
} else if (((b.name().toUpper().startsWith(QStringLiteral("F80")) && sole_inclination) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("F65")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TT8")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("F63")) ||
|
||||
@@ -1160,16 +1165,19 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("CT800")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("TRX4500")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("MATRIXTF50")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("T01_")) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) &&
|
||||
horizon_treadmill_force_ftms) || // FTMS, TF-769DF2
|
||||
((b.name().toUpper().startsWith(QStringLiteral("TOORX")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")))) &&
|
||||
!toorx_ftms && toorx_ftms_treadmill) ||
|
||||
!b.name().compare(ftms_treadmill, Qt::CaseInsensitive) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MOBVOI TM")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
|
||||
b.name().toUpper().startsWith(QStringLiteral("ESANGLINKER"))) &&
|
||||
!horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1314,10 +1322,12 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton)
|
||||
.toBool()) || // ss2k on a peloton bike
|
||||
(b.name().toUpper().startsWith("KICKR CORE")) ||
|
||||
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
|
||||
(b.name().toUpper().startsWith("B94")) || (b.name().toUpper().startsWith("STAGES BIKE")) ||
|
||||
(b.name().toUpper().startsWith("SUITO")) || (b.name().toUpper().startsWith("D2RIDE")) ||
|
||||
(b.name().toUpper().startsWith("DIRETO XR")) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
(b.name().toUpper().startsWith("DIRETO XR")) ||
|
||||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
!ftmsBike && !snodeBike && !fitPlusBike && !stagesBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1429,6 +1439,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(ftmsRower);
|
||||
} else if ((b.name().toUpper().startsWith(QLatin1String("ECH-STRIDE")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-UK-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-FR-")) ||
|
||||
b.name().toUpper().startsWith(QLatin1String("ECH-SD-SPT"))) &&
|
||||
!echelonStride && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1845,6 +1856,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
trxappgateusb->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(trxappgateusb);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("TUN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FITHIWAY")) ||
|
||||
((b.name().startsWith(QStringLiteral("TOORX")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
|
||||
@@ -1915,6 +1927,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
skandikaWiriBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(skandikaWiriBike);
|
||||
} else if (((b.name().toUpper().startsWith("RQ") && b.name().length() == 5) ||
|
||||
(b.name().toUpper().startsWith("R-Q") && b.name().length() > 6) ||
|
||||
(b.name().toUpper().startsWith("SCH130")) || // not a renpho bike an FTMS one
|
||||
((b.name().startsWith(QStringLiteral("TOORX"))) && toorx_ftms && !toorx_ftms_treadmill)) &&
|
||||
!renphoBike && !snodeBike && !fitPlusBike && filter) {
|
||||
@@ -2305,10 +2318,12 @@ void bluetooth::connectedAndDiscovered() {
|
||||
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
|
||||
"activity", "()Landroid/app/Activity;");
|
||||
KeepAwakeHelper::antObject(true)->callMethod<void>(
|
||||
"antStart", "(Landroid/app/Activity;ZZZ)V", activity.object<jobject>(),
|
||||
"antStart", "(Landroid/app/Activity;ZZZZ)V", activity.object<jobject>(),
|
||||
settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool(),
|
||||
settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool(),
|
||||
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool());
|
||||
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool(),
|
||||
device()->deviceType() == bluetoothdevice::TREADMILL ||
|
||||
device()->deviceType() == bluetoothdevice::ELLIPTICAL);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::android_notification, QZSettings::default_android_notification).toBool()) {
|
||||
|
||||
@@ -247,9 +247,15 @@ void bluetoothdevice::update_hr_from_external() {
|
||||
long appleWatchHeartRate = h.heartRate();
|
||||
h.setKcal(KCal.value());
|
||||
h.setDistance(Distance.value());
|
||||
h.setSpeed(Speed.value());
|
||||
h.setPower(m_watt.value());
|
||||
h.setCadence(Cadence.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
qDebug() << "Current Heart from Apple Watch: " << QString::number(appleWatchHeartRate);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
Heart = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/WearableController", "getHeart", "()I");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,15 @@ CharacteristicNotifier2A53::CharacteristicNotifier2A53(bluetoothdevice *Bike, QO
|
||||
|
||||
int CharacteristicNotifier2A53::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
value.append(0x02); // total distance
|
||||
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
|
||||
uint32_t distance = Bike->odometer() * 10000.0;
|
||||
value.append((char)((speed & 0xFF)));
|
||||
value.append((char)((speed >> 8) & 0xFF));
|
||||
value.append((char)(Bike->currentCadence().value()));
|
||||
value.append((char)((distance & 0xFF)));
|
||||
value.append((char)((distance >> 8) & 0xFF));
|
||||
value.append((char)((distance >> 16) & 0xFF));
|
||||
value.append((char)((distance >> 24) & 0xFF));
|
||||
return CN_OK;
|
||||
} else
|
||||
return CN_INVALID;
|
||||
value.append(0x02); // total distance
|
||||
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
|
||||
uint32_t distance = Bike->odometer() * 10000.0;
|
||||
value.append((char)((speed & 0xFF)));
|
||||
value.append((char)((speed >> 8) & 0xFF));
|
||||
value.append((char)(Bike->currentCadence().value()));
|
||||
value.append((char)((distance & 0xFF)));
|
||||
value.append((char)((distance >> 8) & 0xFF));
|
||||
value.append((char)((distance >> 16) & 0xFF));
|
||||
value.append((char)((distance >> 24) & 0xFF));
|
||||
return CN_OK;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "characteristicnotifier2ad2.h"
|
||||
#include "elliptical.h"
|
||||
#include "rower.h"
|
||||
#include "treadmill.h"
|
||||
#include <QSettings>
|
||||
|
||||
@@ -8,11 +9,17 @@ CharacteristicNotifier2AD2::CharacteristicNotifier2AD2(bluetoothdevice *Bike, QO
|
||||
|
||||
int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
|
||||
QSettings settings;
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
bool rowerAsABike = !virtual_device_rower && dt == bluetoothdevice::ROWING;
|
||||
|
||||
double normalizeWattage = Bike->wattsMetric().value();
|
||||
if (normalizeWattage < 0)
|
||||
normalizeWattage = 0;
|
||||
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
if (dt == bluetoothdevice::BIKE || rowerAsABike) {
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
|
||||
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
|
||||
value.append((char)0x02); // heart rate
|
||||
@@ -32,7 +39,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
value.append(char(Bike->currentHeart().value())); // Actual value.
|
||||
value.append((char)0); // Bkool FTMS protocol HRM offset 1280 fix
|
||||
return CN_OK;
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL || dt == bluetoothdevice::ROWING) {
|
||||
QSettings settings;
|
||||
bool double_cadence = settings.value(QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double).toBool();
|
||||
double cadence_multiplier = 2.0;
|
||||
@@ -50,6 +57,8 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
cadence = ((elliptical *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::TREADMILL)
|
||||
cadence = ((treadmill *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::ROWING)
|
||||
cadence = ((rower *)Bike)->currentCadence().value();
|
||||
|
||||
value.append((char)((uint16_t)(cadence * cadence_multiplier) & 0xFF)); // cadence
|
||||
value.append((char)(((uint16_t)(cadence * cadence_multiplier) >> 8) & 0xFF)); // cadence
|
||||
|
||||
@@ -391,17 +391,19 @@ void csaferower::update() {
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
if (!virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,7 @@ void domyosrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QSt
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -196,7 +195,8 @@ void domyosrower::update() {
|
||||
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
debug("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this);
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset,
|
||||
bikeResistanceGain);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this,
|
||||
&domyosrower::changeInclinationRequested);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &domyosrower::changeInclination);
|
||||
@@ -367,8 +367,9 @@ void domyosrower::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
double domyosrower::GetSpeedFromPacket(const QByteArray &packet) {
|
||||
|
||||
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
|
||||
double data = (double)convertedData / 10.0f;
|
||||
return data;
|
||||
if (convertedData > 65000 || convertedData == 0 || currentCadence().value() == 0)
|
||||
return 0;
|
||||
return (60.0 / (double)(convertedData)) * 30.0;
|
||||
}
|
||||
|
||||
double domyosrower::GetKcalFromPacket(const QByteArray &packet) {
|
||||
|
||||
@@ -404,6 +404,8 @@ void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
@@ -418,12 +420,19 @@ void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
if (virtual_device_rower) {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "bike.h"
|
||||
#include "virtualbike.h"
|
||||
#include "virtualrower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
|
||||
@@ -67,6 +67,7 @@ double elliptical::speedFromWatts() {
|
||||
if (wattsMetric().value() > 0) {
|
||||
double vwatts = ((9.8 * weight) * (currentInclination().value() / 100.0));
|
||||
speed = 210.0 / ((wattsMetric().value() - vwatts) / 75.0 / weight * 1000.0);
|
||||
speed = 60.0 / speed;
|
||||
}
|
||||
return speed;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class elliptical : public bluetoothdevice {
|
||||
void clearStats() override;
|
||||
void setPaused(bool p) override;
|
||||
void setLap() override;
|
||||
uint16_t watts();
|
||||
virtual uint16_t watts();
|
||||
double speedFromWatts();
|
||||
void setGears(double d);
|
||||
double gears();
|
||||
|
||||
@@ -390,7 +390,10 @@ void eslinkertreadmill::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
}
|
||||
} else if (treadmill_type == COSTAWAY) {
|
||||
const double miles = 1.60934;
|
||||
Speed = newValue.at(3) * miles;
|
||||
if(newValue.at(3) == 0xFF)
|
||||
Speed = 0;
|
||||
else
|
||||
Speed = (double)((uint8_t)newValue.at(3)) / 10.0 * miles;
|
||||
Inclination = 0; // this treadmill doesn't have inclination
|
||||
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
|
||||
@@ -276,7 +276,8 @@ void fitshowtreadmill::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
QBluetoothUuid nobleproconnect(QStringLiteral("0000ae00-0000-1000-8000-00805f9b34fb"));
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString() + QStringLiteral(" ") +
|
||||
QString::number(servRepr));
|
||||
if (gatt == nobleproconnect || servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
|
||||
if ((gatt == nobleproconnect && serviceId.isNull()) || servRepr == 0xfff0 || (servRepr == 0xffe0 && serviceId.isNull())) {
|
||||
qDebug() << "adding" << gatt.toString() << "as the default service";
|
||||
serviceId = gatt; // NOTE: clazy-rule-of-tow
|
||||
}
|
||||
}
|
||||
@@ -470,7 +471,11 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
.toBool())
|
||||
miles = 1.60934;
|
||||
|
||||
Speed = speed * miles;
|
||||
if(IS_RUNNING)
|
||||
Speed = speed * miles;
|
||||
else
|
||||
Speed = 0;
|
||||
|
||||
if (Speed.value() != speed) {
|
||||
emit speedChanged(speed);
|
||||
}
|
||||
@@ -499,6 +504,9 @@ void fitshowtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
long appleWatchHeartRate = h->heartRate();
|
||||
h->setKcal(KCal.value());
|
||||
h->setDistance(Distance.value());
|
||||
h->setSpeed(Speed.value());
|
||||
h->setPower(m_watt.value());
|
||||
h->setCadence(Cadence.value());
|
||||
Heart = appleWatchHeartRate;
|
||||
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
|
||||
#else
|
||||
|
||||
@@ -52,11 +52,15 @@ void ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -89,7 +93,8 @@ void ftmsbike::forcePower(int16_t requestPower) {
|
||||
void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
|
||||
resistance_lvl_mode == false) {
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
|
||||
@@ -272,16 +277,20 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
}
|
||||
|
||||
if (Flags.totDistance) {
|
||||
|
||||
/*
|
||||
* the distance sent from the most trainers is a total distance, so it's useless for QZ
|
||||
*
|
||||
Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
|
||||
(uint32_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint32_t)((uint8_t)newValue.at(index)))) /
|
||||
1000.0;
|
||||
1000.0;*/
|
||||
index += 3;
|
||||
} else {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
}
|
||||
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
|
||||
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
|
||||
|
||||
if (Flags.resistanceLvl) {
|
||||
@@ -290,7 +299,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
emit resistanceRead(Resistance.value());
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
} else {
|
||||
resistance_received = true;
|
||||
}
|
||||
double ac = 0.01243107769;
|
||||
double bc = 1.145964912;
|
||||
double cc = -23.50977444;
|
||||
@@ -308,10 +318,13 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
(2.0 * ar)) *
|
||||
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
if (!resistance_received) {
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Flags.instantPower) {
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
@@ -361,7 +374,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(index))));
|
||||
Heart = ((double)(((uint8_t)newValue.at(index))));
|
||||
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
} else {
|
||||
@@ -551,7 +564,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
#endif
|
||||
{
|
||||
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
|
||||
Heart = ((double)((newValue.at(index))));
|
||||
Heart = ((double)(((uint8_t)newValue.at(index))));
|
||||
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
} else {
|
||||
@@ -847,6 +860,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
if (bluetoothDevice.name().toUpper().startsWith("SUITO")) {
|
||||
qDebug() << QStringLiteral("SUITO found");
|
||||
max_resistance = 16;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("MAGNUS "))) {
|
||||
qDebug() << QStringLiteral("MAGNUS found");
|
||||
resistance_lvl_mode = true;
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
|
||||
@@ -104,6 +104,9 @@ class ftmsbike : public bike {
|
||||
|
||||
bool powerForced = false;
|
||||
|
||||
bool resistance_lvl_mode = false;
|
||||
bool resistance_received = false;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
@@ -54,8 +54,7 @@ void ftmsrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
|
||||
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer);
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
@@ -466,6 +465,8 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
@@ -481,16 +482,25 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
if (virtual_device_enabled) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
if (!virtual_device_rower) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&ftmsrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&ftmsrower::debug);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else {
|
||||
qDebug() << QStringLiteral("creating virtual rower interface...");
|
||||
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
|
||||
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
|
||||
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
|
||||
void ftmsrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "rower.h"
|
||||
#include "virtualbike.h"
|
||||
#include "virtualrower.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
|
||||
130
src/homeform.cpp
130
src/homeform.cpp
@@ -47,17 +47,6 @@ using namespace std::chrono_literals;
|
||||
#include <QtAndroid>
|
||||
#endif
|
||||
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#define STRAVA_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef STRAVA_CLIENT_ID
|
||||
#define STRAVA_CLIENT_ID 7976
|
||||
#if defined(WIN32)
|
||||
@@ -760,7 +749,7 @@ void homeform::peloton_start_workout() {
|
||||
if (!stravaPelotonActivityName.isEmpty() && !stravaPelotonInstructorName.isEmpty()) {
|
||||
QString path = getWritableAppDir() + "training/" + workoutNameBasedOnBluetoothDevice() + "/" +
|
||||
stravaPelotonInstructorName + "/";
|
||||
QDir().mkdir(path);
|
||||
QDir().mkpath(path);
|
||||
lastTrainProgramFileSaved =
|
||||
path + stravaPelotonActivityName.replace("/", "-") + " - " + stravaPelotonInstructorName + ".xml";
|
||||
trainProgram->save(lastTrainProgramFileSaved);
|
||||
@@ -1038,6 +1027,19 @@ void homeform::trainProgramSignals() {
|
||||
connect(this, &homeform::workoutEventStateChanged, bluetoothManager->device(),
|
||||
&bluetoothdevice::workoutEventStateChanged);
|
||||
|
||||
if (trainProgram) {
|
||||
setChartIconVisible(trainProgram->powerzoneWorkout());
|
||||
if (chartFooterVisible()) {
|
||||
if (trainProgram->powerzoneWorkout()) {
|
||||
// reloading
|
||||
setChartFooterVisible(false);
|
||||
setChartFooterVisible(true);
|
||||
} else {
|
||||
setChartFooterVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("trainProgram associated to a device");
|
||||
} else {
|
||||
qDebug() << QStringLiteral("trainProgram NOT associated to a device");
|
||||
@@ -2242,6 +2244,12 @@ void homeform::sortTiles() {
|
||||
target_pace->setGridId(i);
|
||||
dataList.append(target_pace);
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::tile_pace_enabled, true).toBool() &&
|
||||
settings.value(QZSettings::tile_pace_order, 51).toInt() == i) {
|
||||
pace->setGridId(i);
|
||||
dataList.append(pace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2347,6 +2355,13 @@ void homeform::deviceConnected(QBluetoothDeviceInfo b) {
|
||||
if (settings.value(QZSettings::floating_startup, QZSettings::default_floating_startup).toBool()) {
|
||||
floatingOpen();
|
||||
}
|
||||
|
||||
if(!settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString().compare(QZSettings::default_heart_rate_belt_name) &&
|
||||
!settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
|
||||
QAndroidJniObject::callStaticMethod<void>(
|
||||
"org/cagnulen/qdomyoszwift/WearableController", "start", "(Landroid/content/Context;)V",
|
||||
QtAndroid::androidContext().object());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool()) {
|
||||
@@ -3202,6 +3217,9 @@ void homeform::StopRequested() {
|
||||
|
||||
void homeform::Stop() {
|
||||
QSettings settings;
|
||||
|
||||
m_startRequested = false;
|
||||
|
||||
qDebug() << QStringLiteral("Stop pressed - paused") << paused << QStringLiteral("stopped") << stopped;
|
||||
|
||||
if (stopped) {
|
||||
@@ -3460,7 +3478,7 @@ void homeform::update() {
|
||||
else if (next.speed != -1)
|
||||
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.inclination != -1)
|
||||
else if (next.inclination != -200)
|
||||
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination) + QStringLiteral(" ") +
|
||||
next.duration.toString(QStringLiteral("mm:ss")));
|
||||
else if (next.power != -1) {
|
||||
@@ -3533,7 +3551,22 @@ void homeform::update() {
|
||||
wattKg->setSecondLine(
|
||||
QStringLiteral("AVG: ") + QString::number(bluetoothManager->device()->wattKg().average(), 'f', 1) +
|
||||
QStringLiteral("MAX: ") + QString::number(bluetoothManager->device()->wattKg().max(), 'f', 1));
|
||||
datetime->setValue(QTime::currentTime().toString(QStringLiteral("hh:mm:ss")));
|
||||
QLocale locale = QLocale::system();
|
||||
|
||||
// Format the time based on the locale
|
||||
QString timeFormat = locale.timeFormat(QLocale::ShortFormat);
|
||||
bool usesAMPMFormat = timeFormat.toUpper().contains("A");
|
||||
QDateTime currentTime = QDateTime::currentDateTime();
|
||||
|
||||
QString formattedTime;
|
||||
if (usesAMPMFormat) {
|
||||
// The locale uses 12-hour format with AM/PM
|
||||
formattedTime = currentTime.toString("h:mm:ss AP");
|
||||
} else {
|
||||
// The locale uses 24-hour format
|
||||
formattedTime = currentTime.toString("H:mm:ss");
|
||||
}
|
||||
datetime->setValue(formattedTime);
|
||||
if (power5s)
|
||||
watts = bluetoothManager->device()->wattsMetric().average5s();
|
||||
else
|
||||
@@ -3809,17 +3842,23 @@ void homeform::update() {
|
||||
}
|
||||
switch (trainProgram->currentRow().pace_intensity) {
|
||||
case 0:
|
||||
this->target_zone->setValue(tr("Easy"));
|
||||
this->target_zone->setValue(tr("Rec."));
|
||||
break;
|
||||
case 1:
|
||||
this->target_zone->setValue(tr("Moder."));
|
||||
this->target_zone->setValue(tr("Easy"));
|
||||
break;
|
||||
case 2:
|
||||
this->target_zone->setValue(tr("Chall."));
|
||||
this->target_zone->setValue(tr("Moder."));
|
||||
break;
|
||||
case 3:
|
||||
this->target_zone->setValue(tr("Chall."));
|
||||
break;
|
||||
case 4:
|
||||
this->target_zone->setValue(tr("Max"));
|
||||
break;
|
||||
default:
|
||||
this->target_zone->setValue(tr("N/A"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer() * 1000.0, 'f', 0));
|
||||
@@ -3901,6 +3940,16 @@ void homeform::update() {
|
||||
}
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
|
||||
if (((elliptical *)bluetoothManager->device())->currentSpeed().value() > 2)
|
||||
this->pace->setValue(
|
||||
((elliptical *)bluetoothManager->device())->currentPace().toString(QStringLiteral("m:ss")));
|
||||
else
|
||||
this->pace->setValue("N/A");
|
||||
this->pace->setSecondLine(
|
||||
QStringLiteral("AVG: ") +
|
||||
((elliptical *)bluetoothManager->device())->averagePace().toString(QStringLiteral("m:ss")) +
|
||||
QStringLiteral(" MAX: ") +
|
||||
((elliptical *)bluetoothManager->device())->maxPace().toString(QStringLiteral("m:ss")));
|
||||
odometer->setValue(QString::number(bluetoothManager->device()->odometer() * unit_conversion, 'f', 2));
|
||||
resistance = ((elliptical *)bluetoothManager->device())->currentResistance().value();
|
||||
peloton_resistance = ((elliptical *)bluetoothManager->device())->pelotonResistance().value();
|
||||
@@ -3953,6 +4002,18 @@ void homeform::update() {
|
||||
if (trainProgram) {
|
||||
int8_t lower_requested_peloton_resistance = trainProgram->currentRow().lower_requested_peloton_resistance;
|
||||
int8_t upper_requested_peloton_resistance = trainProgram->currentRow().upper_requested_peloton_resistance;
|
||||
double lower_requested_peloton_resistance_to_bike_resistance = 0;
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((bike *)bluetoothManager->device())->pelotonToBikeResistance(lower_requested_peloton_resistance);
|
||||
else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((rower *)bluetoothManager->device())->pelotonToBikeResistance(lower_requested_peloton_resistance);
|
||||
else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL)
|
||||
lower_requested_peloton_resistance_to_bike_resistance =
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->pelotonToEllipticalResistance(lower_requested_peloton_resistance);
|
||||
|
||||
if (lower_requested_peloton_resistance != -1) {
|
||||
this->target_peloton_resistance->setSecondLine(
|
||||
QStringLiteral("MIN: ") + QString::number(lower_requested_peloton_resistance, 'f', 0) +
|
||||
@@ -3967,7 +4028,10 @@ void homeform::update() {
|
||||
.toBool()) {
|
||||
if (lower_requested_peloton_resistance == -1) {
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("white"));
|
||||
} else if (((int8_t)qRound(peloton_resistance)) < lower_requested_peloton_resistance) {
|
||||
} else if (resistance < lower_requested_peloton_resistance_to_bike_resistance) {
|
||||
// we need to compare the real resistance and not the peloton resistance because most of the bikes
|
||||
// have a 1:3 conversion so this compare will be always true even if the actual resistance is the
|
||||
// same #1608
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("red"));
|
||||
} else if (((int8_t)qRound(peloton_resistance)) <= upper_requested_peloton_resistance) {
|
||||
this->peloton_resistance->setValueFontColor(QStringLiteral("limegreen"));
|
||||
@@ -4322,9 +4386,11 @@ void homeform::update() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool() &&
|
||||
KeepAwakeHelper::antObject(false)) {
|
||||
KeepAwakeHelper::antObject(false)->callMethod<void>(
|
||||
"setCadenceSpeedPower", "(FII)V", (float)bluetoothManager->device()->currentSpeed().value(), (int)watts,
|
||||
(int)cadence);
|
||||
double v = bluetoothManager->device()->currentSpeed().value();
|
||||
v *= settings.value(QZSettings::ant_speed_gain, QZSettings::default_ant_speed_gain).toDouble();
|
||||
v += settings.value(QZSettings::ant_speed_offset, QZSettings::default_ant_speed_offset).toDouble();
|
||||
KeepAwakeHelper::antObject(false)->callMethod<void>("setCadenceSpeedPower", "(FII)V", (float)v, (int)watts,
|
||||
(int)cadence);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5424,6 +5490,10 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
|
||||
activityNamePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant(QStringLiteral("form-data; name=\"name\"")));
|
||||
|
||||
QString prefix = QStringLiteral("");
|
||||
if(settings.value(QZSettings::strava_date_prefix, QZSettings::default_strava_date_prefix).toBool())
|
||||
prefix = " " + QDate::currentDate().toString(Qt::TextDate);
|
||||
|
||||
// use metadata config if the user selected it
|
||||
QString activityName =
|
||||
QStringLiteral(" ") + settings.value(QZSettings::strava_suffix, QZSettings::default_strava_suffix).toString();
|
||||
@@ -5436,11 +5506,11 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
|
||||
pelotonHandler->current_ride_id;
|
||||
} else {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
|
||||
activityName = QStringLiteral("Run") + activityName;
|
||||
activityName = prefix + QStringLiteral("Run") + activityName;
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
|
||||
activityName = QStringLiteral("Row") + activityName;
|
||||
activityName = prefix + QStringLiteral("Row") + activityName;
|
||||
} else {
|
||||
activityName = QStringLiteral("Ride") + activityName;
|
||||
activityName = prefix + QStringLiteral("Ride") + activityName;
|
||||
}
|
||||
}
|
||||
activityNamePart.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
@@ -5739,6 +5809,14 @@ void homeform::setVideoIconVisible(bool value) {
|
||||
emit videoIconVisibleChanged(m_VideoIconVisible);
|
||||
}
|
||||
|
||||
bool homeform::chartIconVisible() { return m_ChartIconVisible; }
|
||||
|
||||
void homeform::setChartIconVisible(bool value) {
|
||||
|
||||
m_ChartIconVisible = value;
|
||||
emit chartIconVisibleChanged(m_ChartIconVisible);
|
||||
}
|
||||
|
||||
int homeform::videoPosition() { return m_VideoPosition; }
|
||||
|
||||
void homeform::setVideoPosition(int value) {
|
||||
@@ -5959,6 +6037,10 @@ void homeform::sendMail() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SMTP_SERVER
|
||||
textMessage += QStringLiteral("\n\nSMTP server: ") + QString(STRINGIFY(SMTP_SERVER));
|
||||
#endif
|
||||
|
||||
text.setText(textMessage);
|
||||
message.addPart(&text);
|
||||
|
||||
|
||||
@@ -24,6 +24,18 @@
|
||||
#include "qmdnsengine/cache.h"
|
||||
#include "qmdnsengine/resolver.h"
|
||||
|
||||
#if __has_include("secret.h")
|
||||
#include "secret.h"
|
||||
#else
|
||||
#define STRAVA_SECRET_KEY test
|
||||
#if defined(WIN32)
|
||||
#pragma message("DEFINE STRAVA_SECRET_KEY!!!")
|
||||
#else
|
||||
#warning "DEFINE STRAVA_SECRET_KEY!!!"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
class DataObject : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
@@ -137,6 +149,8 @@ class homeform : public QObject {
|
||||
Q_PROPERTY(bool mapsVisible READ mapsVisible NOTIFY mapsVisibleChanged WRITE setMapsVisible)
|
||||
Q_PROPERTY(bool videoIconVisible READ videoIconVisible NOTIFY videoIconVisibleChanged WRITE setVideoIconVisible)
|
||||
Q_PROPERTY(bool videoVisible READ videoVisible NOTIFY videoVisibleChanged WRITE setVideoVisible)
|
||||
Q_PROPERTY(bool chartIconVisible READ chartIconVisible NOTIFY chartIconVisibleChanged WRITE setChartIconVisible)
|
||||
Q_PROPERTY(bool chartFooterVisible READ chartFooterVisible NOTIFY chartFooterVisibleChanged WRITE setChartFooterVisible)
|
||||
Q_PROPERTY(QUrl videoPath READ videoPath NOTIFY videoPathChanged)
|
||||
Q_PROPERTY(int videoPosition READ videoPosition NOTIFY videoPositionChanged WRITE setVideoPosition)
|
||||
Q_PROPERTY(double videoRate READ videoRate NOTIFY videoRateChanged WRITE setVideoRate)
|
||||
@@ -381,9 +395,11 @@ class homeform : public QObject {
|
||||
void setPelotonProvider(const QString &value) { m_pelotonProvider = value; }
|
||||
bool generalPopupVisible();
|
||||
bool licensePopupVisible();
|
||||
bool mapsVisible();
|
||||
bool mapsVisible();
|
||||
bool videoIconVisible();
|
||||
bool videoVisible() { return m_VideoVisible; }
|
||||
bool chartIconVisible();
|
||||
bool chartFooterVisible() { return m_ChartFooterVisible; }
|
||||
int videoPosition();
|
||||
double videoRate();
|
||||
double currentSpeed() {
|
||||
@@ -415,10 +431,15 @@ class homeform : public QObject {
|
||||
}
|
||||
void setLicensePopupVisible(bool value);
|
||||
void setVideoIconVisible(bool value);
|
||||
void setChartIconVisible(bool value);
|
||||
void setVideoVisible(bool value) {
|
||||
m_VideoVisible = value;
|
||||
emit videoVisibleChanged(m_VideoVisible);
|
||||
}
|
||||
void setChartFooterVisible(bool value) {
|
||||
m_ChartFooterVisible = value;
|
||||
emit chartFooterVisibleChanged(m_ChartFooterVisible);
|
||||
}
|
||||
void setVideoPosition(int position); // on startup
|
||||
void videoSeekPosition(int ms); // in realtime
|
||||
void setVideoRate(double rate);
|
||||
@@ -559,6 +580,8 @@ class homeform : public QObject {
|
||||
bool m_MapsVisible = false;
|
||||
bool m_VideoIconVisible = false;
|
||||
bool m_VideoVisible = false;
|
||||
bool m_ChartFooterVisible = false;
|
||||
bool m_ChartIconVisible = false;
|
||||
int m_VideoPosition = 0;
|
||||
double m_VideoRate = 1;
|
||||
QOAuth2AuthorizationCodeFlow *strava = nullptr;
|
||||
@@ -804,6 +827,8 @@ class homeform : public QObject {
|
||||
void videoPositionChanged(int value);
|
||||
void videoPathChanged(QUrl value);
|
||||
void videoRateChanged(double value);
|
||||
void chartIconVisibleChanged(bool value);
|
||||
void chartFooterVisibleChanged(bool value);
|
||||
void currentSpeedChanged(double value);
|
||||
void mapsVisibleChanged(bool value);
|
||||
void autoResistanceChanged(bool value);
|
||||
|
||||
@@ -845,7 +845,8 @@ void horizontreadmill::update() {
|
||||
}
|
||||
|
||||
if (requestSpeed != -1) {
|
||||
bool minSpeed = fabs(requestSpeed - float_one_point_round(currentSpeed().value())) >= minStepSpeed();
|
||||
bool minSpeed =
|
||||
fabs(requestSpeed - float_one_point_round(currentSpeed().value())) >= (minStepSpeed() - 0.09);
|
||||
bool forceSpeedNeed = checkIfForceSpeedNeeding(requestSpeed);
|
||||
qDebug() << "requestSpeed=" << requestSpeed << minSpeed << forceSpeedNeed
|
||||
<< float_one_point_round(currentSpeed().value());
|
||||
|
||||
43
src/inner_templates/chartjs/chartlive.htm
Normal file
43
src/inner_templates/chartjs/chartlive.htm
Normal file
@@ -0,0 +1,43 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Line Chart</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">
|
||||
<script src="resize-observer.min.js"></script>
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script src="chartjs.3.4.1.min.js"></script>
|
||||
<script src="moment.js"></script>
|
||||
<script src="chartjs-adapter-moment.js"></script>
|
||||
<script src="chartjs-plugin-annotation.min.js"></script>
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<script src="dochartlive.js"></script>
|
||||
<script src="html2canvas.min.js"></script>
|
||||
<style>
|
||||
canvas{
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
html, body
|
||||
{
|
||||
-ms-content-zooming:none;
|
||||
touch-action: none;
|
||||
content-zooming: none;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
overflow-y: none;
|
||||
overflow-x: none;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#1d2330">
|
||||
<div id="divcanvas" style="width:100vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
530
src/inner_templates/chartjs/dochartlive.js
Normal file
530
src/inner_templates/chartjs/dochartlive.js
Normal file
@@ -0,0 +1,530 @@
|
||||
window.chartColors = {
|
||||
red: 'rgb(255, 29, 0)',
|
||||
redt: 'rgb(255, 29, 0, 0.55)',
|
||||
orange: 'rgb(255, 159, 64)',
|
||||
oranget: 'rgb(255, 159, 64, 0.55)',
|
||||
darkorange: 'rgb(255, 140, 0)',
|
||||
darkoranget: 'rgb(255, 140, 0, 0.55)',
|
||||
orangered: 'rgb(255, 69, 0)',
|
||||
orangeredt: 'rgb(255, 69, 0, 0.55)',
|
||||
yellow: 'rgb(255, 205, 86)',
|
||||
yellowt: 'rgb(255, 205, 86, 0.55)',
|
||||
green: 'rgb(75, 192, 192)',
|
||||
greent: 'rgb(75, 192, 192, 0.55)',
|
||||
blue: 'rgb(54, 162, 235)',
|
||||
purple: 'rgb(153, 102, 255)',
|
||||
grey: 'rgb(201, 203, 207)',
|
||||
greyt: 'rgb(201, 203, 207, 0.55)',
|
||||
white: 'rgb(255, 255, 255)',
|
||||
whitet: 'rgb(255, 255, 255, 0.55)',
|
||||
limegreen: 'rgb(50, 205, 50)',
|
||||
limegreent: 'rgb(50, 205, 50, 0.55)',
|
||||
gold: 'rgb(255, 215, 0)',
|
||||
goldt: 'rgb(255, 215, 0, 0.55)',
|
||||
black: 'rgb(0, 0, 0)',
|
||||
blackt: 'rgb(0, 0, 0, 0.55)',
|
||||
lightsteelblue: 'rgb(176,192,222)',
|
||||
lightsteelbluet: 'rgb(176,192,222, 0.55)',
|
||||
};
|
||||
|
||||
var ftp = 200;
|
||||
var ftpZones = [];
|
||||
var maxHeartRate = 190;
|
||||
var heartZones = [];
|
||||
var miles = 1;
|
||||
var powerChart = null;
|
||||
|
||||
function process_trainprogram(arr) {
|
||||
let powerWorkout = false;
|
||||
let elapsed = 0;
|
||||
|
||||
for (let el of arr.list) {
|
||||
if(el.power != -1) {
|
||||
powerWorkout = true;
|
||||
for (i=0; i<el.duration_s; i++) {
|
||||
powerChart.data.datasets[1].data.push({x: elapsed++, y: el.power});
|
||||
}
|
||||
}
|
||||
}
|
||||
powerChart.options.scales.x.max = elapsed;
|
||||
powerChart.update();
|
||||
}
|
||||
|
||||
function process_arr(arr) {
|
||||
let ctx = document.getElementById('canvas').getContext('2d');
|
||||
let div = document.getElementById('divcanvas');
|
||||
|
||||
let reqpower = [];
|
||||
let reqcadence = [];
|
||||
let heart = [];
|
||||
let cadence = [];
|
||||
let speed = [];
|
||||
let inclination = [];
|
||||
let resistance = [];
|
||||
let watts = [];
|
||||
let reqresistance = [];
|
||||
let pelotonresistance = [];
|
||||
let pelotonreqresistance = [];
|
||||
let distributionPowerZones = [];
|
||||
let maxEl = 0;
|
||||
let saveScreenshot = [];
|
||||
let workoutName = '';
|
||||
let workoutStartDate = '';
|
||||
let instructorName = '';
|
||||
let watts_avg = 0;
|
||||
let watts_max = 0;
|
||||
let heart_avg = 0;
|
||||
let heart_max = 0;
|
||||
let jouls = 0;
|
||||
let deviceType = 0;
|
||||
let cadence_avg = 0;
|
||||
let peloton_resistance_avg = 0;
|
||||
let calories = 0;
|
||||
let distance = 0;
|
||||
saveScreenshot[0] = false;
|
||||
saveScreenshot[1] = false;
|
||||
saveScreenshot[2] = false;
|
||||
saveScreenshot[3] = false;
|
||||
saveScreenshot[4] = false;
|
||||
saveScreenshot[5] = false;
|
||||
saveScreenshot[6] = false;
|
||||
saveScreenshot[7] = false;
|
||||
distributionPowerZones[0] = 0;
|
||||
distributionPowerZones[1] = 0;
|
||||
distributionPowerZones[2] = 0;
|
||||
distributionPowerZones[3] = 0;
|
||||
distributionPowerZones[4] = 0;
|
||||
distributionPowerZones[5] = 0;
|
||||
distributionPowerZones[6] = 0;
|
||||
|
||||
for (let el of arr) {
|
||||
let wattel = {};
|
||||
let reqpowerel = {};
|
||||
let reqcadenceel = {};
|
||||
let heartel = {};
|
||||
let cadenceel = {};
|
||||
let resistanceel = {};
|
||||
let reqresistanceel = {};
|
||||
let pelotonresistanceel = {};
|
||||
let pelotonreqresistanceel = {};
|
||||
let speedel = {};
|
||||
let inclinationel = {};
|
||||
let time = el.elapsed_s + el.elapsed_m * 60 + el.elapsed_h * 3600;
|
||||
workoutName = el.workoutName;
|
||||
workoutStartDate = el.workoutStartDate;
|
||||
instructorName = el.instructorName;
|
||||
watts_avg = el.watts_avg;
|
||||
watts_max = el.watts_max;
|
||||
heart_avg = el.heart_avg;
|
||||
heart_max = el.heart_max;
|
||||
jouls = el.jouls;
|
||||
deviceType = el.deviceType;
|
||||
peloton_resistance_avg = el.peloton_resistance_avg;
|
||||
cadence_avg = el.cadence_avg;
|
||||
distance = el.distance;
|
||||
calories = el.calories;
|
||||
maxEl = time;
|
||||
wattel.x = time;
|
||||
wattel.y = el.watts;
|
||||
watts.push(wattel);
|
||||
if(el.watts < ftpZones[0])
|
||||
distributionPowerZones[0]++;
|
||||
else if(el.watts < ftpZones[1])
|
||||
distributionPowerZones[1]++;
|
||||
else if(el.watts < ftpZones[2])
|
||||
distributionPowerZones[2]++;
|
||||
else if(el.watts < ftpZones[3])
|
||||
distributionPowerZones[3]++;
|
||||
else if(el.watts < ftpZones[4])
|
||||
distributionPowerZones[4]++;
|
||||
else if(el.watts < ftpZones[5])
|
||||
distributionPowerZones[5]++;
|
||||
else
|
||||
distributionPowerZones[6]++;
|
||||
reqpowerel.x = time;
|
||||
reqpowerel.y = el.req_power;
|
||||
// they are added from the process_trainprogram()
|
||||
//reqpower.push(reqpowerel);
|
||||
|
||||
reqcadenceel.x = time;
|
||||
reqcadenceel.y = el.req_cadence;
|
||||
reqcadence.push(reqcadenceel);
|
||||
heartel.x = time;
|
||||
heartel.y = el.heart;
|
||||
heart.push(heartel);
|
||||
cadenceel.x = time;
|
||||
cadenceel.y = el.cadence;
|
||||
cadence.push(cadenceel);
|
||||
resistanceel.x = time;
|
||||
resistanceel.y = el.resistance;
|
||||
resistance.push(resistanceel);
|
||||
reqresistanceel.x = time;
|
||||
reqresistanceel.y = el.req_resistance;
|
||||
reqresistance.push(reqresistanceel);
|
||||
|
||||
pelotonresistanceel.x = time;
|
||||
pelotonresistanceel.y = el.peloton_resistance;
|
||||
pelotonresistance.push(pelotonresistanceel);
|
||||
pelotonreqresistanceel.x = time;
|
||||
pelotonreqresistanceel.y = el.peloton_req_resistance;
|
||||
pelotonreqresistance.push(pelotonreqresistanceel);
|
||||
|
||||
speedel.x = time;
|
||||
speedel.y = el.speed;
|
||||
speed.push(speedel);
|
||||
inclinationel.x = time;
|
||||
inclinationel.y = el.inclination;
|
||||
inclination.push(inclinationel);
|
||||
}
|
||||
|
||||
const backgroundFill = {
|
||||
id: 'custom_canvas_background_color',
|
||||
beforeDraw: (chart) => {
|
||||
const ctx = chart.canvas.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fillRect(0, 0, chart.width, chart.height);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
let config = {
|
||||
type: 'line',
|
||||
plugins: [backgroundFill],
|
||||
data: {
|
||||
datasets: [{
|
||||
label: 'Watts',
|
||||
backgroundColor: window.chartColors.red,
|
||||
borderColor: window.chartColors.red,
|
||||
cubicInterpolationMode: 'monotone',
|
||||
data: watts,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
segment: {
|
||||
borderColor: ctx => ctx.p0.parsed.y < ftpZones[0] && ctx.p1.parsed.y < ftpZones[0] ? window.chartColors.grey :
|
||||
ctx.p0.parsed.y < ftpZones[1] && ctx.p1.parsed.y < ftpZones[1] ? window.chartColors.limegreen :
|
||||
ctx.p0.parsed.y < ftpZones[2] && ctx.p1.parsed.y < ftpZones[2] ? window.chartColors.gold :
|
||||
ctx.p0.parsed.y < ftpZones[3] && ctx.p1.parsed.y < ftpZones[3] ? window.chartColors.orange :
|
||||
ctx.p0.parsed.y < ftpZones[4] && ctx.p1.parsed.y < ftpZones[4] ? window.chartColors.darkorange :
|
||||
ctx.p0.parsed.y < ftpZones[5] && ctx.p1.parsed.y < ftpZones[5] ? window.chartColors.orangered :
|
||||
window.chartColors.red,
|
||||
}
|
||||
}, {
|
||||
label: 'Req. Watts',
|
||||
backgroundColor: window.chartColors.black,
|
||||
borderColor: window.chartColors.black,
|
||||
//cubicInterpolationMode: 'monotone',
|
||||
data: reqpower,
|
||||
fill: false,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
},
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
aspectRatio: div.width / div.height,
|
||||
grid: {
|
||||
zeroLineColor: 'rgba(0,255,0,1)'
|
||||
},
|
||||
plugins: {
|
||||
/*
|
||||
title:{
|
||||
display:true,
|
||||
backgroundColor: "#1d2330",
|
||||
padding: {
|
||||
top: 2,
|
||||
bottom: 2
|
||||
},
|
||||
text:'Watt'
|
||||
},*/
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
annotation: {
|
||||
annotations: {
|
||||
box1: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: 0,
|
||||
yMax: ftpZones[0],
|
||||
backgroundColor: "#d6d6d620"
|
||||
},
|
||||
box2: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[0],
|
||||
yMax: ftpZones[1],
|
||||
backgroundColor: window.chartColors.limegreent,
|
||||
},
|
||||
box3: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[1],
|
||||
yMax: ftpZones[2],
|
||||
backgroundColor: window.chartColors.goldt,
|
||||
},
|
||||
box4: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[2],
|
||||
yMax: ftpZones[3],
|
||||
backgroundColor: window.chartColors.oranget,
|
||||
},
|
||||
box5: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[3],
|
||||
yMax: ftpZones[4],
|
||||
backgroundColor: window.chartColors.darkoranget,
|
||||
},
|
||||
box6: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[4],
|
||||
yMax: ftpZones[5],
|
||||
backgroundColor: window.chartColors.orangeredt,
|
||||
},
|
||||
box7: {
|
||||
// Indicates the type of annotation
|
||||
type: 'box',
|
||||
xMin: 0,
|
||||
//xMax: maxEl,
|
||||
yMin: ftpZones[5],
|
||||
yMax: (watts_max > ftpZones[3] * 2 ? watts_max + 10 : ftpZones[3] * 2),
|
||||
backgroundColor: window.chartColors.redt,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: true
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Time'
|
||||
},
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback: function(value, index, values) {
|
||||
return value !== 0 ? Math.floor(value / 3600).toString().padStart(2, "0") + ":" + Math.floor((value / 60) - (Math.floor(value / 3600) * 60)).toString().padStart(2, "0") : "";
|
||||
},
|
||||
padding: -20,
|
||||
//stepSize: 300,
|
||||
align: "end",
|
||||
},
|
||||
//max: maxEl,
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: false,
|
||||
text: 'Watt'
|
||||
},
|
||||
min: 0,
|
||||
max: (watts_max > ftpZones[4] + 10 ? watts_max + 10 : ftpZones[4] + 10),
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
autoSkip: false,
|
||||
callback: value => [ftpZones[0] * 0.8, ftpZones[0], ftpZones[1], ftpZones[2], ftpZones[3], ftpZones[4], ftpZones[5]].includes(value) ?
|
||||
value === ftpZones[0] * 0.8 ? 'zone 1' :
|
||||
value === ftpZones[0] ? 'zone 2' :
|
||||
value === ftpZones[1] ? 'zone 3' :
|
||||
value === ftpZones[2] ? 'zone 4' :
|
||||
value === ftpZones[3] ? 'zone 5' :
|
||||
value === ftpZones[4] ? 'zone 6' :
|
||||
value === ftpZones[5] ? 'zone 7' : undefined : undefined,
|
||||
color: 'black',
|
||||
padding: -50,
|
||||
align: 'end',
|
||||
z: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
powerChart = new Chart(ctx, config);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
el = new MainWSQueueElement({
|
||||
msg: null
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 2000, 1);
|
||||
el.enqueue().then(process_workout).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
|
||||
function process_workout(arr) {
|
||||
powerChart.data.datasets[0].data.push({x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600), y: arr.watts});
|
||||
powerChart.update();
|
||||
refresh();
|
||||
}
|
||||
|
||||
function dochart_init() {
|
||||
onSettingsOK = true;
|
||||
keys_arr = ['ftp', 'miles_unit', 'age', 'heart_rate_zone1', 'heart_rate_zone2', 'heart_rate_zone3', 'heart_rate_zone4', 'heart_max_override_enable', 'heart_max_override_value']
|
||||
let el = new MainWSQueueElement({
|
||||
msg: 'getsettings',
|
||||
content: {
|
||||
keys: keys_arr
|
||||
}
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getsettings') {
|
||||
var heart_max_override_enable = false;
|
||||
var heart_max_override_value = 195;
|
||||
var heart_rate_zone1 = 0;
|
||||
var heart_rate_zone2 = 0;
|
||||
var heart_rate_zone3 = 0;
|
||||
var heart_rate_zone4 = 0;
|
||||
|
||||
for (let key of keys_arr) {
|
||||
if (msg.content[key] === undefined)
|
||||
return null;
|
||||
if (key === 'ftp') {
|
||||
ftp = msg.content[key];
|
||||
ftpZones[0] = Math.round(ftp * 0.55);
|
||||
ftpZones[1] = Math.round(ftp * 0.75);
|
||||
ftpZones[2] = Math.round(ftp * 0.90);
|
||||
ftpZones[3] = Math.round(ftp * 1.05);
|
||||
ftpZones[4] = Math.round(ftp * 1.20);
|
||||
ftpZones[5] = Math.round(ftp * 1.50);
|
||||
} else if (key === 'age') {
|
||||
age = msg.content[key];
|
||||
maxHeartRate = 220 - age;
|
||||
} else if (key === 'heart_max_override_enable') {
|
||||
heart_max_override_enable = msg.content[key];
|
||||
} else if (key === 'heart_max_override_value') {
|
||||
heart_max_override_value = msg.content[key];
|
||||
} else if (key === 'heart_rate_zone1') {
|
||||
heart_rate_zone1 = msg.content[key];
|
||||
heartZones[0] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone2') {
|
||||
heart_rate_zone2 = msg.content[key];
|
||||
heartZones[1] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone3') {
|
||||
heart_rate_zone3 = msg.content[key];
|
||||
heartZones[2] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'heart_rate_zone4') {
|
||||
heart_rate_zone4 = msg.content[key];
|
||||
heartZones[3] = Math.round(maxHeartRate * (msg.content[key] / 100));
|
||||
} else if (key === 'miles_unit') {
|
||||
if(msg.content[key] === true || msg.content[key] === 'true')
|
||||
miles = 0.621371;
|
||||
}
|
||||
}
|
||||
if(heart_max_override_enable) {
|
||||
maxHeartRate = heart_max_override_value;
|
||||
heartZones[0] = Math.round(maxHeartRate * (heart_rate_zone1 / 100));
|
||||
heartZones[1] = Math.round(maxHeartRate * (heart_rate_zone2 / 100));
|
||||
heartZones[2] = Math.round(maxHeartRate * (heart_rate_zone3 / 100));
|
||||
heartZones[3] = Math.round(maxHeartRate * (heart_rate_zone4 / 100));
|
||||
}
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 5000, 3);
|
||||
el.enqueue().then(onSettingsOK).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
})
|
||||
|
||||
el = new MainWSQueueElement({
|
||||
msg: 'getsessionarray'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_getsessionarray') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_arr).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
|
||||
el = new MainWSQueueElement({
|
||||
msg: 'gettrainingprogram'
|
||||
}, function(msg) {
|
||||
if (msg.msg === 'R_gettrainingprogram') {
|
||||
return msg.content;
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
el.enqueue().then(process_trainprogram).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$(window).on('load', function () {
|
||||
dochart_init(); return;
|
||||
|
||||
// DEBUG
|
||||
ftpZones[0] = Math.round(ftp * 0.55);
|
||||
ftpZones[1] = Math.round(ftp * 0.75);
|
||||
ftpZones[2] = Math.round(ftp * 0.90);
|
||||
ftpZones[3] = Math.round(ftp * 1.05);
|
||||
ftpZones[4] = Math.round(ftp * 1.20);
|
||||
ftpZones[5] = Math.round(ftp * 1.50);
|
||||
|
||||
heartZones[0] = 110;
|
||||
heartZones[1] = 130;
|
||||
heartZones[2] = 150;
|
||||
heartZones[3] = 170;
|
||||
|
||||
arr = [{'watts': 50, 'req_power': 150, 'elapsed_s':0,'elapsed_m':0,'elapsed_h':0, 'heart':90, 'resistance': 10, 'req_resistance': 15, 'cadence': 80, 'req_cadence': 90, 'speed': 10, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 60, 'req_power': 150, 'elapsed_s':1,'elapsed_m':1,'elapsed_h':0, 'heart':92, 'resistance': 11, 'req_resistance': 30, 'cadence': 90, 'req_cadence': 100, 'speed': 8, 'inclination': 2, 'peloton_resistance': 20, 'peloton_req_resistance': 25},
|
||||
{'watts': 70, 'req_power': 170, 'elapsed_s':2,'elapsed_m':2,'elapsed_h':0, 'heart':110, 'resistance': 12, 'req_resistance': 40, 'cadence': 100, 'req_cadence': 90, 'speed': 9, 'inclination': 2.5, 'peloton_resistance': 30, 'peloton_req_resistance': 35},
|
||||
{'watts': 140, 'req_power': 170, 'elapsed_s':3,'elapsed_m':3,'elapsed_h':0, 'heart':115, 'resistance': 16, 'req_resistance': 41, 'cadence': 90, 'req_cadence': 95, 'speed': 11, 'inclination': 1, 'peloton_resistance': 40, 'peloton_req_resistance': 45},
|
||||
{'watts': 130, 'req_power': 170, 'elapsed_s':4,'elapsed_m':4,'elapsed_h':0, 'heart':130, 'resistance': 18, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 95, 'speed': 10, 'inclination': 4, 'peloton_resistance': 50, 'peloton_req_resistance': 55},
|
||||
{'watts': 160, 'req_power': 170, 'elapsed_s':5,'elapsed_m':5,'elapsed_h':0, 'heart':135, 'resistance': 22, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 95, 'speed': 12, 'inclination': 1, 'peloton_resistance': 60, 'peloton_req_resistance': 15},
|
||||
{'watts': 180, 'req_power': 130, 'elapsed_s':6,'elapsed_m':6,'elapsed_h':0, 'heart':140, 'resistance': 31, 'req_resistance': 43, 'cadence': 95, 'req_cadence': 90, 'speed': 10, 'inclination': 3, 'peloton_resistance': 70, 'peloton_req_resistance': 15},
|
||||
{'watts': 120, 'req_power': 130, 'elapsed_s':7,'elapsed_m':7,'elapsed_h':0, 'heart':150, 'resistance': 18, 'req_resistance': 35, 'cadence': 95, 'req_cadence': 80, 'speed': 10, 'inclination': 4, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 190, 'req_power': 150, 'elapsed_s':1,'elapsed_m':8,'elapsed_h':0, 'heart':155, 'resistance': 17, 'req_resistance': 35, 'cadence': 95, 'req_cadence': 80, 'speed': 13, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 195, 'req_power': 170, 'elapsed_s':2,'elapsed_m':9,'elapsed_h':0, 'heart':165, 'resistance': 19, 'req_resistance': 30, 'cadence': 80, 'req_cadence': 80, 'speed': 12, 'inclination': 3, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 200, 'req_power': 170, 'elapsed_s':3,'elapsed_m':10,'elapsed_h':0, 'heart':153, 'resistance': 20, 'req_resistance': 25, 'cadence': 90, 'req_cadence': 90, 'speed': 10, 'inclination': 2, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 206, 'req_power': 170, 'elapsed_s':4,'elapsed_m':11,'elapsed_h':0, 'heart':152, 'resistance': 21, 'req_resistance': 35, 'cadence': 90, 'req_cadence': 90, 'speed': 12, 'inclination': 7, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 211, 'req_power': 170, 'elapsed_s':5,'elapsed_m':12,'elapsed_h':0, 'heart':180, 'resistance': 25, 'req_resistance': 35, 'cadence': 90, 'req_cadence': 70, 'speed': 10, 'inclination': 10, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 222, 'req_power': 130, 'elapsed_s':6,'elapsed_m':13,'elapsed_h':0, 'heart':182, 'resistance': 31, 'req_resistance': 35, 'cadence': 80, 'req_cadence': 70, 'speed': 7, 'inclination': 12, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 237, 'req_power': 130, 'elapsed_s':7,'elapsed_m':14,'elapsed_h':0, 'heart':160, 'resistance': 20, 'req_resistance': 50, 'cadence': 90, 'req_cadence': 70, 'speed': 6, 'inclination': 1, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 250, 'req_power': 170, 'elapsed_s':3,'elapsed_m':15,'elapsed_h':0, 'heart':115, 'resistance': 20, 'req_resistance': 50, 'cadence': 90, 'req_cadence': 90, 'speed': 10, 'inclination': 14, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 266, 'req_power': 170, 'elapsed_s':4,'elapsed_m':16,'elapsed_h':0, 'heart':120, 'resistance': 11, 'req_resistance': 35, 'cadence': 80, 'req_cadence': 60, 'speed': 10, 'inclination': 10, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 351, 'req_power': 170, 'elapsed_s':5,'elapsed_m':17,'elapsed_h':0, 'heart':112, 'resistance': 22, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 60, 'speed': 5, 'inclination': 9, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 322, 'req_power': 130, 'elapsed_s':6,'elapsed_m':18,'elapsed_h':0, 'heart':90, 'resistance': 25, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 96, 'speed': 10, 'inclination': 5, 'peloton_resistance': 10, 'peloton_req_resistance': 15},
|
||||
{'watts': 257, 'req_power': 130, 'elapsed_s':7,'elapsed_m':19,'elapsed_h':0, 'heart':120, 'resistance': 10, 'req_resistance': 23, 'cadence': 80, 'req_cadence': 97, 'speed': 10, 'inclination': 1, 'workoutName': '45min Power Zone Ride', 'workoutStartDate': '20/12/2021', 'instructorName': "Robin Arzon", 'watts_avg': 200, 'watts_max' : 351, 'heart_avg': 120, 'heart_max' : 150, 'jouls': 138000, 'calories': 950, 'distance': 11, 'cadence_avg': 65, 'peloton_resistance_avg': 22, 'deviceType': 1},
|
||||
]
|
||||
process_arr(arr);
|
||||
});
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#loading').hide();
|
||||
});
|
||||
@@ -849,7 +849,7 @@
|
||||
else
|
||||
$('.powerzone-value').html("<b>" + powerzone.toFixed(1) + "</b>");
|
||||
$('.powerzone-avg').html(powerzone_lapavg.toFixed(1));
|
||||
$('.spepowerzoneed-max').html(powerzone_lapmax.toFixed(1));
|
||||
$('.powerzone-max').html(powerzone_lapmax.toFixed(1));
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
|
||||
@@ -62,8 +62,8 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#ODO=\(distance)#")
|
||||
WatchKitConnection.distance = distance;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setKcal(kcal: Double) -> Void
|
||||
@@ -74,8 +74,48 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(kcal)#")
|
||||
WatchKitConnection.kcal = kcal;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setCadence(cadence: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.cadence = cadence;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setSpeed(speed: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.speed = speed;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
@objc public func setPower(power: Double) -> Void
|
||||
{
|
||||
var sender: String
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
sender = "PAD"
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
WatchKitConnection.power = power;
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
|
||||
func createString(sender: String) -> String {
|
||||
return "SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#";
|
||||
}
|
||||
|
||||
@objc func updateHeartRate() {
|
||||
@@ -85,8 +125,7 @@ var pedometer = CMPedometer()
|
||||
} else {
|
||||
sender = "PHONE"
|
||||
}
|
||||
Server.server?.send("SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#CAD=\(WatchKitConnection.stepCadence)#")
|
||||
|
||||
Server.server?.send(createString(sender: sender))
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -97,6 +97,18 @@ class Connection {
|
||||
if sender?.contains("PAD") ?? false && message.contains("ODO=") {
|
||||
let odo : String = message.slice(from: "ODO=", to: "#") ?? ""
|
||||
WatchKitConnection.distance = (Double(odo) ?? 0)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("BCAD=") {
|
||||
let cad : String = message.slice(from: "BCAD=", to: "#") ?? ""
|
||||
WatchKitConnection.cadence = (Double(cad) ?? 0)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("SPD=") {
|
||||
let spd : String = message.slice(from: "SPD=", to: "#") ?? ""
|
||||
WatchKitConnection.speed = (Double(spd) ?? 0)
|
||||
}
|
||||
if sender?.contains("PAD") ?? false && message.contains("PWR=") {
|
||||
let pwr : String = message.slice(from: "PWR=", to: "#") ?? ""
|
||||
WatchKitConnection.power = (Double(pwr) ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ class WatchKitConnection: NSObject {
|
||||
static var distance = 0.0
|
||||
static var stepCadence = 0
|
||||
static var kcal = 0.0
|
||||
static var speed = 0.0
|
||||
static var power = 0.0
|
||||
static var cadence = 0.0
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
@@ -130,6 +133,9 @@ extension WatchKitConnection: WCSessionDelegate {
|
||||
|
||||
replyValues["distance"] = WatchKitConnection.distance
|
||||
replyValues["kcal"] = WatchKitConnection.kcal
|
||||
replyValues["cadence"] = WatchKitConnection.cadence
|
||||
replyValues["power"] = WatchKitConnection.power
|
||||
replyValues["speed"] = WatchKitConnection.speed
|
||||
|
||||
replyHandler(replyValues)
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ class lockscreen {
|
||||
long stepCadence();
|
||||
void setKcal(double kcal);
|
||||
void setDistance(double distance);
|
||||
void setSpeed(double speed);
|
||||
void setPower(double power);
|
||||
void setCadence(double cadence);
|
||||
|
||||
// virtualbike
|
||||
void virtualbike_ios();
|
||||
@@ -53,6 +56,9 @@ class lockscreen {
|
||||
void garminconnect_init();
|
||||
int getHR();
|
||||
int getFootCad();
|
||||
|
||||
// debug
|
||||
static void debug(const char* debugstring);
|
||||
};
|
||||
|
||||
#endif // LOCKSCREEN_H
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#import <ConnectIQ/ConnectIQ.h>
|
||||
#import "qdomyoszwift-Swift2.h"
|
||||
#include "ios/lockscreen.h"
|
||||
#include <QDebug>
|
||||
|
||||
@class virtualbike_ios_swift;
|
||||
@class virtualbike_zwift;
|
||||
@@ -30,7 +31,9 @@ void lockscreen::request()
|
||||
{
|
||||
h = [[healthkit alloc] init];
|
||||
[h request];
|
||||
Garmin = [[GarminConnect alloc] init];
|
||||
if (@available(iOS 13, *)) {
|
||||
Garmin = [[GarminConnect alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
long lockscreen::heartRate()
|
||||
@@ -53,6 +56,20 @@ void lockscreen::setDistance(double distance)
|
||||
[h setDistanceWithDistance:distance * 0.621371];
|
||||
}
|
||||
|
||||
void lockscreen::setPower(double power)
|
||||
{
|
||||
[h setPowerWithPower:power];
|
||||
}
|
||||
void lockscreen::setCadence(double cadence)
|
||||
{
|
||||
[h setCadenceWithCadence:cadence];
|
||||
}
|
||||
void lockscreen::setSpeed(double speed)
|
||||
{
|
||||
[h setSpeedWithSpeed:speed];
|
||||
}
|
||||
|
||||
|
||||
void lockscreen::virtualbike_ios()
|
||||
{
|
||||
_virtualbike = [[virtualbike_ios_swift alloc] init];
|
||||
@@ -237,4 +254,8 @@ double lockscreen::getVolume()
|
||||
[[AVAudioSession sharedInstance] setActive:true error:0];
|
||||
return [[AVAudioSession sharedInstance] outputVolume];
|
||||
}
|
||||
|
||||
void lockscreen::debug(const char* debugstring) {
|
||||
qDebug() << debugstring;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -723,6 +723,9 @@ void m3ibike::processAdvertising(const QByteArray &data) {
|
||||
long appleWatchHeartRate = h->heartRate();
|
||||
h->setKcal(KCal.value());
|
||||
h->setDistance(Distance.value());
|
||||
h->setSpeed(Speed.value());
|
||||
h->setPower(m_watt.value());
|
||||
h->setCadence(Cadence.value());
|
||||
if (appleWatchHeartRate == 0)
|
||||
Heart = k3.pulse;
|
||||
else
|
||||
|
||||
12
src/main.qml
12
src/main.qml
@@ -548,7 +548,7 @@ ApplicationWindow {
|
||||
id: toolButtonMaps
|
||||
icon.source: ( "icons/icons/maps-icon-16.png" )
|
||||
onClicked: { loadMaps(); }
|
||||
anchors.right: toolButtonLockTiles.left
|
||||
anchors.right: toolButtonChart.left
|
||||
visible: rootItem.mapsVisible
|
||||
}
|
||||
|
||||
@@ -569,6 +569,14 @@ ApplicationWindow {
|
||||
visible: rootItem.videoIconVisible
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: toolButtonChart
|
||||
icon.source: ( "icons/icons/chart.png" )
|
||||
onClicked: { rootItem.chartFooterVisible = !rootItem.chartFooterVisible }
|
||||
anchors.right: toolButtonLockTiles.left
|
||||
visible: rootItem.chartIconVisible
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: toolButtonLockTiles
|
||||
icon.source: ( window.lockTiles ? "icons/icons/unlock.png" : "icons/icons/lock.png")
|
||||
@@ -737,7 +745,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
text: "version 2.13.98"
|
||||
text: "version 2.16.15"
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <chrono>
|
||||
@@ -13,9 +14,100 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHeartService) {
|
||||
nordictrackifitadbbikeLogcatAdbThread::nordictrackifitadbbikeLogcatAdbThread(QString s) { Q_UNUSED(s) }
|
||||
|
||||
void nordictrackifitadbbikeLogcatAdbThread::run() {
|
||||
QSettings settings;
|
||||
bool nordictrack_ifit_adb_remote = settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote).toBool();
|
||||
QString ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
|
||||
runAdbCommand("connect " + ip);
|
||||
|
||||
while (1) {
|
||||
runAdbTailCommand("logcat");
|
||||
if(adbCommandPending.length() != 0) {
|
||||
runAdbCommand(adbCommandPending);
|
||||
adbCommandPending = "";
|
||||
}
|
||||
msleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
QString nordictrackifitadbbikeLogcatAdbThread::runAdbCommand(QString command) {
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QProcess process;
|
||||
emit debug("adb >> " + command);
|
||||
process.start("adb/adb.exe", QStringList(command.split(' ')));
|
||||
process.waitForFinished(-1); // will wait forever until finished
|
||||
|
||||
QString out = process.readAllStandardOutput();
|
||||
QString err = process.readAllStandardError();
|
||||
|
||||
emit debug("adb << OUT " + out);
|
||||
emit debug("adb << ERR" + err);
|
||||
#else
|
||||
QString out;
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
bool nordictrackifitadbbikeLogcatAdbThread::runCommand(QString command) {
|
||||
if(adbCommandPending.length() == 0) {
|
||||
adbCommandPending = command;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void nordictrackifitadbbikeLogcatAdbThread::runAdbTailCommand(QString command) {
|
||||
#ifdef Q_OS_WINDOWS
|
||||
auto process = new QProcess;
|
||||
QObject::connect(process, &QProcess::readyReadStandardOutput, [process, this]() {
|
||||
QString output = process->readAllStandardOutput();
|
||||
// qDebug() << "adbLogCat STDOUT << " << output;
|
||||
QStringList lines = output.split('\n', Qt::SplitBehaviorFlags::SkipEmptyParts);
|
||||
bool wattFound = false;
|
||||
bool hrmFound = false;
|
||||
foreach (QString line, lines) {
|
||||
if (line.contains("Changed KPH")) {
|
||||
emit debug(line);
|
||||
speed = line.split(' ').last().toDouble();
|
||||
} else if (line.contains("Changed Grade")) {
|
||||
emit debug(line);
|
||||
inclination = line.split(' ').last().toDouble();
|
||||
} else if (line.contains("Changed Watts")) {
|
||||
emit debug(line);
|
||||
watt = line.split(' ').last().toDouble();
|
||||
wattFound = true;
|
||||
} else if (line.contains("HeartRateDataUpdate")) {
|
||||
emit debug(line);
|
||||
QStringList splitted = line.split(' ', Qt::SkipEmptyParts);
|
||||
if (splitted.length() > 14) {
|
||||
hrm = splitted[14].toInt();
|
||||
hrmFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
emit onSpeedInclination(speed, inclination);
|
||||
if (wattFound)
|
||||
emit onWatt(watt);
|
||||
if (hrmFound)
|
||||
emit onHRM(hrm);
|
||||
});
|
||||
QObject::connect(process, &QProcess::readyReadStandardError, [process, this]() {
|
||||
auto output = process->readAllStandardError();
|
||||
emit debug("adbLogCat ERROR << " + output);
|
||||
});
|
||||
emit debug("adbLogCat >> " + command);
|
||||
process->start("adb/adb.exe", QStringList(command.split(' ')));
|
||||
process->waitForFinished(-1);
|
||||
#endif
|
||||
}
|
||||
|
||||
nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHeartService,
|
||||
uint8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
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);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
@@ -36,31 +128,55 @@ nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHe
|
||||
if (!firstStateChanged && !this->hasVirtualDevice()) {
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
if (virtual_device_enabled) {
|
||||
debug("creating virtual bike interface...");
|
||||
auto virtualBike = new virtualbike(this);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this,
|
||||
&nordictrackifitadbbike::changeInclinationRequested);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
firstStateChanged = 1;
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence) {
|
||||
qDebug() << "ios_peloton_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualbike_ios();
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
qDebug() << QStringLiteral("creating virtual bike interface...");
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &nordictrackifitadbbike::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
|
||||
if (nordictrack_ifit_adb_remote) {
|
||||
#ifdef Q_OS_ANDROID
|
||||
if(nordictrack_ifit_adb_remote) {
|
||||
QAndroidJniObject IP = QAndroidJniObject::fromString(ip).object<jstring>();
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote", "createConnection",
|
||||
"(Ljava/lang/String;Landroid/content/Context;)V", IP.object<jstring>(), QtAndroid::androidContext().object());
|
||||
}
|
||||
"(Ljava/lang/String;Landroid/content/Context;)V",
|
||||
IP.object<jstring>(), QtAndroid::androidContext().object());
|
||||
#elif defined Q_OS_WIN
|
||||
logcatAdbThread = new nordictrackifitadbbikeLogcatAdbThread("logcatAdbThread");
|
||||
/*connect(logcatAdbThread, &nordictrackifitadbbikeLogcatAdbThread::onSpeedInclination, this,
|
||||
&nordictrackifitadbbike::onSpeedInclination);
|
||||
connect(logcatAdbThread, &nordictrackifitadbbikeLogcatAdbThread::onWatt, this,
|
||||
&nordictrackifitadbbike::onWatt);*/
|
||||
connect(logcatAdbThread, &nordictrackifitadbbikeLogcatAdbThread::onHRM, this, &nordictrackifitadbbike::onHRM);
|
||||
connect(logcatAdbThread, &nordictrackifitadbbikeLogcatAdbThread::debug, this, &nordictrackifitadbbike::debug);
|
||||
logcatAdbThread->start();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool nordictrackifitadbbike::inclinationAvailableByHardware() { return true; }
|
||||
|
||||
double nordictrackifitadbbike::getDouble(QString v) {
|
||||
QChar d = QLocale().decimalPoint();
|
||||
if(d == ',') {
|
||||
if (d == ',') {
|
||||
v = v.replace('.', ',');
|
||||
}
|
||||
return QLocale().toDouble(v);
|
||||
@@ -117,8 +233,9 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
if (aValues.length()) {
|
||||
resistance = getDouble(aValues.last());
|
||||
m_pelotonResistance = (100 / 32) * resistance;
|
||||
qDebug() << QStringLiteral("Current Peloton Resistance: ") << m_pelotonResistance.value() << resistance;
|
||||
//Resistance = resistance;
|
||||
qDebug() << QStringLiteral("Current Peloton Resistance: ") << m_pelotonResistance.value()
|
||||
<< resistance;
|
||||
// Resistance = resistance;
|
||||
}
|
||||
} else if (line.contains(QStringLiteral("Changed Watts"))) {
|
||||
QStringList aValues = line.split(" ");
|
||||
@@ -136,28 +253,36 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
}
|
||||
|
||||
// since the motor of the bike is slow, let's filter the inclination changes to more than 4 seconds
|
||||
if(lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > 4) {
|
||||
if (lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > 4) {
|
||||
lastInclinationChanged = QDateTime::currentDateTime();
|
||||
#ifdef Q_OS_ANDROID
|
||||
bool nordictrack_ifit_adb_remote = settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote).toBool();
|
||||
if(nordictrack_ifit_adb_remote) {
|
||||
if(requestInclination != -100) {
|
||||
bool nordictrack_ifit_adb_remote =
|
||||
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
|
||||
.toBool();
|
||||
if (nordictrack_ifit_adb_remote) {
|
||||
if (requestInclination != -100) {
|
||||
double inc = qRound(requestInclination / 0.5) * 0.5;
|
||||
if(inc != currentInclination().value()) {
|
||||
if (inc != currentInclination().value()) {
|
||||
int x1 = 75;
|
||||
int y2 = (int) (616.18 - (17.223 * (inc + gears())));
|
||||
int y1Resistance = (int) (616.18 - (17.223 * currentInclination().value()));
|
||||
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";
|
||||
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>());
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
requestInclination = -100;
|
||||
}
|
||||
#endif
|
||||
|
||||
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";";
|
||||
requestInclination = -100;
|
||||
@@ -175,6 +300,11 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
@@ -188,8 +318,21 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadencep =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadencep && h && firstStateChanged) {
|
||||
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current Gear: ") + QString::number(gear));
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
@@ -199,6 +342,24 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
}
|
||||
}
|
||||
|
||||
void nordictrackifitadbbike::onHRM(int hrm) {
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
|
||||
if (
|
||||
#ifdef Q_OS_ANDROID
|
||||
(!settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) &&
|
||||
#endif
|
||||
heartRateBeltName.startsWith(QStringLiteral("Disabled")) && !disable_hr_frommachinery) {
|
||||
|
||||
Heart = hrm;
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
}
|
||||
}
|
||||
|
||||
resistance_t nordictrackifitadbbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
if (pelotonResistance <= 10) {
|
||||
return 1;
|
||||
@@ -287,4 +448,3 @@ void nordictrackifitadbbike::changeInclinationRequested(double grade, double per
|
||||
}
|
||||
|
||||
bool nordictrackifitadbbike::connected() { return true; }
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include "bike.h"
|
||||
@@ -25,10 +26,42 @@
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class nordictrackifitadbbikeLogcatAdbThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit nordictrackifitadbbikeLogcatAdbThread(QString s);
|
||||
bool runCommand(QString command);
|
||||
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void onSpeedInclination(double speed, double inclination);
|
||||
void debug(QString message);
|
||||
void onWatt(double watt);
|
||||
void onHRM(int hrm);
|
||||
|
||||
private:
|
||||
QString adbCommandPending = "";
|
||||
QString runAdbCommand(QString command);
|
||||
double speed = 0;
|
||||
double inclination = 0;
|
||||
double watt = 0;
|
||||
int hrm = 0;
|
||||
QString name;
|
||||
struct adbfile {
|
||||
QDateTime date;
|
||||
QString name;
|
||||
};
|
||||
|
||||
void runAdbTailCommand(QString command);
|
||||
};
|
||||
|
||||
class nordictrackifitadbbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
nordictrackifitadbbike(bool noWriteResistance, bool noHeartService);
|
||||
nordictrackifitadbbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
bool connected() override;
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
bool inclinationAvailableByHardware() override;
|
||||
@@ -55,9 +88,9 @@ class nordictrackifitadbbike : public bike {
|
||||
QUdpSocket *socket = nullptr;
|
||||
QHostAddress lastSender;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
nordictrackifitadbbikeLogcatAdbThread *logcatAdbThread = nullptr;
|
||||
|
||||
QString lastCommand;
|
||||
#endif
|
||||
|
||||
QString ip;
|
||||
|
||||
@@ -73,6 +106,7 @@ class nordictrackifitadbbike : public bike {
|
||||
|
||||
void processPendingDatagrams();
|
||||
void changeInclinationRequested(double grade, double percentage);
|
||||
void onHRM(int hrm);
|
||||
|
||||
void update();
|
||||
};
|
||||
|
||||
@@ -367,7 +367,8 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
|
||||
|
||||
bool atLeastOnePower = false;
|
||||
if (trainrows.empty() && !segments_segment_list.isEmpty() &&
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::ROWING) {
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::ROWING &&
|
||||
bluetoothManager->device()->deviceType() != bluetoothdevice::TREADMILL) {
|
||||
foreach (QJsonValue o, segments_segment_list) {
|
||||
QJsonArray subsegments_v2 = o["subsegments_v2"].toArray();
|
||||
if (!subsegments_v2.isEmpty()) {
|
||||
@@ -432,6 +433,14 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
|
||||
trainrows.append(row);
|
||||
atLeastOnePower = true;
|
||||
}
|
||||
} else if (!zone.toUpper().compare(QStringLiteral("RECOVERY"))) {
|
||||
r.duration = QTime(0, len / 60, len % 60, 0);
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.45;
|
||||
if (r.power != -1) {
|
||||
atLeastOnePower = true;
|
||||
}
|
||||
trainrows.append(r);
|
||||
qDebug() << r.duration << "power" << r.power;
|
||||
} else if (!zone.toUpper().compare(QStringLiteral("FLAT ROAD"))) {
|
||||
r.duration = QTime(0, len / 60, len % 60, 0);
|
||||
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.50;
|
||||
@@ -512,6 +521,16 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
|
||||
}
|
||||
trainrows.append(r);
|
||||
qDebug() << r.duration << "power" << r.power;
|
||||
} else {
|
||||
if(len > 0 && atLeastOnePower) {
|
||||
r.duration = QTime(0, len / 60, len % 60, 0);
|
||||
r.power = -1;
|
||||
if (r.power != -1) {
|
||||
atLeastOnePower = true;
|
||||
}
|
||||
qDebug() << "ERROR not handled!" << zone;
|
||||
trainrows.append(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,36 +543,50 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
|
||||
QJsonObject target_metrics_data_list = ride[QStringLiteral("target_metrics_data")].toObject();
|
||||
QJsonArray pace_intensities_list = target_metrics_data_list[QStringLiteral("pace_intensities")].toArray();
|
||||
|
||||
int pace_count = 0;
|
||||
int pace_count = 0;
|
||||
rower_pace_offset = 0;
|
||||
|
||||
foreach (QJsonValue o, pace_intensities_list) {
|
||||
if(o["value"].toInt() < 0) {
|
||||
if(abs(o["value"].toInt()) > rower_pace_offset)
|
||||
rower_pace_offset = abs(o["value"].toInt());
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "rower_pace_offset" << rower_pace_offset;
|
||||
|
||||
foreach (QJsonValue o, pace_intensities_list) {
|
||||
qDebug() << o;
|
||||
pace_count = o["value"].toInt();
|
||||
if (pace_count < 4 && pace_count >= 0) {
|
||||
pace_count = o["value"].toInt() + rower_pace_offset;
|
||||
if (pace_count < 5 && pace_count >= 0) {
|
||||
rower_pace[pace_count].display_name = o["display_name"].toString();
|
||||
rower_pace[pace_count].value = o["value"].toInt();
|
||||
|
||||
QJsonArray levels = o["pace_levels"].toArray();
|
||||
if (levels.count() > 6) {
|
||||
if (levels.count() > 10) {
|
||||
qDebug() << "peloton pace levels had been changed!";
|
||||
}
|
||||
int count = 0;
|
||||
foreach (QJsonValue level, levels) {
|
||||
count = level["slug"].toString().right(1).toInt() - 1;
|
||||
if (count >= 0 && count < 6) {
|
||||
rower_pace[pace_count].levels[count].fast_pace = level["fast_pace"].toDouble();
|
||||
rower_pace[pace_count].levels[count].slow_pace = level["slow_pace"].toDouble();
|
||||
rower_pace[pace_count].levels[count].display_name = level["display_name"].toString();
|
||||
rower_pace[pace_count].levels[count].slug = level["slug"].toString();
|
||||
|
||||
qDebug() << count << rower_pace[pace_count].levels[count].display_name
|
||||
<< rower_pace[pace_count].levels[count].fast_pace
|
||||
<< rower_pace[pace_count].levels[count].slow_pace
|
||||
<< rower_pace[pace_count].levels[count].slug;
|
||||
if(level["slug"].toString().split("_").count() > 1 ) {
|
||||
count = level["slug"].toString().split("_")[1].toInt() - 1;
|
||||
if (count >= 0 && count < 11) {
|
||||
rower_pace[pace_count].levels[count].fast_pace = level["fast_pace"].toDouble();
|
||||
rower_pace[pace_count].levels[count].slow_pace = level["slow_pace"].toDouble();
|
||||
rower_pace[pace_count].levels[count].display_name = level["display_name"].toString();
|
||||
rower_pace[pace_count].levels[count].slug = level["slug"].toString();
|
||||
|
||||
qDebug() << count << level << rower_pace[pace_count].levels[count].display_name
|
||||
<< rower_pace[pace_count].levels[count].fast_pace
|
||||
<< rower_pace[pace_count].levels[count].slow_pace
|
||||
<< rower_pace[pace_count].levels[count].slug;
|
||||
} else {
|
||||
qDebug() << level["slug"].toString() << "slug error";
|
||||
}
|
||||
} else {
|
||||
qDebug() << level["slug"].toString() << "slug error";
|
||||
qDebug() << level["slug"].toString() << "slug count error";
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << pace_count << rower_pace[pace_count].display_name << rower_pace[pace_count].value;
|
||||
} else {
|
||||
qDebug() << "pace_count error!";
|
||||
@@ -694,8 +727,8 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
|
||||
1;
|
||||
double strokes_rate_lower = strokes_rate[QStringLiteral("lower")].toDouble();
|
||||
double strokes_rate_upper = strokes_rate[QStringLiteral("upper")].toDouble();
|
||||
int pace_intensity_lower = pace_intensity[QStringLiteral("lower")].toInt();
|
||||
int pace_intensity_upper = pace_intensity[QStringLiteral("upper")].toInt();
|
||||
int pace_intensity_lower = pace_intensity[QStringLiteral("lower")].toInt() + rower_pace_offset;
|
||||
int pace_intensity_upper = pace_intensity[QStringLiteral("upper")].toInt() + rower_pace_offset;
|
||||
int offset_start = offset[QStringLiteral("start")].toInt();
|
||||
int offset_end = offset[QStringLiteral("end")].toInt();
|
||||
double strokes_rate_average = ((strokes_rate_upper - strokes_rate_lower) / 2.0) + strokes_rate_lower;
|
||||
@@ -710,7 +743,7 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
|
||||
r.cadence = ((strokes_rate_upper - strokes_rate_lower) / 2.0) + strokes_rate_lower;
|
||||
}
|
||||
|
||||
if (pace_intensity_lower >= 0 && pace_intensity_lower < 4) {
|
||||
if (pace_intensity_lower >= 0 && pace_intensity_lower < 5) {
|
||||
r.average_speed =
|
||||
(rowerpaceToSpeed(rower_pace[pace_intensity_lower].levels[peloton_rower_level].fast_pace) +
|
||||
rowerpaceToSpeed(rower_pace[pace_intensity_lower].levels[peloton_rower_level].slow_pace)) /
|
||||
|
||||
@@ -94,10 +94,11 @@ class peloton : public QObject {
|
||||
typedef struct _peloton_rower_pace_intensities {
|
||||
QString display_name;
|
||||
int value;
|
||||
_peloton_rower_pace_intensities_level levels[6];
|
||||
_peloton_rower_pace_intensities_level levels[10];
|
||||
} _peloton_rower_pace_intensities;
|
||||
|
||||
_peloton_rower_pace_intensities rower_pace[4];
|
||||
_peloton_rower_pace_intensities rower_pace[5];
|
||||
int rower_pace_offset = 0;
|
||||
|
||||
private slots:
|
||||
void login_onfinish(QNetworkReply *reply);
|
||||
|
||||
@@ -1042,6 +1042,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
m_pelotonResistance = 30;
|
||||
break;
|
||||
case 0x0b:
|
||||
case 0x0c:
|
||||
Resistance = 5;
|
||||
m_pelotonResistance = 35;
|
||||
break;
|
||||
@@ -1050,6 +1051,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
m_pelotonResistance = 40;
|
||||
break;
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
Resistance = 7;
|
||||
m_pelotonResistance = 45;
|
||||
break;
|
||||
@@ -1078,6 +1080,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
m_pelotonResistance = 75;
|
||||
break;
|
||||
case 0x21:
|
||||
case 0x22:
|
||||
Resistance = 14;
|
||||
m_pelotonResistance = 80;
|
||||
break;
|
||||
@@ -1086,6 +1089,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
m_pelotonResistance = 85;
|
||||
break;
|
||||
case 0x26:
|
||||
case 0x27:
|
||||
Resistance = 16;
|
||||
m_pelotonResistance = 100;
|
||||
break;
|
||||
|
||||
@@ -214,9 +214,10 @@ void proformelliptical::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
|
||||
if (newValue.length() == 20 && newValue.at(0) == 0x01 && newValue.at(1) == 0x12 &&
|
||||
((newValue.at(2) == 0x46) || (newValue.at(2) == 0x5a))) {
|
||||
double speed_from_machinery =
|
||||
(double)(((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14))) / 100.0;
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed =
|
||||
(double)(((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14))) / 100.0;
|
||||
Speed = speed_from_machinery;
|
||||
} else {
|
||||
Speed = speedFromWatts();
|
||||
}
|
||||
@@ -235,6 +236,7 @@ void proformelliptical::characteristicChanged(const QLowEnergyCharacteristic &ch
|
||||
|
||||
Resistance = GetResistanceFromPacket(newValue);
|
||||
Cadence = (newValue.at(18) * cadence_gain) + cadence_offset;
|
||||
m_watt = (double)(((uint16_t)((uint8_t)newValue.at(13)) << 8) + (uint16_t)((uint8_t)newValue.at(12)));
|
||||
if (watts())
|
||||
KCal += ((((0.048 * ((double)watts()) + 1.19) * weight * 3.5) / 200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
@@ -518,3 +520,5 @@ void proformelliptical::controllerStateChanged(QLowEnergyController::ControllerS
|
||||
m_control->connectToDevice();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t proformelliptical::watts() { return m_watt.value(); }
|
||||
|
||||
@@ -49,6 +49,7 @@ class proformelliptical : public elliptical {
|
||||
void sendPoll();
|
||||
void forceIncline(double incline);
|
||||
void forceSpeed(double speed);
|
||||
uint16_t watts() override;
|
||||
|
||||
QTimer *refresh;
|
||||
uint8_t counterPoll = 0;
|
||||
|
||||
@@ -54,128 +54,262 @@ void proformrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QS
|
||||
}
|
||||
|
||||
void proformrower::forceResistance(resistance_t requestResistance) {
|
||||
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x03, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x58, 0x06, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xf9, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9a, 0x09, 0x00, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x3a, 0x0b, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xdb, 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x7c, 0x0e, 0x00, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x1c, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xbd, 0x11, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x5e, 0x13, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xfe, 0x14, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9f, 0x16, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x40, 0x18, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe0, 0x19, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res17[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x81, 0x1b, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res18[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x22, 0x1d, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res19[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xc2, 0x1e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res20[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x63, 0x20, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res21[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x04, 0x22, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res22[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xa4, 0x23, 0x00, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res23[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x45, 0x25, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res24[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe6, 0x26, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
QSettings settings;
|
||||
bool proform_rower_sport_rl =
|
||||
settings.value(QZSettings::proform_rower_sport_rl, QZSettings::default_proform_rower_sport_rl).toBool();
|
||||
|
||||
switch (requestResistance) {
|
||||
case 1:
|
||||
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
|
||||
break;
|
||||
case 2:
|
||||
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
|
||||
break;
|
||||
case 3:
|
||||
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
|
||||
break;
|
||||
case 4:
|
||||
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
|
||||
break;
|
||||
case 5:
|
||||
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
|
||||
break;
|
||||
case 6:
|
||||
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
|
||||
break;
|
||||
case 7:
|
||||
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
|
||||
break;
|
||||
case 8:
|
||||
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
|
||||
break;
|
||||
case 9:
|
||||
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
|
||||
break;
|
||||
case 10:
|
||||
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
|
||||
break;
|
||||
case 11:
|
||||
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
|
||||
break;
|
||||
case 12:
|
||||
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
|
||||
break;
|
||||
case 13:
|
||||
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
|
||||
break;
|
||||
case 14:
|
||||
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
|
||||
break;
|
||||
case 15:
|
||||
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
|
||||
break;
|
||||
case 16:
|
||||
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
|
||||
break;
|
||||
case 17:
|
||||
writeCharacteristic((uint8_t *)res17, sizeof(res17), QStringLiteral("resistance17"), false, true);
|
||||
break;
|
||||
case 18:
|
||||
writeCharacteristic((uint8_t *)res18, sizeof(res18), QStringLiteral("resistance18"), false, true);
|
||||
break;
|
||||
case 19:
|
||||
writeCharacteristic((uint8_t *)res19, sizeof(res19), QStringLiteral("resistance19"), false, true);
|
||||
break;
|
||||
case 20:
|
||||
writeCharacteristic((uint8_t *)res20, sizeof(res20), QStringLiteral("resistance20"), false, true);
|
||||
break;
|
||||
case 21:
|
||||
writeCharacteristic((uint8_t *)res21, sizeof(res21), QStringLiteral("resistance21"), false, true);
|
||||
break;
|
||||
case 22:
|
||||
writeCharacteristic((uint8_t *)res22, sizeof(res22), QStringLiteral("resistance22"), false, true);
|
||||
break;
|
||||
case 23:
|
||||
writeCharacteristic((uint8_t *)res23, sizeof(res23), QStringLiteral("resistance23"), false, true);
|
||||
break;
|
||||
case 24:
|
||||
writeCharacteristic((uint8_t *)res24, sizeof(res24), QStringLiteral("resistance24"), false, true);
|
||||
break;
|
||||
if (proform_rower_sport_rl) {
|
||||
const uint8_t unlock_res[] = {0xfe, 0x02, 0x0d, 0x02};
|
||||
|
||||
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x76, 0x01, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x17, 0x03, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xb8, 0x04, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x58, 0x06, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xf9, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9a, 0x09, 0x00, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x3a, 0x0b, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xdb, 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x7c, 0x0e, 0x00, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x1c, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xbd, 0x11, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x5e, 0x13, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xfe, 0x14, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9f, 0x16, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x40, 0x18, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe0, 0x19, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res17[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x81, 0x1b, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res18[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x22, 0x1d, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res19[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xc2, 0x1e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res20[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x63, 0x20, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res21[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x04, 0x22, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res22[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xa4, 0x23, 0x00, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res23[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x45, 0x25, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res24[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe6, 0x26, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
writeCharacteristic((uint8_t*)unlock_res, sizeof(unlock_res), QStringLiteral("unlock_resistance"), false, false);
|
||||
|
||||
switch (requestResistance) {
|
||||
case 1:
|
||||
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
|
||||
break;
|
||||
case 2:
|
||||
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
|
||||
break;
|
||||
case 3:
|
||||
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
|
||||
break;
|
||||
case 4:
|
||||
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
|
||||
break;
|
||||
case 5:
|
||||
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
|
||||
break;
|
||||
case 6:
|
||||
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
|
||||
break;
|
||||
case 7:
|
||||
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
|
||||
break;
|
||||
case 8:
|
||||
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
|
||||
break;
|
||||
case 9:
|
||||
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
|
||||
break;
|
||||
case 10:
|
||||
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
|
||||
break;
|
||||
case 11:
|
||||
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
|
||||
break;
|
||||
case 12:
|
||||
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
|
||||
break;
|
||||
case 13:
|
||||
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
|
||||
break;
|
||||
case 14:
|
||||
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
|
||||
break;
|
||||
case 15:
|
||||
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
|
||||
break;
|
||||
case 16:
|
||||
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
|
||||
break;
|
||||
case 17:
|
||||
writeCharacteristic((uint8_t *)res17, sizeof(res17), QStringLiteral("resistance17"), false, true);
|
||||
break;
|
||||
case 18:
|
||||
writeCharacteristic((uint8_t *)res18, sizeof(res18), QStringLiteral("resistance18"), false, true);
|
||||
break;
|
||||
case 19:
|
||||
writeCharacteristic((uint8_t *)res19, sizeof(res19), QStringLiteral("resistance19"), false, true);
|
||||
break;
|
||||
case 20:
|
||||
writeCharacteristic((uint8_t *)res20, sizeof(res20), QStringLiteral("resistance20"), false, true);
|
||||
break;
|
||||
case 21:
|
||||
writeCharacteristic((uint8_t *)res21, sizeof(res21), QStringLiteral("resistance21"), false, true);
|
||||
break;
|
||||
case 22:
|
||||
writeCharacteristic((uint8_t *)res22, sizeof(res22), QStringLiteral("resistance22"), false, true);
|
||||
break;
|
||||
case 23:
|
||||
writeCharacteristic((uint8_t *)res23, sizeof(res23), QStringLiteral("resistance23"), false, true);
|
||||
break;
|
||||
case 24:
|
||||
writeCharacteristic((uint8_t *)res24, sizeof(res24), QStringLiteral("resistance24"), false, true);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x01, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x03, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x02,
|
||||
0x00, 0x10, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x58, 0x06, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xf9, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9a, 0x09, 0x00, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x3a, 0x0b, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xdb, 0x0c, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x7c, 0x0e, 0x00, 0xae, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x1c, 0x10, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xbd, 0x11, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x5e, 0x13, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xfe, 0x14, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x9f, 0x16, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x40, 0x18, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe0, 0x19, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res17[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x81, 0x1b, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res18[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x22, 0x1d, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res19[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xc2, 0x1e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res20[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x63, 0x20, 0x00, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res21[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x04, 0x22, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res22[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xa4, 0x23, 0x00, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res23[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0x45, 0x25, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
const uint8_t res24[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x14, 0x09, 0x02, 0x01,
|
||||
0x04, 0xe6, 0x26, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
switch (requestResistance) {
|
||||
case 1:
|
||||
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
|
||||
break;
|
||||
case 2:
|
||||
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
|
||||
break;
|
||||
case 3:
|
||||
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
|
||||
break;
|
||||
case 4:
|
||||
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
|
||||
break;
|
||||
case 5:
|
||||
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
|
||||
break;
|
||||
case 6:
|
||||
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
|
||||
break;
|
||||
case 7:
|
||||
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
|
||||
break;
|
||||
case 8:
|
||||
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
|
||||
break;
|
||||
case 9:
|
||||
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
|
||||
break;
|
||||
case 10:
|
||||
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
|
||||
break;
|
||||
case 11:
|
||||
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
|
||||
break;
|
||||
case 12:
|
||||
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
|
||||
break;
|
||||
case 13:
|
||||
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
|
||||
break;
|
||||
case 14:
|
||||
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
|
||||
break;
|
||||
case 15:
|
||||
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
|
||||
break;
|
||||
case 16:
|
||||
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
|
||||
break;
|
||||
case 17:
|
||||
writeCharacteristic((uint8_t *)res17, sizeof(res17), QStringLiteral("resistance17"), false, true);
|
||||
break;
|
||||
case 18:
|
||||
writeCharacteristic((uint8_t *)res18, sizeof(res18), QStringLiteral("resistance18"), false, true);
|
||||
break;
|
||||
case 19:
|
||||
writeCharacteristic((uint8_t *)res19, sizeof(res19), QStringLiteral("resistance19"), false, true);
|
||||
break;
|
||||
case 20:
|
||||
writeCharacteristic((uint8_t *)res20, sizeof(res20), QStringLiteral("resistance20"), false, true);
|
||||
break;
|
||||
case 21:
|
||||
writeCharacteristic((uint8_t *)res21, sizeof(res21), QStringLiteral("resistance21"), false, true);
|
||||
break;
|
||||
case 22:
|
||||
writeCharacteristic((uint8_t *)res22, sizeof(res22), QStringLiteral("resistance22"), false, true);
|
||||
break;
|
||||
case 23:
|
||||
writeCharacteristic((uint8_t *)res23, sizeof(res23), QStringLiteral("resistance23"), false, true);
|
||||
break;
|
||||
case 24:
|
||||
writeCharacteristic((uint8_t *)res24, sizeof(res24), QStringLiteral("resistance24"), false, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +329,9 @@ void proformrower::update() {
|
||||
update_metrics(true, watts());
|
||||
|
||||
{
|
||||
bool proform_rower_sport_rl =
|
||||
settings.value(QZSettings::proform_rower_sport_rl, QZSettings::default_proform_rower_sport_rl).toBool();
|
||||
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x14, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x1c, 0x9e, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
|
||||
@@ -217,8 +354,16 @@ void proformrower::update() {
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
|
||||
break;
|
||||
case 3:
|
||||
if (requestResistance != -1 && proform_rower_sport_rl) {
|
||||
if (requestResistance != currentResistance().value() && requestResistance >= 0 &&
|
||||
requestResistance <= max_resistance) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
forceResistance(requestResistance);
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), true);
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance != -1 && !proform_rower_sport_rl) {
|
||||
if (requestResistance != currentResistance().value() && requestResistance >= 0 &&
|
||||
requestResistance <= max_resistance) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
@@ -284,12 +429,14 @@ double proformrower::GetResistanceFromPacket(QByteArray packet) {
|
||||
case 6:
|
||||
return 4;
|
||||
case 7:
|
||||
case 8:
|
||||
return 5;
|
||||
case 9:
|
||||
return 6;
|
||||
case 0x0b:
|
||||
return 7;
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
return 8;
|
||||
case 0x0e:
|
||||
return 9;
|
||||
@@ -300,12 +447,14 @@ double proformrower::GetResistanceFromPacket(QByteArray packet) {
|
||||
case 0x13:
|
||||
return 12;
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
return 13;
|
||||
case 0x16:
|
||||
return 14;
|
||||
case 0x18:
|
||||
return 15;
|
||||
case 0x19:
|
||||
case 0x1a:
|
||||
return 16;
|
||||
case 0x1b:
|
||||
return 17;
|
||||
@@ -322,6 +471,7 @@ double proformrower::GetResistanceFromPacket(QByteArray packet) {
|
||||
case 0x25:
|
||||
return 23;
|
||||
case 0x26:
|
||||
case 0x27:
|
||||
return 24;
|
||||
}
|
||||
return 1;
|
||||
@@ -398,6 +548,9 @@ void proformrower::characteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
}
|
||||
|
||||
void proformrower::btinit() {
|
||||
QSettings settings;
|
||||
bool proform_rower_sport_rl =
|
||||
settings.value(QZSettings::proform_rower_sport_rl, QZSettings::default_proform_rower_sport_rl).toBool();
|
||||
|
||||
{
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
@@ -415,6 +568,7 @@ void proformrower::btinit() {
|
||||
uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
|
||||
|
||||
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x14, 0x28, 0x90, 0x07,
|
||||
0x01, 0xed, 0xe8, 0xe9, 0xe8, 0xf5, 0xf0, 0x09, 0x00, 0x1d};
|
||||
uint8_t initData11[] = {0x01, 0x12, 0x28, 0x39, 0x48, 0x55, 0x60, 0x99, 0xb0, 0xad,
|
||||
@@ -438,6 +592,13 @@ void proformrower::btinit() {
|
||||
uint8_t noOpData9[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x13, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
uint8_t initData10_proform_rower_sport_rl[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x14, 0x28, 0x90, 0x07, 0x01, 0xf2, 0xf4, 0xf4, 0xf2, 0xfe, 0x08, 0x00, 0x1e, 0x2a};
|
||||
uint8_t initData11_proform_rower_sport_rl[] = {0x01, 0x12, 0x3c, 0x4c, 0x5a, 0x66, 0x90, 0x88, 0xa6, 0xc2, 0xe4, 0x04, 0x22, 0x4e, 0x98, 0xb0, 0xce, 0x1a, 0x2c, 0x7c};
|
||||
uint8_t initData12_proform_rower_sport_rl[] = {0xff, 0x08, 0x8a, 0xd6, 0x20, 0x98, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
uint8_t noOpData7_proform_rower_sport_rl[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x14, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData8_proform_rower_sport_rl[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
|
||||
@@ -464,26 +625,53 @@ void proformrower::btinit() {
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
if (proform_rower_sport_rl) {
|
||||
writeCharacteristic(initData10_proform_rower_sport_rl, sizeof(initData10_proform_rower_sport_rl), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData11_proform_rower_sport_rl, sizeof(initData11_proform_rower_sport_rl), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12_proform_rower_sport_rl, sizeof(initData12_proform_rower_sport_rl), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData7_proform_rower_sport_rl, sizeof(noOpData7_proform_rower_sport_rl), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData8_proform_rower_sport_rl, sizeof(noOpData8_proform_rower_sport_rl), QStringLiteral("init"), false, true);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
} else {
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
}
|
||||
/*writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
|
||||
|
||||
@@ -72,6 +72,8 @@ void proformtreadmill::forceIncline(double incline) {
|
||||
settings.value(QZSettings::proform_treadmill_se, QZSettings::default_proform_treadmill_se).toBool();
|
||||
bool norditrack_s25i_treadmill =
|
||||
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool();
|
||||
bool proform_treadmill_z1300i =
|
||||
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
|
||||
|
||||
if (proform_treadmill_1800i) {
|
||||
uint8_t i = abs(incline * 10);
|
||||
@@ -92,7 +94,7 @@ void proformtreadmill::forceIncline(double incline) {
|
||||
|
||||
if (norditrack_s25i_treadmill) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se) {
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i) {
|
||||
write[14] = write[11] + write[12] + 0x12;
|
||||
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_t65s_83_treadmill) {
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
@@ -127,6 +129,8 @@ void proformtreadmill::forceSpeed(double speed) {
|
||||
.toBool();
|
||||
bool norditrack_s25i_treadmill =
|
||||
settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool();
|
||||
bool proform_treadmill_z1300i =
|
||||
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
|
||||
|
||||
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
|
||||
uint8_t write[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x04, 0x09, 0x02, 0x01,
|
||||
@@ -137,7 +141,8 @@ void proformtreadmill::forceSpeed(double speed) {
|
||||
|
||||
if (norditrack_s25i_treadmill) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_cadence_lt) {
|
||||
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_cadence_lt ||
|
||||
proform_treadmill_z1300i) {
|
||||
write[14] = write[11] + write[12] + 0x11;
|
||||
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_t65s_83_treadmill) {
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
@@ -198,6 +203,8 @@ void proformtreadmill::update() {
|
||||
settings
|
||||
.value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i)
|
||||
.toBool();
|
||||
bool proform_treadmill_z1300i =
|
||||
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
|
||||
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
|
||||
// QZSettings::default_proform_treadmill_995i).toBool();
|
||||
|
||||
@@ -240,7 +247,7 @@ void proformtreadmill::update() {
|
||||
counterPoll = 0;
|
||||
}
|
||||
} else*/
|
||||
if (proform_treadmill_9_0) {
|
||||
if (proform_treadmill_9_0 || proform_treadmill_z1300i) {
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x92, 0x1a, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
@@ -265,14 +272,15 @@ void proformtreadmill::update() {
|
||||
if (requestInclination < 0)
|
||||
requestInclination = 0;
|
||||
if (requestInclination != currentInclination().value() && requestInclination >= -3 &&
|
||||
requestInclination <= 15) {
|
||||
requestInclination <= (proform_treadmill_z1300i ? 12 : 15)) {
|
||||
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
|
||||
forceIncline(requestInclination);
|
||||
}
|
||||
requestInclination = -100;
|
||||
}
|
||||
if (requestSpeed != -1) {
|
||||
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
|
||||
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 &&
|
||||
requestSpeed <= (proform_treadmill_z1300i ? 19.3 : 22)) {
|
||||
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
|
||||
forceSpeed(requestSpeed);
|
||||
}
|
||||
@@ -1134,6 +1142,8 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
bool nordictrack_incline_trainer_x7i =
|
||||
settings.value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i)
|
||||
.toBool();
|
||||
bool proform_treadmill_z1300i =
|
||||
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
|
||||
|
||||
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
|
||||
@@ -1143,8 +1153,8 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
|
||||
if (newValue.length() != 20 || newValue.at(0) != 0x00 || newValue.at(1) != 0x12 || newValue.at(2) != 0x01 ||
|
||||
newValue.at(3) != 0x04 ||
|
||||
((nordictrack10 || nordictrackt70 || proform_treadmill_1800i || proform_treadmill_8_0 ||
|
||||
proform_treadmill_9_0 || nordictrack_incline_trainer_x7i) &&
|
||||
((nordictrack10 || nordictrackt70 || proform_treadmill_1800i || proform_treadmill_z1300i ||
|
||||
proform_treadmill_8_0 || proform_treadmill_9_0 || nordictrack_incline_trainer_x7i) &&
|
||||
(newValue.at(4) != 0x02 || (newValue.at(5) != 0x31 && newValue.at(5) != 0x34))) ||
|
||||
((norditrack_s25i_treadmill) && (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2f))) ||
|
||||
((nordictrack_t65s_treadmill || nordictrack_t65s_83_treadmill || nordictrack_s30_treadmill ||
|
||||
@@ -1235,6 +1245,8 @@ void proformtreadmill::btinit() {
|
||||
bool nordictrack_incline_trainer_x7i =
|
||||
settings.value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i)
|
||||
.toBool();
|
||||
bool proform_treadmill_z1300i =
|
||||
settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool();
|
||||
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
|
||||
// QZSettings::default_proform_treadmill_995i).toBool();
|
||||
|
||||
@@ -1348,6 +1360,83 @@ void proformtreadmill::btinit() {
|
||||
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
} else if (proform_treadmill_z1300i) {
|
||||
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
|
||||
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
|
||||
uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
|
||||
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
|
||||
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
|
||||
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x04,
|
||||
0x00, 0xb0, 0x4c, 0xda, 0x7e, 0x14, 0xb8, 0x46, 0xfa, 0x98};
|
||||
uint8_t initData11[] = {0x01, 0x12, 0x34, 0xd2, 0x86, 0x3c, 0xf0, 0x9e, 0x52, 0xe0,
|
||||
0xbc, 0x4a, 0x0e, 0xc4, 0x88, 0x56, 0x0a, 0xc8, 0x84, 0x42};
|
||||
uint8_t initData12[] = {0xff, 0x08, 0x36, 0xec, 0xe0, 0x80, 0x02, 0x00, 0x00, 0x12,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
|
||||
0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
|
||||
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
|
||||
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t noOpData6[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
|
||||
QThread::msleep(400);
|
||||
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
|
||||
|
||||
@@ -458,17 +458,20 @@ void proformwifibike::characteristicChanged(const QString &newValue) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!values[QStringLiteral("Current Watts")].isUndefined()) {
|
||||
double watt = values[QStringLiteral("Current Watts")].toString().toDouble();
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")))
|
||||
// some buggy TDF1 bikes send spurious wattage at the end with cadence = 0
|
||||
if (Cadence.value() > 0) {
|
||||
if (!values[QStringLiteral("Current Watts")].isUndefined()) {
|
||||
double watt = values[QStringLiteral("Current Watts")].toString().toDouble();
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")))
|
||||
m_watt = watt;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
} else if (!values[QStringLiteral("Watt attuali")].isUndefined()) {
|
||||
double watt = values[QStringLiteral("Watt attuali")].toString().toDouble();
|
||||
m_watt = watt;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
} else if (!values[QStringLiteral("Watt attuali")].isUndefined()) {
|
||||
double watt = values[QStringLiteral("Watt attuali")].toString().toDouble();
|
||||
m_watt = watt;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!values[QStringLiteral("Actual Incline")].isUndefined()) {
|
||||
|
||||
@@ -720,6 +720,8 @@ DISTFILES += \
|
||||
$$PWD/android/src/MediaProjection.java \
|
||||
$$PWD/android/src/NotificationUtils.java \
|
||||
$$PWD/android/src/ScreenCaptureService.java \
|
||||
$$PWD/android/src/WearableController.java \
|
||||
$$PWD/android/src/WearableMessageListenerService.java \
|
||||
.clang-format \
|
||||
AppxManifest.xml \
|
||||
android/AndroidManifest.xml \
|
||||
@@ -743,6 +745,7 @@ DISTFILES += \
|
||||
android/src/MyActivity.java \
|
||||
android/src/PowerChannelController.java \
|
||||
android/src/SpeedChannelController.java \
|
||||
android/src/SDMChannelController.java \
|
||||
android/src/Usbserial.java \
|
||||
android/src/com/cgutman/adblib/AdbBase64.java \
|
||||
android/src/com/cgutman/adblib/AdbConnection.java \
|
||||
@@ -812,4 +815,4 @@ INCLUDEPATH += purchasing/inapp
|
||||
|
||||
WINRT_MANIFEST = AppxManifest.xml
|
||||
|
||||
VERSION = 2.13.98
|
||||
VERSION = 2.16.15
|
||||
|
||||
@@ -102,5 +102,10 @@
|
||||
<file>inner_templates/chartjs/bike.png</file>
|
||||
<file>inner_templates/chartjs/html2canvas.min.js</file>
|
||||
<file>inner_templates/chartjs/qzlogo.png</file>
|
||||
<file>inner_templates/chartjs/dochartlive.js</file>
|
||||
<file>inner_templates/chartjs/chartlive.htm</file>
|
||||
<file>ChartFooter.qml</file>
|
||||
<file>ChartFooterInnerJS.qml</file>
|
||||
<file>ChartFooterInnerNoJS.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -664,8 +664,19 @@ const QString QZSettings::fakedevice_rower = QStringLiteral("fakedevice_rower");
|
||||
const QString QZSettings::zwift_ocr_climb_portal = QStringLiteral("zwift_ocr_climb_portal");
|
||||
const QString QZSettings::poll_device_time = QStringLiteral("poll_device_time");
|
||||
const QString QZSettings::proform_bike_PFEVEX71316_1 = QStringLiteral("proform_bike_PFEVEX71316_1");
|
||||
const QString QZSettings::schwinn_bike_resistance_v3 = QStringLiteral("schwinn_bike_resistance_v3");
|
||||
const QString QZSettings::watt_ignore_builtin = QStringLiteral("watt_ignore_builtin");
|
||||
const QString QZSettings::proform_treadmill_z1300i = QStringLiteral("proform_treadmill_z1300i");
|
||||
const QString QZSettings::ftms_bike = QStringLiteral("ftms_bike");
|
||||
const QString QZSettings::default_ftms_bike = QStringLiteral("Disabled");
|
||||
const QString QZSettings::ftms_treadmill = QStringLiteral("ftms_treadmill");
|
||||
const QString QZSettings::default_ftms_treadmill = QStringLiteral("Disabled");
|
||||
const QString QZSettings::ant_speed_offset = QStringLiteral("ant_speed_offset");
|
||||
const QString QZSettings::ant_speed_gain = QStringLiteral("ant_speed_gain");
|
||||
const QString QZSettings::proform_rower_sport_rl = QStringLiteral("proform_rower_sport_rl");
|
||||
const QString QZSettings::strava_date_prefix = QStringLiteral("strava_date_prefix");
|
||||
|
||||
const uint32_t allSettingsCount = 556;
|
||||
const uint32_t allSettingsCount = 565;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1228,6 +1239,15 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::zwift_ocr_climb_portal, QZSettings::default_zwift_ocr_climb_portal},
|
||||
{QZSettings::poll_device_time, QZSettings::default_poll_device_time},
|
||||
{QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1},
|
||||
{QZSettings::schwinn_bike_resistance_v3, QZSettings::default_schwinn_bike_resistance_v3},
|
||||
{QZSettings::watt_ignore_builtin, QZSettings::default_watt_ignore_builtin},
|
||||
{QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i},
|
||||
{QZSettings::ftms_bike, QZSettings::default_ftms_bike},
|
||||
{QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill},
|
||||
{QZSettings::ant_speed_offset, QZSettings::default_ant_speed_offset},
|
||||
{QZSettings::ant_speed_gain, QZSettings::default_ant_speed_gain},
|
||||
{QZSettings::proform_rower_sport_rl, QZSettings::default_proform_rower_sport_rl},
|
||||
{QZSettings::strava_date_prefix, QZSettings::default_strava_date_prefix},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -1863,6 +1863,39 @@ class QZSettings {
|
||||
static const QString proform_bike_PFEVEX71316_1;
|
||||
static constexpr bool default_proform_bike_PFEVEX71316_1 = false;
|
||||
|
||||
static const QString schwinn_bike_resistance_v3;
|
||||
static constexpr bool default_schwinn_bike_resistance_v3 = false;
|
||||
|
||||
static const QString watt_ignore_builtin;
|
||||
static constexpr bool default_watt_ignore_builtin = true;
|
||||
|
||||
static const QString proform_treadmill_z1300i;
|
||||
static constexpr bool default_proform_treadmill_z1300i = false;
|
||||
|
||||
static const QString ftms_bike;
|
||||
static const QString default_ftms_bike;
|
||||
|
||||
static const QString ftms_treadmill;
|
||||
static const QString default_ftms_treadmill;
|
||||
|
||||
static const QString proform_rower_sport_rl;
|
||||
static constexpr bool default_proform_rower_sport_rl = false;
|
||||
|
||||
static const QString strava_date_prefix;
|
||||
static constexpr bool default_strava_date_prefix = false;
|
||||
|
||||
/**
|
||||
* @brief Adjusts value in a metric object that's configured specifically for measuring SPEED on ANT+.
|
||||
*/
|
||||
static const QString ant_speed_offset;
|
||||
static constexpr float default_ant_speed_offset = 0;
|
||||
|
||||
/**
|
||||
* @brief Adjusts value in a metric object that's configured specifically for measuring SPEED on ANT+.
|
||||
*/
|
||||
static const QString ant_speed_gain;
|
||||
static constexpr float default_ant_speed_gain = 1;
|
||||
|
||||
/**
|
||||
* @brief Write the QSettings values using the constants from this namespace.
|
||||
* @param showDefaults Optionally indicates if the default should be shown with the key.
|
||||
|
||||
@@ -320,10 +320,19 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
if (isnan(res)) {
|
||||
res = 0;
|
||||
}
|
||||
if (settings.value(QZSettings::schwinn_bike_resistance, QZSettings::default_schwinn_bike_resistance).toBool())
|
||||
|
||||
bool schwinn_bike_resistance_v2 =
|
||||
settings.value(QZSettings::schwinn_bike_resistance_v2, QZSettings::default_schwinn_bike_resistance_v2).toBool();
|
||||
bool schwinn_bike_resistance_v3 =
|
||||
settings.value(QZSettings::schwinn_bike_resistance_v3, QZSettings::default_schwinn_bike_resistance_v3).toBool();
|
||||
|
||||
if (settings.value(QZSettings::schwinn_bike_resistance, QZSettings::default_schwinn_bike_resistance).toBool() || schwinn_bike_resistance_v2 ||
|
||||
schwinn_bike_resistance_v3) {
|
||||
resistance = pelotonToBikeResistance(res);
|
||||
else
|
||||
} else {
|
||||
resistance = res;
|
||||
}
|
||||
|
||||
if (qFabs(resistance - Resistance.value()) >=
|
||||
(double)settings.value(QZSettings::schwinn_resistance_smooth, QZSettings::default_schwinn_resistance_smooth)
|
||||
.toInt()) {
|
||||
@@ -361,6 +370,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
#endif
|
||||
#endif
|
||||
|
||||
emit debug(QStringLiteral("Current Peloton Resistance: ") + QString::number(m_pelotonResistance.value()));
|
||||
emit debug(QStringLiteral("Current Calculated Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
|
||||
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
|
||||
@@ -555,7 +565,15 @@ resistance_t schwinnic4bike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
QSettings settings;
|
||||
bool schwinn_bike_resistance_v2 =
|
||||
settings.value(QZSettings::schwinn_bike_resistance_v2, QZSettings::default_schwinn_bike_resistance_v2).toBool();
|
||||
if (!schwinn_bike_resistance_v2) {
|
||||
bool schwinn_bike_resistance_v3 =
|
||||
settings.value(QZSettings::schwinn_bike_resistance_v3, QZSettings::default_schwinn_bike_resistance_v3).toBool();
|
||||
if (schwinn_bike_resistance_v3) {
|
||||
// y = -35,3 + 1,91x + -0,0358x^2 + 4,3E-04x^3
|
||||
if (pelotonResistance < 30)
|
||||
return 0;
|
||||
|
||||
return -35.3 + 1.91 * pelotonResistance - 0.0358 * pow(pelotonResistance, 2) + 4.3E-04 * pow(pelotonResistance, 3);
|
||||
} else if (!schwinn_bike_resistance_v2) {
|
||||
if (pelotonResistance > 54)
|
||||
return pelotonResistance;
|
||||
if (pelotonResistance < 26)
|
||||
|
||||
277
src/settings.qml
277
src/settings.qml
@@ -810,6 +810,27 @@ import QtQuick.Dialogs 1.0
|
||||
|
||||
// from version 2.13.99
|
||||
property bool proform_bike_PFEVEX71316_1: false
|
||||
property bool schwinn_bike_resistance_v3: false
|
||||
|
||||
// from version 2.15.2
|
||||
property bool watt_ignore_builtin: true
|
||||
|
||||
// from version 2.16.4
|
||||
property bool proform_treadmill_z1300i: false
|
||||
|
||||
// from version 2.16.5
|
||||
property string ftms_bike: "Disabled"
|
||||
property string ftms_treadmill: "Disabled"
|
||||
|
||||
// from version 2.16.6
|
||||
property real ant_speed_offset: 0
|
||||
property real ant_speed_gain: 1
|
||||
|
||||
// from version 2.16.12
|
||||
property bool proform_rower_sport_rl: false
|
||||
|
||||
// from version 2.16.13
|
||||
property bool strava_date_prefix: false
|
||||
}
|
||||
|
||||
function paddingZeros(text, limit) {
|
||||
@@ -2175,6 +2196,45 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("FTMS Bike:")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
ComboBox {
|
||||
id: ftmsBikeTextField
|
||||
model: rootItem.bluetoothDevices
|
||||
displayText: settings.ftms_bike
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onActivated: {
|
||||
console.log("combomodel activated" + ftmsBikeTextField.currentIndex)
|
||||
displayText = ftmsBikeTextField.currentValue
|
||||
}
|
||||
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: { settings.ftms_bike = ftmsBikeTextField.displayText; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("If you have a generic FTMS bike and the tiles doesn't appear on the main QZ screen, select here the bluetooth name of your bike.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -2214,7 +2274,7 @@ import QtQuick.Dialogs 1.0
|
||||
}
|
||||
SwitchDelegate {
|
||||
id: schwinnBikeResistanceV2Delegate
|
||||
text: qsTr("Resistance Alternative Calc.")
|
||||
text: qsTr("Res. Alternative Calc. v2")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
@@ -2226,6 +2286,19 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.schwinn_bike_resistance_v2 = checked
|
||||
}
|
||||
SwitchDelegate {
|
||||
text: qsTr("Res. Alternative Calc. v3")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.schwinn_bike_resistance_v3
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.schwinn_bike_resistance_v3 = checked
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
@@ -3119,6 +3192,79 @@ import QtQuick.Dialogs 1.0
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
text: qsTr("ANT+ Speed Offset")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
id: antspeedOffsetTextField
|
||||
text: settings.ant_speed_offset
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
onAccepted: settings.ant_speed_offset = text
|
||||
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: { settings.ant_speed_offset = antspeedOffsetTextField.text; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("You can increase/decrease your speed sent over ANT+. The number you enter as an Offset adds that amount to your speed.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
text: qsTr("ANT+ Speed Gain:")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
id: antspeedGainTextField
|
||||
text: settings.ant_speed_gain
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
//inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
onAccepted: settings.ant_speed_gain = text
|
||||
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: { settings.ant_speed_gain = antspeedGainTextField.text; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("You can increase/decrease your speed output sent over ANT+. For example, to use a rower to cycle in Zwift, you could double your speed output to better match your cycling speed. The number you enter is a multiplier applied to your actual speed.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: antHeartDelegate
|
||||
text: qsTr("Ant+ Heart")
|
||||
@@ -3687,7 +3833,7 @@ import QtQuick.Dialogs 1.0
|
||||
}
|
||||
ComboBox {
|
||||
id: pelotonRowerLevelTextField
|
||||
model: [ "1", "2", "3", "4", "5", "6" ]
|
||||
model: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ]
|
||||
displayText: settings.peloton_rower_level
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
@@ -5093,6 +5239,45 @@ import QtQuick.Dialogs 1.0
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("FTMS Treadmill:")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
ComboBox {
|
||||
id: ftmsTreadmillTextField
|
||||
model: rootItem.bluetoothDevices
|
||||
displayText: settings.ftms_treadmill
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onActivated: {
|
||||
console.log("combomodel activated" + ftmsTreadmillTextField.currentIndex)
|
||||
displayText = ftmsTreadmillTextField.currentValue
|
||||
}
|
||||
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: { settings.ftms_treadmill = ftmsTreadmillTextField.displayText; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("If you have a generic FTMS bike and the tiles doesn't appear on the main QZ screen, select here the bluetooth name of your bike.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Expand the bars to the right to display the options under this setting. Select your specific model (if it is listed) and leave all other settings on default. If you encounter problems or have a question about settings for your specific equipment with QZ, click here to open a support ticket on GitHub or ask the QZ community on the QZ Facebook Group.")
|
||||
font.bold: true
|
||||
@@ -5227,6 +5412,19 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
onClicked: { settings.proform_treadmill_1800i = checked; window.settings_restart_to_apply = true; }
|
||||
}
|
||||
SwitchDelegate {
|
||||
text: qsTr("Proform z1300i")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.proform_treadmill_z1300i
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: { settings.proform_treadmill_z1300i = checked; window.settings_restart_to_apply = true; }
|
||||
}
|
||||
SwitchDelegate {
|
||||
id: proformSEDelegate
|
||||
text: qsTr("Proform SE")
|
||||
@@ -6439,6 +6637,27 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
AccordionElement {
|
||||
title: qsTr("Proform/Nordictrack Rower Options")
|
||||
indicatRectColor: Material.color(Material.Grey)
|
||||
textColor: Material.color(Material.Yellow)
|
||||
color: Material.backgroundColor
|
||||
accordionContent:
|
||||
SwitchDelegate {
|
||||
text: qsTr("Proform Sport RL")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.proform_rower_sport_rl
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: { settings.proform_rower_sport_rl = checked; window.settings_restart_to_apply = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6994,6 +7213,33 @@ import QtQuick.Dialogs 1.0
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
text: qsTr("Date Prefix on Strava Workout")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.strava_date_prefix
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.strava_date_prefix = checked
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Append the Date to the Strava Activity as a prefix only for non-peloton workout")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: volumeChangeGearsDelegate
|
||||
text: qsTr("Volumes buttons change gears")
|
||||
@@ -7181,6 +7427,33 @@ import QtQuick.Dialogs 1.0
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
text: qsTr("Disable Wattage from Machinery")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.watt_ignore_builtin
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.watt_ignore_builtin = checked
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("This prevents your fitness device from sending its wattage calculation to QZ and defaults to QZ’s more accurate calculation.")
|
||||
font.bold: true
|
||||
font.italic: true
|
||||
font.pixelSize: 9
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WordWrap
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
color: Material.color(Material.Lime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +133,12 @@ void soleelliptical::update() {
|
||||
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
|
||||
gattNotifyCharacteristic.isValid() && initDone) {
|
||||
|
||||
update_metrics(true, watts());
|
||||
|
||||
QSettings settings;
|
||||
bool watt_ignore_builtin =
|
||||
settings.value(QZSettings::watt_ignore_builtin, QZSettings::default_watt_ignore_builtin).toBool();
|
||||
|
||||
update_metrics(watt_ignore_builtin, watts());
|
||||
|
||||
bool sole_elliptical_inclination =
|
||||
settings.value(QZSettings::sole_elliptical_inclination, QZSettings::default_sole_elliptical_inclination)
|
||||
.toBool();
|
||||
@@ -339,16 +342,19 @@ void soleelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
// double distance = GetDistanceFromPacket(newValue) *
|
||||
// settings.value(QZSettings::domyos_elliptical_speed_ratio,
|
||||
// QZSettings::default_domyos_elliptical_speed_ratio).toDouble();
|
||||
uint16_t watt = (newValue.at(13) << 8) | newValue.at(14);
|
||||
uint16_t watt = ((uint16_t)((uint8_t)newValue.at(13)) << 8) | (uint16_t)((uint8_t)newValue.at(14));
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
bool watt_ignore_builtin =
|
||||
settings.value(QZSettings::watt_ignore_builtin, QZSettings::default_watt_ignore_builtin).toBool();
|
||||
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = ((uint8_t)newValue.at(10));
|
||||
}
|
||||
// m_watt = watt;
|
||||
if(!watt_ignore_builtin)
|
||||
m_watt = watt;
|
||||
|
||||
if (Resistance.value() < 1) {
|
||||
emit debug(QStringLiteral("invalid resistance value ") + QString::number(Resistance.value()) +
|
||||
@@ -364,7 +370,7 @@ void soleelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
|
||||
{
|
||||
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) && !disable_hr_frommachinery) {
|
||||
Heart = ((uint8_t)newValue.at(18));
|
||||
} else {
|
||||
} else if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,40 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
#define TRAINPROGRAM_FIELD_TO_STRING() \
|
||||
item[QStringLiteral("duration")] = row.duration.toString(); \
|
||||
item[QStringLiteral("duration_s")] = QTime(0,0,0).secsTo(row.duration); \
|
||||
item[QStringLiteral("distance")] = row.distance; \
|
||||
item[QStringLiteral("speed")] = row.speed; \
|
||||
item[QStringLiteral("minspeed")] = row.minSpeed; \
|
||||
item[QStringLiteral("maxspeed")] = row.maxSpeed; \
|
||||
item[QStringLiteral("fanspeed")] = row.fanspeed; \
|
||||
item[QStringLiteral("inclination")] = row.inclination; \
|
||||
item[QStringLiteral("resistance")] = row.resistance; \
|
||||
item[QStringLiteral("maxresistance")] = row.maxResistance; \
|
||||
item[QStringLiteral("mets")] = row.mets; \
|
||||
item[QStringLiteral("pace_intensity")] = row.pace_intensity; \
|
||||
item[QStringLiteral("lower_resistance")] = row.lower_resistance; \
|
||||
item[QStringLiteral("upper_resistance")] = row.upper_resistance; \
|
||||
item[QStringLiteral("requested_peloton_resistance")] = row.requested_peloton_resistance; \
|
||||
item[QStringLiteral("lower_requested_peloton_resistance")] = row.lower_requested_peloton_resistance; \
|
||||
item[QStringLiteral("upper_requested_peloton_resistance")] = row.upper_requested_peloton_resistance; \
|
||||
item[QStringLiteral("power")] = row.power; \
|
||||
item[QStringLiteral("cadence")] = row.cadence; \
|
||||
item[QStringLiteral("lower_cadence")] = row.lower_cadence; \
|
||||
item[QStringLiteral("upper_cadence")] = row.upper_cadence; \
|
||||
item[QStringLiteral("forcespeed")] = row.forcespeed; \
|
||||
item[QStringLiteral("loopTimeHR")] = row.loopTimeHR; \
|
||||
item[QStringLiteral("zoneHR")] = row.zoneHR; \
|
||||
item[QStringLiteral("HRmin")] = row.HRmin; \
|
||||
item[QStringLiteral("HRmax")] = row.HRmax; \
|
||||
item[QStringLiteral("maxSpeed")] = row.maxSpeed; \
|
||||
item[QStringLiteral("latitude")] = row.latitude; \
|
||||
item[QStringLiteral("longitude")] = row.longitude; \
|
||||
item[QStringLiteral("altitude")] = row.altitude; \
|
||||
item[QStringLiteral("azimuth")] = row.azimuth;
|
||||
|
||||
|
||||
QHash<QString, TemplateInfoSenderBuilder *> TemplateInfoSenderBuilder::instanceMap;
|
||||
TemplateInfoSenderBuilder::TemplateInfoSenderBuilder(QObject *parent) : QObject(parent) {
|
||||
engine = new QJSEngine(this);
|
||||
@@ -428,21 +462,7 @@ void TemplateInfoSenderBuilder::onLoadTrainingPrograms(const QJsonValue &msgCont
|
||||
fileXml + QStringLiteral(".xml"));
|
||||
for (auto &row : lst) {
|
||||
QJsonObject item;
|
||||
item[QStringLiteral("duration")] = row.duration.toString();
|
||||
item[QStringLiteral("speed")] = row.speed;
|
||||
item[QStringLiteral("fanspeed")] = row.fanspeed;
|
||||
item[QStringLiteral("inclination")] = row.inclination;
|
||||
item[QStringLiteral("resistance")] = row.resistance;
|
||||
item[QStringLiteral("requested_peloton_resistance")] = row.requested_peloton_resistance;
|
||||
item[QStringLiteral("cadence")] = row.cadence;
|
||||
item[QStringLiteral("forcespeed")] = row.forcespeed;
|
||||
item[QStringLiteral("loopTimeHR")] = row.loopTimeHR;
|
||||
item[QStringLiteral("zoneHR")] = row.zoneHR;
|
||||
item[QStringLiteral("HRmin")] = row.HRmin;
|
||||
item[QStringLiteral("HRmax")] = row.HRmax;
|
||||
item[QStringLiteral("maxSpeed")] = row.maxSpeed;
|
||||
item[QStringLiteral("latitude")] = row.latitude;
|
||||
item[QStringLiteral("longitude")] = row.longitude;
|
||||
TRAINPROGRAM_FIELD_TO_STRING();
|
||||
outArr.append(item);
|
||||
}
|
||||
}
|
||||
@@ -454,6 +474,27 @@ void TemplateInfoSenderBuilder::onLoadTrainingPrograms(const QJsonValue &msgCont
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onGetTrainingProgram(const QJsonValue &msgContent, TemplateInfoSender *tempSender) {
|
||||
QJsonObject main;
|
||||
QJsonArray outArr;
|
||||
QJsonObject outObj;
|
||||
QString fileXml;
|
||||
if (homeform::singleton() && homeform::singleton()->trainingProgram()) {
|
||||
QList<trainrow> lst = homeform::singleton()->trainingProgram()->loadedRows;
|
||||
for (auto &row : lst) {
|
||||
QJsonObject item;
|
||||
TRAINPROGRAM_FIELD_TO_STRING();
|
||||
outArr.append(item);
|
||||
}
|
||||
}
|
||||
outObj[QStringLiteral("list")] = outArr;
|
||||
outObj[QStringLiteral("name")] = fileXml;
|
||||
main[QStringLiteral("content")] = outObj;
|
||||
main[QStringLiteral("msg")] = QStringLiteral("R_gettrainingprogram");
|
||||
QJsonDocument out(main);
|
||||
tempSender->send(out.toJson());
|
||||
}
|
||||
|
||||
void TemplateInfoSenderBuilder::onAppendActivityDescription(const QJsonValue &msgContent,
|
||||
TemplateInfoSender *tempSender) {
|
||||
QJsonObject content;
|
||||
@@ -793,6 +834,9 @@ void TemplateInfoSenderBuilder::onDataReceived(const QByteArray &data) {
|
||||
} else if (msg == QStringLiteral("loadtrainingprograms")) {
|
||||
onLoadTrainingPrograms(jsonObject[QStringLiteral("content")], sender);
|
||||
return;
|
||||
} else if (msg == QStringLiteral("gettrainingprogram")) {
|
||||
onGetTrainingProgram(jsonObject[QStringLiteral("content")], sender);
|
||||
return;
|
||||
} else if (msg == QStringLiteral("appendactivitydescription")) {
|
||||
onAppendActivityDescription(jsonObject[QStringLiteral("content")], sender);
|
||||
return;
|
||||
|
||||
@@ -79,6 +79,7 @@ class TemplateInfoSenderBuilder : public QObject {
|
||||
void onAutoresistance(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onSaveTrainingProgram(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onLoadTrainingPrograms(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onGetTrainingProgram(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onAppendActivityDescription(const QJsonValue &msgContent, TemplateInfoSender *tempSender);
|
||||
void onGetSessionArray(TemplateInfoSender *tempSender);
|
||||
void onGetLatLon(TemplateInfoSender *tempSender);
|
||||
|
||||
@@ -84,6 +84,12 @@ class trainprogram : public QObject {
|
||||
double weightedInclination(int step);
|
||||
double medianInclination(int step);
|
||||
bool overridePowerForCurrentRow(double power);
|
||||
bool powerzoneWorkout() {
|
||||
foreach(trainrow r, rows) {
|
||||
if(r.power != -1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<trainrow> rows;
|
||||
QList<trainrow> loadedRows; // rows as loaded
|
||||
|
||||
@@ -488,7 +488,7 @@ void trxappgateusbbike::btinit(bool startTape) {
|
||||
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic((uint8_t *)initData5, sizeof(initData5), QStringLiteral("init"), false, true);
|
||||
writeCharacteristic((uint8_t *)initData6, sizeof(initData6), QStringLiteral("init"), false, true);
|
||||
} else if (bike_type == TYPE::HERTZ_XR_770 || bike_type == TYPE::HERTZ_XR_770_2) {
|
||||
} else if (bike_type == TYPE::HERTZ_XR_770 || bike_type == TYPE::HERTZ_XR_770_2 || bike_type == TYPE::FITHIWAY) {
|
||||
const uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x01, 0x92};
|
||||
const uint8_t initData2[] = {0xf0, 0xa0, 0x02, 0x01, 0x93};
|
||||
const uint8_t initData3[] = {0xf0, 0xa3, 0x01, 0x01, 0x01, 0x96};
|
||||
@@ -728,7 +728,8 @@ void trxappgateusbbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
|
||||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::DKN_MOTION_2 || bike_type == TYPE::FYTTER_RI08 ||
|
||||
bike_type == TYPE::HERTZ_XR_770_2 || bike_type == TYPE::VIRTUFIT_2 || bike_type == TYPE::TUNTURI) {
|
||||
bike_type == TYPE::HERTZ_XR_770_2 || bike_type == TYPE::VIRTUFIT_2 || bike_type == TYPE::TUNTURI ||
|
||||
bike_type == TYPE::FITHIWAY) {
|
||||
uuidWrite = QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3");
|
||||
uuidNotify1 = QStringLiteral("49535343-1E4D-4BD9-BA61-23C647249616");
|
||||
uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
|
||||
@@ -815,7 +816,8 @@ void trxappgateusbbike::serviceScanDone(void) {
|
||||
QString uuid2 = QStringLiteral("49535343-FE7D-4AE5-8FA9-9FAFD205E455");
|
||||
QString uuid3 = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
|
||||
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
|
||||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::FYTTER_RI08 || bike_type == TYPE::TUNTURI) {
|
||||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::FYTTER_RI08 || bike_type == TYPE::TUNTURI ||
|
||||
bike_type == TYPE::FITHIWAY) {
|
||||
uuid = uuid2;
|
||||
}
|
||||
|
||||
@@ -982,6 +984,9 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
|
||||
bike_type = TYPE::DKN_MOTION;
|
||||
qDebug() << QStringLiteral("DKN MOTION bike found");
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("FITHIWAY"))) {
|
||||
bike_type = TYPE::FITHIWAY;
|
||||
qDebug() << QStringLiteral("FITHIWAY bike found");
|
||||
}
|
||||
|
||||
bluetoothDevice = device;
|
||||
|
||||
@@ -100,6 +100,7 @@ class trxappgateusbbike : public bike {
|
||||
VIRTUFIT_2 = 15,
|
||||
TUNTURI = 16,
|
||||
TUNTURI_2 = 17,
|
||||
FITHIWAY = 18,
|
||||
} TYPE;
|
||||
TYPE bike_type = TRXAPPGATE;
|
||||
|
||||
|
||||
@@ -41,7 +41,12 @@ void trxappgateusbtreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
if (gattWriteCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
|
||||
|
||||
@@ -234,7 +234,10 @@ void wahookickrsnapbike::update() {
|
||||
uint8_t b[20];
|
||||
memcpy(b, a.constData(), a.length());
|
||||
writeCharacteristic(b, a.length(), "setResistance", false, true);
|
||||
} else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) {
|
||||
inclinationChanged(lastGrade, lastGrade);
|
||||
}
|
||||
lastGearValue = gears();
|
||||
requestResistance = -1;
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
@@ -789,8 +792,9 @@ void wahookickrsnapbike::controllerStateChanged(QLowEnergyController::Controller
|
||||
|
||||
void wahookickrsnapbike::inclinationChanged(double grade, double percentage) {
|
||||
Q_UNUSED(percentage);
|
||||
lastGrade = grade;
|
||||
emit debug(QStringLiteral("writing inclination ") + QString::number(grade));
|
||||
QByteArray a = setSimGrade(grade);
|
||||
QByteArray a = setSimGrade(grade + gears());
|
||||
uint8_t b[20];
|
||||
memcpy(b, a.constData(), a.length());
|
||||
writeCharacteristic(b, a.length(), "setSimGrade", false, true);
|
||||
|
||||
@@ -83,6 +83,8 @@ class wahookickrsnapbike : public bike {
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QByteArray lastPacket;
|
||||
double lastGearValue = -1;
|
||||
double lastGrade = 0;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
QDateTime lastGoodCadence = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
|
||||
@@ -1,110 +1,107 @@
|
||||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
|
||||
# Author: Al Udell
|
||||
# Revised: April 22, 2023
|
||||
|
||||
# process-image.py - take Zwift screenshot, crop incline, OCR incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Convert screenshot to a numpy array
|
||||
screenshot_np = np.array(screenshot)
|
||||
|
||||
# Crop image to incline area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
|
||||
# Values for Zwift climb portal incline
|
||||
col1 = int(screenwidth/3000 * 2822)
|
||||
row1 = int(screenheight/2000 * 218)
|
||||
col2 = int(screenwidth/3000 * 2980)
|
||||
row2 = int(screenheight/2000 * 302)
|
||||
|
||||
cropped_np = screenshot_np[row1:row2, col1:col2]
|
||||
|
||||
# Convert numpy array to PIL image
|
||||
cropped_pil = Image.fromarray(cropped_np)
|
||||
|
||||
# Convert PIL Image to a cv2 image
|
||||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# Convert cv2 image to HSV
|
||||
result = cropped_cv2.copy()
|
||||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Isolate white mask
|
||||
lower = np.array([0,0,159])
|
||||
upper = np.array([0,0,255])
|
||||
mask0 = cv2.inRange(image, lower, upper)
|
||||
result0 = cv2.bitwise_and(result, result, mask=mask0)
|
||||
|
||||
# Isolate yellow mask
|
||||
lower = np.array([24,239,241])
|
||||
upper = np.array([24,253,255])
|
||||
mask1 = cv2.inRange(image, lower, upper)
|
||||
result1 = cv2.bitwise_and(result, result, mask=mask1)
|
||||
|
||||
# Isolate orange mask
|
||||
lower = np.array([8,191,243])
|
||||
upper = np.array([8,192,243])
|
||||
mask2 = cv2.inRange(image, lower, upper)
|
||||
result2 = cv2.bitwise_and(result, result, mask=mask2)
|
||||
|
||||
# Isolate red mask
|
||||
lower = np.array([0,255,255])
|
||||
upper = np.array([10,255,255])
|
||||
mask3 = cv2.inRange(image, lower, upper)
|
||||
result3 = cv2.bitwise_and(result, result, mask=mask3)
|
||||
|
||||
# Join colour masks
|
||||
mask = mask0+mask1+mask2+mask3
|
||||
|
||||
# Set output image to zero everywhere except mask
|
||||
merge = image.copy()
|
||||
merge[np.where(mask==0)] = 0
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Convert to black/white by threshold
|
||||
ret,bin = cv2.threshold(gray,30,255,cv2.THRESH_BINARY)
|
||||
|
||||
# Closing
|
||||
kernel = np.ones((3,3),np.uint8)
|
||||
closing = cv2.morphologyEx(bin, cv2.MORPH_CLOSE, kernel)
|
||||
|
||||
# Invert black/white
|
||||
inv = cv2.bitwise_not(closing)
|
||||
|
||||
# Apply average blur
|
||||
averageBlur = cv2.blur(inv, (3, 3))
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(averageBlur, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]}"
|
||||
|
||||
# Remove all characters that are not "-" and integers from OCR text
|
||||
pattern = r"[^-\d]+"
|
||||
ocr_text = re.sub(pattern, "", ocr_text)
|
||||
if ocr_text:
|
||||
incline = ocr_text
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(incline)
|
||||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
|
||||
# Author: Al Udell
|
||||
# Revised: August 16, 2023
|
||||
|
||||
# zwift-incline-climb-portal.py - take Zwift screenshot, crop incline, OCR incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Crop image to incline area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
|
||||
# Values for Zwift climb portal incline
|
||||
col1 = int(screenwidth/3000 * 2822)
|
||||
row1 = int(screenheight/2000 * 218)
|
||||
col2 = int(screenwidth/3000 * 2980)
|
||||
row2 = int(screenheight/2000 * 302)
|
||||
|
||||
cropped = screenshot.crop((col1, row1, col2, row2))
|
||||
|
||||
# Scale image to correct size for borderless window mode
|
||||
width, height = cropped.size
|
||||
cropped = cropped.resize((int(width * 1.3), int(height * 1.3)))
|
||||
|
||||
# Convert image to np array
|
||||
cropped_np = np.array(cropped)
|
||||
|
||||
# Convert np array to PIL
|
||||
cropped_pil = Image.fromarray(cropped_np)
|
||||
|
||||
# Convert PIL image to cv2 RGB
|
||||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# Convert cv2 RGB to HSV
|
||||
result = cropped_cv2.copy()
|
||||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Isolate white mask
|
||||
lower = np.array([0,0,159])
|
||||
upper = np.array([0,0,255])
|
||||
mask0 = cv2.inRange(image, lower, upper)
|
||||
result0 = cv2.bitwise_and(result, result, mask=mask0)
|
||||
|
||||
# Isolate yellow mask
|
||||
lower = np.array([24,239,241])
|
||||
upper = np.array([24,253,255])
|
||||
mask1 = cv2.inRange(image, lower, upper)
|
||||
result1 = cv2.bitwise_and(result, result, mask=mask1)
|
||||
|
||||
# Isolate orange mask
|
||||
lower = np.array([8,191,243])
|
||||
upper = np.array([8,192,243])
|
||||
mask2 = cv2.inRange(image, lower, upper)
|
||||
result2 = cv2.bitwise_and(result, result, mask=mask2)
|
||||
|
||||
# Isolate red mask
|
||||
lower = np.array([0,255,255])
|
||||
upper = np.array([10,255,255])
|
||||
mask3 = cv2.inRange(image, lower, upper)
|
||||
result3 = cv2.bitwise_and(result, result, mask=mask3)
|
||||
|
||||
# Join colour masks
|
||||
mask = mask0+mask1+mask2+mask3
|
||||
|
||||
# Set output image to zero everywhere except mask
|
||||
merge = image.copy()
|
||||
merge[np.where(mask==0)] = 0
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Convert to black/white by threshold
|
||||
ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
|
||||
|
||||
# Apply gaussian blur
|
||||
gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]}"
|
||||
|
||||
# Remove all characters that are not "-" and integers from OCR text
|
||||
pattern = r"[^-\d]+"
|
||||
ocr_text = re.sub(pattern, "", ocr_text)
|
||||
if ocr_text:
|
||||
incline = ocr_text
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(incline)
|
||||
|
||||
@@ -1,110 +1,103 @@
|
||||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
|
||||
# Author: Al Udell
|
||||
# Revised: April 22, 2023
|
||||
|
||||
# process-image.py - take Zwift screenshot, crop incline, OCR incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Convert screenshot to a numpy array
|
||||
screenshot_np = np.array(screenshot)
|
||||
|
||||
# Crop image to incline area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
|
||||
# Values for Zwift regular incline
|
||||
col1 = int(screenwidth/3000 * 2800)
|
||||
row1 = int(screenheight/2000 * 75)
|
||||
col2 = screenwidth
|
||||
row2 = int(screenheight/2000 * 200)
|
||||
|
||||
cropped_np = screenshot_np[row1:row2, col1:col2]
|
||||
|
||||
# Convert numpy array to PIL image
|
||||
cropped_pil = Image.fromarray(cropped_np)
|
||||
|
||||
# Convert PIL Image to a cv2 image
|
||||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# Convert cv2 image to HSV
|
||||
result = cropped_cv2.copy()
|
||||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Isolate white mask
|
||||
lower = np.array([0,0,159])
|
||||
upper = np.array([0,0,255])
|
||||
mask0 = cv2.inRange(image, lower, upper)
|
||||
result0 = cv2.bitwise_and(result, result, mask=mask0)
|
||||
|
||||
# Isolate yellow mask
|
||||
lower = np.array([24,239,241])
|
||||
upper = np.array([24,253,255])
|
||||
mask1 = cv2.inRange(image, lower, upper)
|
||||
result1 = cv2.bitwise_and(result, result, mask=mask1)
|
||||
|
||||
# Isolate orange mask
|
||||
lower = np.array([8,191,243])
|
||||
upper = np.array([8,192,243])
|
||||
mask2 = cv2.inRange(image, lower, upper)
|
||||
result2 = cv2.bitwise_and(result, result, mask=mask2)
|
||||
|
||||
# Isolate red mask
|
||||
lower = np.array([0,255,255])
|
||||
upper = np.array([10,255,255])
|
||||
mask3 = cv2.inRange(image, lower, upper)
|
||||
result3 = cv2.bitwise_and(result, result, mask=mask3)
|
||||
|
||||
# Join colour masks
|
||||
mask = mask0+mask1+mask2+mask3
|
||||
|
||||
# Set output image to zero everywhere except mask
|
||||
merge = image.copy()
|
||||
merge[np.where(mask==0)] = 0
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Convert to black/white by threshold
|
||||
ret,bin = cv2.threshold(gray,30,255,cv2.THRESH_BINARY)
|
||||
|
||||
# Closing
|
||||
kernel = np.ones((3,3),np.uint8)
|
||||
closing = cv2.morphologyEx(bin, cv2.MORPH_CLOSE, kernel)
|
||||
|
||||
# Invert black/white
|
||||
inv = cv2.bitwise_not(closing)
|
||||
|
||||
# Apply average blur
|
||||
averageBlur = cv2.blur(inv, (3, 3))
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(averageBlur, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]}"
|
||||
|
||||
# Remove all characters that are not "-" and integers from OCR text
|
||||
pattern = r"[^-\d]+"
|
||||
ocr_text = re.sub(pattern, "", ocr_text)
|
||||
if ocr_text:
|
||||
incline = ocr_text
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(incline)
|
||||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR
|
||||
# Author: Al Udell
|
||||
# Revised: August 16, 2023
|
||||
|
||||
# zwift-incline.py - take Zwift screenshot, crop incline, OCR incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Crop image to incline area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
|
||||
# Values for Zwift regular incline
|
||||
col1 = int(screenwidth/3000 * 2800)
|
||||
row1 = int(screenheight/2000 * 90)
|
||||
col2 = int(screenwidth/3000 * 2975)
|
||||
row2 = int(screenheight/2000 * 195)
|
||||
|
||||
cropped = screenshot.crop((col1, row1, col2, row2))
|
||||
|
||||
# Convert image to np array
|
||||
cropped_np = np.array(cropped)
|
||||
|
||||
# Convert np array to PIL
|
||||
cropped_pil = Image.fromarray(cropped_np)
|
||||
|
||||
# Convert PIL image to cv2 RGB
|
||||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR)
|
||||
|
||||
# Convert cv2 RGB to HSV
|
||||
result = cropped_cv2.copy()
|
||||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# Isolate white mask
|
||||
lower = np.array([0,0,159])
|
||||
upper = np.array([0,0,255])
|
||||
mask0 = cv2.inRange(image, lower, upper)
|
||||
result0 = cv2.bitwise_and(result, result, mask=mask0)
|
||||
|
||||
# Isolate yellow mask
|
||||
lower = np.array([24,239,241])
|
||||
upper = np.array([24,253,255])
|
||||
mask1 = cv2.inRange(image, lower, upper)
|
||||
result1 = cv2.bitwise_and(result, result, mask=mask1)
|
||||
|
||||
# Isolate orange mask
|
||||
lower = np.array([8,191,243])
|
||||
upper = np.array([8,192,243])
|
||||
mask2 = cv2.inRange(image, lower, upper)
|
||||
result2 = cv2.bitwise_and(result, result, mask=mask2)
|
||||
|
||||
# Isolate red mask
|
||||
lower = np.array([0,255,255])
|
||||
upper = np.array([10,255,255])
|
||||
mask3 = cv2.inRange(image, lower, upper)
|
||||
result3 = cv2.bitwise_and(result, result, mask=mask3)
|
||||
|
||||
# Join colour masks
|
||||
mask = mask0+mask1+mask2+mask3
|
||||
|
||||
# Set output image to zero everywhere except mask
|
||||
merge = image.copy()
|
||||
merge[np.where(mask==0)] = 0
|
||||
|
||||
# Convert to grayscale
|
||||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
# Convert to black/white by threshold
|
||||
ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV)
|
||||
|
||||
# Apply gaussian blur
|
||||
gaussianBlur = cv2.GaussianBlur(bin,(3,3),0)
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]}"
|
||||
|
||||
# Remove all characters that are not "-" and integers from OCR text
|
||||
pattern = r"[^-\d]+"
|
||||
ocr_text = re.sub(pattern, "", ocr_text)
|
||||
if ocr_text:
|
||||
incline = ocr_text
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(incline)
|
||||
|
||||
@@ -1,67 +1,71 @@
|
||||
# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts
|
||||
# Author: Al Udell
|
||||
# Revised: April 27, 2023
|
||||
|
||||
# process-image.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Convert screenshot to a numpy array
|
||||
screenshot_np = np.array(screenshot)
|
||||
|
||||
# Convert numpy array to a cv2 RGB image
|
||||
screenshot_cv2 = cv2.cvtColor(screenshot_np, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# Crop image to workout instruction area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
col1 = int(screenwidth/3000 * 1010)
|
||||
row1 = int(screenheight/2000 * 260)
|
||||
col2 = int(screenwidth/3000 * 1285)
|
||||
row2 = int(screenheight/2000 * 480)
|
||||
cropped_cv2 = screenshot_cv2[row1:row2, col1:col2]
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(cropped_cv2, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]} "
|
||||
|
||||
# Find the speed number
|
||||
num_pattern = r'\d+(\.\d+)?' # Regular expression pattern to match numbers with optional decimal places
|
||||
unit_pattern = r'\s+(kph|mph)' # Regular expression pattern to match "kph" or "mph" units
|
||||
speed_match = re.search(num_pattern + unit_pattern, ocr_text)
|
||||
if speed_match:
|
||||
speed = speed_match.group(0)
|
||||
pattern = r'\d+\.\d+'
|
||||
speed = re.findall(pattern, speed)[0]
|
||||
else:
|
||||
speed = 'None'
|
||||
|
||||
# Find the incline number
|
||||
incline_pattern = r'\d+\s*%' # Regular expression pattern to match numbers with "%"
|
||||
incline_match = re.search(incline_pattern, ocr_text)
|
||||
if incline_match:
|
||||
incline = incline_match.group(0)
|
||||
pattern = r'\d+'
|
||||
incline = re.findall(pattern, incline)[0]
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(f"{speed};{incline}")
|
||||
|
||||
# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts
|
||||
# Author: Al Udell
|
||||
# Revised: August 16, 2023
|
||||
|
||||
# zwift-workout.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline
|
||||
|
||||
# imports
|
||||
import cv2
|
||||
import numpy as np
|
||||
import re
|
||||
from datetime import datetime
|
||||
from paddleocr import PaddleOCR
|
||||
from PIL import Image, ImageGrab
|
||||
|
||||
# Take Zwift screenshot
|
||||
screenshot = ImageGrab.grab()
|
||||
|
||||
# Scale image to 3000 x 2000
|
||||
screenshot = screenshot.resize((3000, 2000))
|
||||
|
||||
# Crop image to workout instruction area
|
||||
screenwidth, screenheight = screenshot.size
|
||||
|
||||
# Values for Zwift workout instructions
|
||||
col1 = int(screenwidth/3000 * 1010)
|
||||
row1 = int(screenheight/2000 * 260)
|
||||
col2 = int(screenwidth/3000 * 1285)
|
||||
row2 = int(screenheight/2000 * 480)
|
||||
|
||||
cropped = screenshot.crop((col1, row1, col2, row2))
|
||||
|
||||
# Scale image to correct size for borderless window mode
|
||||
width, height = cropped.size
|
||||
cropped = cropped.resize((int(width * 0.99), int(height * 0.99)))
|
||||
|
||||
# Convert image to np array
|
||||
cropped_np = np.array(cropped)
|
||||
|
||||
# OCR image
|
||||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False)
|
||||
result = ocr.ocr(cropped_np, cls=False, det=True, rec=True)
|
||||
|
||||
# Extract OCR text
|
||||
ocr_text = ''
|
||||
for line in result:
|
||||
for word in line:
|
||||
ocr_text += f"{word[1][0]} "
|
||||
|
||||
# Find the speed number
|
||||
num_pattern = r'\d+(\.\d+)?' # Regular expression pattern to match numbers with optional decimal places
|
||||
unit_pattern = r'\s+(kph|mph)' # Regular expression pattern to match "kph" or "mph" units
|
||||
speed_match = re.search(num_pattern + unit_pattern, ocr_text)
|
||||
if speed_match:
|
||||
speed = speed_match.group(0)
|
||||
pattern = r'\d+\.\d+'
|
||||
speed = re.findall(pattern, speed)[0]
|
||||
else:
|
||||
speed = 'None'
|
||||
|
||||
# Find the incline number
|
||||
incline_pattern = r'-?\d+\s*%' # Regular expression pattern to match numbers with "%"
|
||||
incline_match = re.search(incline_pattern, ocr_text)
|
||||
if incline_match:
|
||||
incline = incline_match.group(0)
|
||||
pattern = r'-?\d+'
|
||||
incline = re.findall(pattern, incline)[0]
|
||||
else:
|
||||
incline = 'None'
|
||||
|
||||
print(f"{speed};{incline}")
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ class SoleF80TreadmillTestData : public BluetoothDeviceTestData {
|
||||
|
||||
public:
|
||||
SoleF80TreadmillTestData() : BluetoothDeviceTestData("Sole F80") {
|
||||
this->addDeviceName("F80", comparison::StartsWithIgnoreCase);
|
||||
this->addDeviceName("F65", comparison::StartsWithIgnoreCase);
|
||||
this->addDeviceName("S77", comparison::StartsWithIgnoreCase);
|
||||
this->addDeviceName("TT8", comparison::StartsWithIgnoreCase);
|
||||
@@ -40,6 +39,7 @@ class SoleF85TreadmillTestData : public BluetoothDeviceTestData {
|
||||
public:
|
||||
SoleF85TreadmillTestData() : BluetoothDeviceTestData("Sole F85 Treadmill") {
|
||||
this->addDeviceName("F85", comparison::StartsWithIgnoreCase);
|
||||
this->addDeviceName("F80", comparison::StartsWithIgnoreCase);
|
||||
}
|
||||
|
||||
deviceType get_expectedDeviceType() const override { return deviceType::SoleF80Treadmill; }
|
||||
|
||||
Reference in New Issue
Block a user