mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
184 Commits
virtualgea
...
Fulgaz-Dir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89963c6b49 | ||
|
|
2ef0a3c5a7 | ||
|
|
019b3c8abb | ||
|
|
317116f2d5 | ||
|
|
fe005a2f00 | ||
|
|
08c1e26d3b | ||
|
|
e98820601a | ||
|
|
c499092460 | ||
|
|
e3d50bda7c | ||
|
|
c060e8b24a | ||
|
|
f15f841860 | ||
|
|
15010b27dd | ||
|
|
88c6091e21 | ||
|
|
4a6df1c020 | ||
|
|
3d24e7c1a0 | ||
|
|
54a8b2619a | ||
|
|
038c4a6165 | ||
|
|
0831a4ed20 | ||
|
|
74fc5f660c | ||
|
|
ae5dd54738 | ||
|
|
c0299b16ac | ||
|
|
6401a66f4c | ||
|
|
ba064c2acd | ||
|
|
9375f15207 | ||
|
|
24183a4968 | ||
|
|
9e1537caad | ||
|
|
9fe72d13c0 | ||
|
|
df5e80a5be | ||
|
|
3b751d44e6 | ||
|
|
3815e45107 | ||
|
|
580eb3f092 | ||
|
|
aba59cd136 | ||
|
|
369fbc4bc0 | ||
|
|
871e704852 | ||
|
|
b574e86804 | ||
|
|
91735a714b | ||
|
|
da3b5b168e | ||
|
|
d339cd461d | ||
|
|
f5ac438905 | ||
|
|
073b331535 | ||
|
|
184c99ff6c | ||
|
|
b25f7acf20 | ||
|
|
cb0df3ae27 | ||
|
|
7f9ffb7e0e | ||
|
|
dd104da06d | ||
|
|
9440089d05 | ||
|
|
39b6ad7463 | ||
|
|
fdc548fda7 | ||
|
|
cc85cbba4f | ||
|
|
7576a77cd8 | ||
|
|
61c633474a | ||
|
|
69aefc0b30 | ||
|
|
19ca844968 | ||
|
|
5bb3a808a1 | ||
|
|
63cedd457d | ||
|
|
d9925ac780 | ||
|
|
e4a71e2940 | ||
|
|
d66d2fd915 | ||
|
|
d20f651672 | ||
|
|
19a564d832 | ||
|
|
5e100f8857 | ||
|
|
5491007a2a | ||
|
|
028b5b4c4a | ||
|
|
8eb0083897 | ||
|
|
89ed87ecb1 | ||
|
|
eac18d7a51 | ||
|
|
d9a50973cd | ||
|
|
39c33f3ebc | ||
|
|
2858468a04 | ||
|
|
1bbbda4efb | ||
|
|
49fc672538 | ||
|
|
d93107e286 | ||
|
|
08af7d61a5 | ||
|
|
e25dfc2354 | ||
|
|
a4a4d1b9c5 | ||
|
|
5761865916 | ||
|
|
5be7b8530e | ||
|
|
fe44490ad9 | ||
|
|
97138d8492 | ||
|
|
76845d5507 | ||
|
|
28bc5670c4 | ||
|
|
e0b84cb4a3 | ||
|
|
4cdf23d544 | ||
|
|
7beb1aed6f | ||
|
|
91841a1ff7 | ||
|
|
886310497c | ||
|
|
5e96d3cff3 | ||
|
|
d66af32eed | ||
|
|
982318326f | ||
|
|
2d680b9c4c | ||
|
|
17e2d211c1 | ||
|
|
e1020be250 | ||
|
|
ce219a790a | ||
|
|
7e55ded95d | ||
|
|
f70c6e8feb | ||
|
|
bdfaaecc83 | ||
|
|
4622fd6df2 | ||
|
|
fa5691fa2a | ||
|
|
2b995f8396 | ||
|
|
e044dc69bc | ||
|
|
26bf095f19 | ||
|
|
2ba66d9625 | ||
|
|
621440f981 | ||
|
|
a0c1efce9c | ||
|
|
861f916eb4 | ||
|
|
8ae1c59b41 | ||
|
|
d213b5dffe | ||
|
|
099531be72 | ||
|
|
b7e92ab33c | ||
|
|
0720d431db | ||
|
|
62c7b7b9df | ||
|
|
4eed4be958 | ||
|
|
eb0dc48d24 | ||
|
|
4ff8e8335a | ||
|
|
ec263a402d | ||
|
|
0ffb06cc79 | ||
|
|
302526000f | ||
|
|
76891d41e2 | ||
|
|
bb3f9fe216 | ||
|
|
dd0ce73260 | ||
|
|
206fa06049 | ||
|
|
3985eecfe6 | ||
|
|
97a7b5c27c | ||
|
|
02c7063655 | ||
|
|
0067a728a4 | ||
|
|
3ec15253d0 | ||
|
|
09772d0968 | ||
|
|
6496e0cf7c | ||
|
|
b6ef01c59a | ||
|
|
522ea54ff2 | ||
|
|
ba4cc11196 | ||
|
|
95622182c9 | ||
|
|
0349dfcbaf | ||
|
|
9fc9d9cfe9 | ||
|
|
5ea16d0869 | ||
|
|
57b259fcba | ||
|
|
b78cf1fca5 | ||
|
|
77b71e56de | ||
|
|
c8e3d370a1 | ||
|
|
ab8a325da2 | ||
|
|
a3158514af | ||
|
|
2804d4686a | ||
|
|
cf0dc2d00d | ||
|
|
164aa38cb1 | ||
|
|
14998d0f25 | ||
|
|
ca74fe7ccd | ||
|
|
facba11bae | ||
|
|
8b8302fb53 | ||
|
|
eaea4bf8b8 | ||
|
|
3d9c3e4103 | ||
|
|
e840d7b3e9 | ||
|
|
3a248ad2c5 | ||
|
|
5912d7df2d | ||
|
|
94842114e6 | ||
|
|
d83df0ba5a | ||
|
|
0764fb50b2 | ||
|
|
bb5de868ab | ||
|
|
2b8fe6c28d | ||
|
|
0153e09f0d | ||
|
|
dc44433d7c | ||
|
|
8bdefdb331 | ||
|
|
c7f5e320fc | ||
|
|
c1582cc763 | ||
|
|
f2f0f7a793 | ||
|
|
3d665e397e | ||
|
|
194f8686f3 | ||
|
|
fb79d0ddd6 | ||
|
|
d7e0a4e441 | ||
|
|
465123a156 | ||
|
|
88d01562b1 | ||
|
|
85421f41b8 | ||
|
|
a67cb10633 | ||
|
|
f00a161fc1 | ||
|
|
c071c56eb7 | ||
|
|
b39f769423 | ||
|
|
dde526c059 | ||
|
|
c223d6e81d | ||
|
|
d531a1d313 | ||
|
|
b0722cc827 | ||
|
|
2e534abfbb | ||
|
|
6d3ca9877a | ||
|
|
f477cb32ab | ||
|
|
51b79ed413 | ||
|
|
fa78f03f0a |
3915
.github/workflows/main.yml
vendored
3915
.github/workflows/main.yml
vendored
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// QZWidget.swift
|
||||
// QZWidget
|
||||
//
|
||||
// Created by Roberto Viola on 04/10/25.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
struct Provider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> SimpleEntry {
|
||||
SimpleEntry(date: Date(), emoji: "😀")
|
||||
}
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
|
||||
let entry = SimpleEntry(date: Date(), emoji: "😀")
|
||||
completion(entry)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
var entries: [SimpleEntry] = []
|
||||
|
||||
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
|
||||
let currentDate = Date()
|
||||
for hourOffset in 0 ..< 5 {
|
||||
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
|
||||
let entry = SimpleEntry(date: entryDate, emoji: "😀")
|
||||
entries.append(entry)
|
||||
}
|
||||
|
||||
let timeline = Timeline(entries: entries, policy: .atEnd)
|
||||
completion(timeline)
|
||||
}
|
||||
|
||||
// func relevances() async -> WidgetRelevances<Void> {
|
||||
// // Generate a list containing the contexts this widget is relevant in.
|
||||
// }
|
||||
}
|
||||
|
||||
struct SimpleEntry: TimelineEntry {
|
||||
let date: Date
|
||||
let emoji: String
|
||||
}
|
||||
|
||||
struct QZWidgetEntryView : View {
|
||||
var entry: Provider.Entry
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Time:")
|
||||
Text(entry.date, style: .time)
|
||||
|
||||
Text("Emoji:")
|
||||
Text(entry.emoji)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct QZWidget: Widget {
|
||||
let kind: String = "QZWidget"
|
||||
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: kind, provider: Provider()) { entry in
|
||||
if #available(iOS 17.0, *) {
|
||||
QZWidgetEntryView(entry: entry)
|
||||
.containerBackground(.fill.tertiary, for: .widget)
|
||||
} else {
|
||||
QZWidgetEntryView(entry: entry)
|
||||
.padding()
|
||||
.background()
|
||||
}
|
||||
}
|
||||
.configurationDisplayName("My Widget")
|
||||
.description("This is an example widget.")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview(as: .systemSmall) {
|
||||
QZWidget()
|
||||
} timeline: {
|
||||
SimpleEntry(date: .now, emoji: "😀")
|
||||
SimpleEntry(date: .now, emoji: "🤩")
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// QZWidgetBundle.swift
|
||||
// QZWidget
|
||||
//
|
||||
// Created by Roberto Viola on 04/10/25.
|
||||
//
|
||||
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct QZWidgetBundle: WidgetBundle {
|
||||
var body: some Widget {
|
||||
QZWidget()
|
||||
QZWidgetControl()
|
||||
QZWidgetLiveActivity()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// QZWidgetControl.swift
|
||||
// QZWidget
|
||||
//
|
||||
// Created by Roberto Viola on 04/10/25.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
struct QZWidgetControl: ControlWidget {
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(
|
||||
kind: "org.cagnulein.qdomyoszwift.QZWidget",
|
||||
provider: Provider()
|
||||
) { value in
|
||||
ControlWidgetToggle(
|
||||
"Start Timer",
|
||||
isOn: value,
|
||||
action: StartTimerIntent()
|
||||
) { isRunning in
|
||||
Label(isRunning ? "On" : "Off", systemImage: "timer")
|
||||
}
|
||||
}
|
||||
.displayName("Timer")
|
||||
.description("A an example control that runs a timer.")
|
||||
}
|
||||
}
|
||||
|
||||
extension QZWidgetControl {
|
||||
struct Provider: ControlValueProvider {
|
||||
var previewValue: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
func currentValue() async throws -> Bool {
|
||||
let isRunning = true // Check if the timer is running
|
||||
return isRunning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StartTimerIntent: SetValueIntent {
|
||||
static let title: LocalizedStringResource = "Start a timer"
|
||||
|
||||
@Parameter(title: "Timer is running")
|
||||
var value: Bool
|
||||
|
||||
func perform() async throws -> some IntentResult {
|
||||
// Start / stop the timer based on `value`.
|
||||
return .result()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// QZWidgetLiveActivity.swift
|
||||
// QDomyos-Zwift Live Activity Widget
|
||||
//
|
||||
// Displays workout metrics on Dynamic Island and Lock Screen
|
||||
//
|
||||
|
||||
import ActivityKit
|
||||
import WidgetKit
|
||||
import SwiftUI
|
||||
|
||||
// QZWorkoutAttributes is defined in QZWorkoutAttributes.swift (shared file)
|
||||
|
||||
// MARK: - Live Activity Widget
|
||||
@available(iOS 16.1, *)
|
||||
struct QZWidgetLiveActivity: Widget {
|
||||
var body: some WidgetConfiguration {
|
||||
ActivityConfiguration(for: QZWorkoutAttributes.self) { context in
|
||||
// Lock screen/banner UI
|
||||
LockScreenLiveActivityView(context: context)
|
||||
} dynamicIsland: { context in
|
||||
DynamicIsland {
|
||||
// Expanded UI
|
||||
DynamicIslandExpandedRegion(.leading) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
|
||||
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
|
||||
Label("\(Int(speed)) \(speedUnit)", systemImage: "speedometer")
|
||||
.font(.caption)
|
||||
Label("\(context.state.heartRate) bpm", systemImage: "heart.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.trailing) {
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Label("\(Int(context.state.power)) W", systemImage: "bolt.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.yellow)
|
||||
Label("\(Int(context.state.cadence)) rpm", systemImage: "arrow.clockwise")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.center) {
|
||||
// Empty or can add more info
|
||||
}
|
||||
|
||||
DynamicIslandExpandedRegion(.bottom) {
|
||||
HStack {
|
||||
let distanceKm = context.state.distance / 1000.0
|
||||
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
|
||||
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
|
||||
Label(String(format: "%.2f \(distanceUnit)", distance), systemImage: "map")
|
||||
Spacer()
|
||||
Label("\(Int(context.state.kcal)) kcal", systemImage: "flame.fill")
|
||||
.foregroundColor(.orange)
|
||||
}
|
||||
.font(.caption)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
} compactLeading: {
|
||||
// Compact leading (left side of Dynamic Island)
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "heart.fill")
|
||||
.foregroundColor(.red)
|
||||
Text("\(context.state.heartRate)")
|
||||
.font(.caption2)
|
||||
}
|
||||
} compactTrailing: {
|
||||
// Compact trailing (right side of Dynamic Island)
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.foregroundColor(.yellow)
|
||||
Text("\(Int(context.state.power))")
|
||||
.font(.caption2)
|
||||
}
|
||||
} minimal: {
|
||||
// Minimal view (when multiple activities)
|
||||
Image(systemName: "figure.run")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lock Screen View
|
||||
@available(iOS 16.1, *)
|
||||
struct LockScreenLiveActivityView: View {
|
||||
let context: ActivityViewContext<QZWorkoutAttributes>
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Image(systemName: "figure.indoor.cycle")
|
||||
.foregroundColor(.blue)
|
||||
Text(context.attributes.deviceName)
|
||||
.font(.headline)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack(spacing: 16) {
|
||||
let speed = context.attributes.useMiles ? context.state.speed * 0.621371 : context.state.speed
|
||||
let speedUnit = context.attributes.useMiles ? "mph" : "km/h"
|
||||
MetricView(icon: "speedometer", value: String(format: "%.1f", speed), unit: speedUnit)
|
||||
MetricView(icon: "heart.fill", value: "\(context.state.heartRate)", unit: "bpm", color: .red)
|
||||
MetricView(icon: "bolt.fill", value: "\(Int(context.state.power))", unit: "W", color: .yellow)
|
||||
}
|
||||
|
||||
HStack(spacing: 16) {
|
||||
let distanceKm = context.state.distance / 1000.0
|
||||
let distance = context.attributes.useMiles ? distanceKm * 0.621371 : distanceKm
|
||||
let distanceUnit = context.attributes.useMiles ? "mi" : "km"
|
||||
MetricView(icon: "arrow.clockwise", value: "\(Int(context.state.cadence))", unit: "rpm")
|
||||
MetricView(icon: "map", value: String(format: "%.2f", distance), unit: distanceUnit)
|
||||
MetricView(icon: "flame.fill", value: "\(Int(context.state.kcal))", unit: "kcal", color: .orange)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Metric View Component
|
||||
struct MetricView: View {
|
||||
let icon: String
|
||||
let value: String
|
||||
let unit: String
|
||||
var color: Color = .primary
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2) {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(color)
|
||||
.font(.caption)
|
||||
Text(value)
|
||||
.font(.system(.body, design: .rounded))
|
||||
.fontWeight(.semibold)
|
||||
Text(unit)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
@available(iOS 16.1, *)
|
||||
struct QZWidgetLiveActivity_Previews: PreviewProvider {
|
||||
static let attributes = QZWorkoutAttributes(deviceName: "QZ Bike", useMiles: false)
|
||||
static let contentState = QZWorkoutAttributes.ContentState(
|
||||
speed: 25.5,
|
||||
cadence: 85,
|
||||
power: 200,
|
||||
heartRate: 145,
|
||||
distance: 12500, // meters (will be displayed as 12.50 km or 7.77 mi)
|
||||
kcal: 320,
|
||||
useMiles: false
|
||||
)
|
||||
|
||||
static var previews: some View {
|
||||
attributes
|
||||
.previewContext(contentState, viewKind: .dynamicIsland(.compact))
|
||||
.previewDisplayName("Island Compact")
|
||||
attributes
|
||||
.previewContext(contentState, viewKind: .dynamicIsland(.expanded))
|
||||
.previewDisplayName("Island Expanded")
|
||||
attributes
|
||||
.previewContext(contentState, viewKind: .dynamicIsland(.minimal))
|
||||
.previewDisplayName("Minimal")
|
||||
attributes
|
||||
.previewContext(contentState, viewKind: .content)
|
||||
.previewDisplayName("Lock Screen")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// QZWorkoutAttributes.swift
|
||||
// QDomyos-Zwift
|
||||
//
|
||||
// Shared attributes for Live Activities
|
||||
// MUST be included in both qdomyoszwift and QZWidget targets
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ActivityKit
|
||||
|
||||
@available(iOS 16.1, *)
|
||||
public struct QZWorkoutAttributes: ActivityAttributes {
|
||||
public struct ContentState: Codable, Hashable {
|
||||
public var speed: Double
|
||||
public var cadence: Double
|
||||
public var power: Double
|
||||
public var heartRate: Int
|
||||
public var distance: Double
|
||||
public var kcal: Double
|
||||
public var useMiles: Bool
|
||||
|
||||
public init(speed: Double, cadence: Double, power: Double, heartRate: Int, distance: Double, kcal: Double, useMiles: Bool) {
|
||||
self.speed = speed
|
||||
self.cadence = cadence
|
||||
self.power = power
|
||||
self.heartRate = heartRate
|
||||
self.distance = distance
|
||||
self.kcal = kcal
|
||||
self.useMiles = useMiles
|
||||
}
|
||||
}
|
||||
|
||||
public var deviceName: String
|
||||
public var useMiles: Bool
|
||||
|
||||
public init(deviceName: String, useMiles: Bool) {
|
||||
self.deviceName = deviceName
|
||||
self.useMiles = useMiles
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 70;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@@ -130,6 +130,7 @@
|
||||
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
|
||||
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */; };
|
||||
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB42CEF8FD200839641 /* technogymbike.cpp */; };
|
||||
870C72652E91565E00DC8A84 /* ios_liveactivity.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870C72632E91565E00DC8A84 /* ios_liveactivity.mm */; };
|
||||
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706B29C48AEA0094D0F3 /* handleurl.cpp */; };
|
||||
8710706E29C48AF30094D0F3 /* moc_handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */; };
|
||||
8710707329C4A5E70094D0F3 /* GarminConnect.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710707229C4A5E70094D0F3 /* GarminConnect.swift */; };
|
||||
@@ -597,6 +598,12 @@
|
||||
87EBB2AB2D39214E00348B15 /* moc_workoutmodel.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A12D39214E00348B15 /* moc_workoutmodel.cpp */; };
|
||||
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */; };
|
||||
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */; };
|
||||
87EFC5662E918D35005BB573 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87EFC5652E918D35005BB573 /* WidgetKit.framework */; };
|
||||
87EFC5672E918D35005BB573 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */; };
|
||||
87EFC5762E918D38005BB573 /* QZWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
87EFC57D2E918DAA005BB573 /* LiveActivityBridge.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */; };
|
||||
87EFC58F2E919DB7005BB573 /* QZWorkoutAttributes.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */; };
|
||||
87EFC5902E919DB7005BB573 /* QZWorkoutAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */; };
|
||||
87EFE45927A518F5006EA1C3 /* nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */; };
|
||||
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */; };
|
||||
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E3E29178523000DB52C /* octaneelliptical.cpp */; };
|
||||
@@ -704,6 +711,13 @@
|
||||
remoteGlobalIDString = 876E4E102594747F00BD5714;
|
||||
remoteInfo = watchkit;
|
||||
};
|
||||
87EFC5742E918D38005BB573 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 87EFC5632E918D35005BB573;
|
||||
remoteInfo = QZWidgetExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -729,6 +743,17 @@
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
87EFC57B2E918D38005BB573 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
87EFC5762E918D38005BB573 /* QZWidgetExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
99542592E9780B9225F24AA8 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -974,6 +999,8 @@
|
||||
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
|
||||
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymbike.cpp; sourceTree = "<group>"; };
|
||||
870A5DB42CEF8FD200839641 /* technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = technogymbike.cpp; path = ../src/devices/technogymbike/technogymbike.cpp; sourceTree = SOURCE_ROOT; };
|
||||
870C72622E91565E00DC8A84 /* ios_liveactivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_liveactivity.h; path = ../src/ios/ios_liveactivity.h; sourceTree = SOURCE_ROOT; };
|
||||
870C72632E91565E00DC8A84 /* ios_liveactivity.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_liveactivity.mm; path = ../src/ios/ios_liveactivity.mm; sourceTree = SOURCE_ROOT; };
|
||||
8710706A29C48AE90094D0F3 /* handleurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = handleurl.h; path = ../src/handleurl.h; sourceTree = "<group>"; };
|
||||
8710706B29C48AEA0094D0F3 /* handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = handleurl.cpp; path = ../src/handleurl.cpp; sourceTree = "<group>"; };
|
||||
8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_handleurl.cpp; sourceTree = "<group>"; };
|
||||
@@ -1693,6 +1720,11 @@
|
||||
87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformtreadmill.cpp; path = ../src/devices/proformtreadmill/proformtreadmill.cpp; sourceTree = "<group>"; };
|
||||
87EFB56D25BD703C0039DD5A /* proformtreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformtreadmill.h; path = ../src/devices/proformtreadmill/proformtreadmill.h; sourceTree = "<group>"; };
|
||||
87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtreadmill.cpp; sourceTree = "<group>"; };
|
||||
87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = QZWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
87EFC5652E918D35005BB573 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LiveActivityBridge.swift; path = ../src/ios/LiveActivityBridge.swift; sourceTree = SOURCE_ROOT; };
|
||||
87EFC57E2E919C98005BB573 /* QZWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QZWidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||
87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = QZWorkoutAttributes.swift; path = QZWidget/QZWorkoutAttributes.swift; sourceTree = "<group>"; };
|
||||
87EFE45727A518F5006EA1C3 /* nautiluselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautiluselliptical.h; path = ../src/devices/nautiluselliptical/nautiluselliptical.h; sourceTree = "<group>"; };
|
||||
87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautiluselliptical.cpp; path = ../src/devices/nautiluselliptical/nautiluselliptical.cpp; sourceTree = "<group>"; };
|
||||
87EFE45A27A51900006EA1C3 /* moc_nautiluselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nautiluselliptical.cpp; sourceTree = "<group>"; };
|
||||
@@ -1929,6 +1961,20 @@
|
||||
FF5BDAB0076F3391B219EA52 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = "<absolute>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
87EFC5772E918D38005BB573 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||
membershipExceptions = (
|
||||
Info.plist,
|
||||
);
|
||||
target = 87EFC5632E918D35005BB573 /* QZWidgetExtension */;
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
87EFC5682E918D35005BB573 /* QZWidget */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (87EFC5772E918D38005BB573 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = QZWidget; sourceTree = "<group>"; };
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
876E4E172594748000BD5714 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
@@ -1937,6 +1983,15 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
87EFC5612E918D35005BB573 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
87EFC5672E918D35005BB573 /* SwiftUI.framework in Frameworks */,
|
||||
87EFC5662E918D35005BB573 /* WidgetKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D1C883685E82D5676953459A /* Link Binary With Libraries */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -2278,6 +2333,10 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87EFC58E2E919DB7005BB573 /* QZWorkoutAttributes.swift */,
|
||||
87EFC57C2E918DAA005BB573 /* LiveActivityBridge.swift */,
|
||||
870C72622E91565E00DC8A84 /* ios_liveactivity.h */,
|
||||
870C72632E91565E00DC8A84 /* ios_liveactivity.mm */,
|
||||
876C646E2E74139F00F1BEC0 /* fitbackupwriter.h */,
|
||||
876C646F2E74139F00F1BEC0 /* fitbackupwriter.cpp */,
|
||||
876C64702E74139F00F1BEC0 /* moc_fitbackupwriter.cpp */,
|
||||
@@ -3131,6 +3190,7 @@
|
||||
4D765E1B1EA6C757220C63E7 /* CoreFoundation.framework */,
|
||||
FCC237CA5AD60B9BA4447615 /* Foundation.framework */,
|
||||
344F66310C19536DB4886D8F /* qtpcre2 */,
|
||||
87EFC5652E918D35005BB573 /* WidgetKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -3393,6 +3453,7 @@
|
||||
E8C543AB96796ECAA2E65C57 /* qdomyoszwift */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87EFC57E2E919C98005BB573 /* QZWidgetExtension.entitlements */,
|
||||
8768C8CE2BBC12170099DBE1 /* adb */,
|
||||
87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */,
|
||||
8745B2752AFCB4A300991A39 /* android */,
|
||||
@@ -3403,6 +3464,7 @@
|
||||
74B182DB50CB5611B5C1C297 /* Supporting Files */,
|
||||
876E4E122594747F00BD5714 /* watchkit */,
|
||||
876E4E1E2594748000BD5714 /* watchkit Extension */,
|
||||
87EFC5682E918D35005BB573 /* QZWidget */,
|
||||
AF39DD055C3EF8226FBE929D /* Frameworks */,
|
||||
858FCAB0EB1F29CF8B07677C /* Bundle Data */,
|
||||
FE0A091FDBFB3E9C31B7A1BD /* Products */,
|
||||
@@ -3417,6 +3479,7 @@
|
||||
040B10E2EF2CEF79F2205FE2 /* qdomyoszwift.app */,
|
||||
876E4E112594747F00BD5714 /* watchkit.app */,
|
||||
876E4E1A2594748000BD5714 /* watchkit Extension.appex */,
|
||||
87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -3433,11 +3496,13 @@
|
||||
30414803F31797EB689AE508 /* Copy Bundle Resources */,
|
||||
99542592E9780B9225F24AA8 /* Embed Frameworks */,
|
||||
876E4E332594748100BD5714 /* Embed Watch Content */,
|
||||
87EFC57B2E918D38005BB573 /* Embed Foundation Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
876E4E312594748100BD5714 /* PBXTargetDependency */,
|
||||
87EFC5752E918D38005BB573 /* PBXTargetDependency */,
|
||||
);
|
||||
name = qdomyoszwift;
|
||||
packageProductDependencies = (
|
||||
@@ -3483,6 +3548,28 @@
|
||||
productReference = 876E4E1A2594748000BD5714 /* watchkit Extension.appex */;
|
||||
productType = "com.apple.product-type.watchkit2-extension";
|
||||
};
|
||||
87EFC5632E918D35005BB573 /* QZWidgetExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 87EFC5782E918D38005BB573 /* Build configuration list for PBXNativeTarget "QZWidgetExtension" */;
|
||||
buildPhases = (
|
||||
87EFC5602E918D35005BB573 /* Sources */,
|
||||
87EFC5612E918D35005BB573 /* Frameworks */,
|
||||
87EFC5622E918D35005BB573 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
87EFC5682E918D35005BB573 /* QZWidget */,
|
||||
);
|
||||
name = QZWidgetExtension;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = QZWidgetExtension;
|
||||
productReference = 87EFC5642E918D35005BB573 /* QZWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -3490,7 +3577,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
DefaultBuildSystemTypeForWorkspace = Original;
|
||||
LastSwiftUpdateCheck = 1220;
|
||||
LastSwiftUpdateCheck = 2600;
|
||||
TargetAttributes = {
|
||||
799833E5566DEFFC37E4BF1E = {
|
||||
DevelopmentTeam = 6335M7T29D;
|
||||
@@ -3506,6 +3593,9 @@
|
||||
DevelopmentTeam = 6335M7T29D;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
87EFC5632E918D35005BB573 = {
|
||||
CreatedOnToolsVersion = 26.0.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */;
|
||||
@@ -3527,6 +3617,7 @@
|
||||
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
|
||||
876E4E102594747F00BD5714 /* watchkit */,
|
||||
876E4E192594748000BD5714 /* watchkit Extension */,
|
||||
87EFC5632E918D35005BB573 /* QZWidgetExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -3573,6 +3664,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
87EFC5622E918D35005BB573 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -3590,10 +3688,19 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
87EFC5602E918D35005BB573 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
87EFC5902E919DB7005BB573 /* QZWorkoutAttributes.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
F7E50F631C51CD5B5DC0BC43 /* Compile Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
870C72652E91565E00DC8A84 /* ios_liveactivity.mm in Compile Sources */,
|
||||
8738249627E646E3004F1B46 /* characteristicnotifier2acd.cpp in Compile Sources */,
|
||||
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
|
||||
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */,
|
||||
@@ -3842,6 +3949,7 @@
|
||||
87943AB429E0215D007575F2 /* localipaddress.cpp in Compile Sources */,
|
||||
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */,
|
||||
ACB47DC464A2BC9D39C544AD /* gpx.cpp in Compile Sources */,
|
||||
87EFC57D2E918DAA005BB573 /* LiveActivityBridge.swift in Compile Sources */,
|
||||
6361329E515248BB41640C07 /* homeform.cpp in Compile Sources */,
|
||||
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */,
|
||||
87C5F0D026285E7E0067A1B5 /* moc_smtpclient.cpp in Compile Sources */,
|
||||
@@ -3953,6 +4061,7 @@
|
||||
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
|
||||
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
|
||||
876C64712E74139F00F1BEC0 /* moc_fitbackupwriter.cpp in Compile Sources */,
|
||||
87EFC58F2E919DB7005BB573 /* QZWorkoutAttributes.swift in Compile Sources */,
|
||||
876C64722E74139F00F1BEC0 /* fitbackupwriter.cpp in Compile Sources */,
|
||||
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
|
||||
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */,
|
||||
@@ -4132,6 +4241,11 @@
|
||||
target = 876E4E102594747F00BD5714 /* watchkit */;
|
||||
targetProxy = 876E4E302594748100BD5714 /* PBXContainerItemProxy */;
|
||||
};
|
||||
87EFC5752E918D38005BB573 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 87EFC5632E918D35005BB573 /* QZWidgetExtension */;
|
||||
targetProxy = 87EFC5742E918D38005BB573 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@@ -4455,7 +4569,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1165;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4633,6 +4747,7 @@
|
||||
QMAKE_PKGINFO_TYPEINFO = "????";
|
||||
QMAKE_SHORT_VERSION = 1.7;
|
||||
QT_LIBRARY_SUFFIX = "";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
@@ -4655,7 +4770,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1165;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
@@ -4836,6 +4951,7 @@
|
||||
QMAKE_PKGINFO_TYPEINFO = "????";
|
||||
QMAKE_SHORT_VERSION = 1.7;
|
||||
QT_LIBRARY_SUFFIX = _debug;
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
@@ -4891,7 +5007,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1165;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4987,7 +5103,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1165;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -5079,7 +5195,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1166;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -5195,7 +5311,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1166;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -5267,6 +5383,184 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
87EFC5792E918D38005BB573 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = QZWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = QZWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SwiftUI,
|
||||
"-framework",
|
||||
WidgetKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cagnulein.qdomyoszwift.QZWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
87EFC57A2E918D38005BB573 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1209;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = QZWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = QZWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "";
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SwiftUI,
|
||||
"-framework",
|
||||
WidgetKit,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cagnulein.qdomyoszwift.QZWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_APPROACHABLE_CONCURRENCY = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@@ -5297,6 +5591,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
87EFC5782E918D38005BB573 /* Build configuration list for PBXNativeTarget "QZWidgetExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
87EFC5792E918D38005BB573 /* Debug */,
|
||||
87EFC57A2E918D38005BB573 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Debug;
|
||||
};
|
||||
DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
@@ -92,7 +92,7 @@ class BluetoothHandler : public QObject
|
||||
void onKeyPressed(int keyCode)
|
||||
{
|
||||
qDebug() << "Key pressed:" << keyCode;
|
||||
if (m_bluetooth && m_bluetooth->device() && m_bluetooth->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
if (m_bluetooth && m_bluetooth->device() && m_bluetooth->device()->deviceType() == BIKE) {
|
||||
if (keyCode == 115) // up
|
||||
((bike*)m_bluetooth->device())->setGears(((bike*)m_bluetooth->device())->gears() + 1);
|
||||
else if (keyCode == 114) // down
|
||||
|
||||
20
src/Home.qml
20
src/Home.qml
@@ -72,7 +72,19 @@ HomeForm {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("New lap started!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog {
|
||||
id: stopConfirmationDialog
|
||||
text: qsTr("Stop Workout")
|
||||
informativeText: qsTr("Do you really want to stop the current workout?")
|
||||
buttons: (MessageDialog.Yes | MessageDialog.No)
|
||||
onYesClicked: {
|
||||
close();
|
||||
inner_stop();
|
||||
}
|
||||
onNoClicked: close()
|
||||
}
|
||||
|
||||
Timer {
|
||||
@@ -141,7 +153,11 @@ HomeForm {
|
||||
|
||||
start.onClicked: { start_clicked(); }
|
||||
stop.onClicked: {
|
||||
inner_stop();
|
||||
if (rootItem.confirmStopEnabled()) {
|
||||
stopConfirmationDialog.open();
|
||||
} else {
|
||||
inner_stop();
|
||||
}
|
||||
}
|
||||
lap.onClicked: { lap_clicked(); popupLap.open(); popupLapAutoClose.running = true; }
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ Page {
|
||||
width: parent.width
|
||||
anchors.top: row1.bottom
|
||||
anchors.topMargin: 30
|
||||
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
|
||||
text: "This app should automatically connect to your bike/treadmill/rower. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) both Bluetooth and Bluetooth permissions MUST be enabled<br>3) your bike/treadmill/rower should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issues, please feel free to contact me at roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
|
||||
wrapMode: Label.WordWrap
|
||||
visible: rootItem.labelHelp
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.11" android:versionCode="1155" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.13" android:versionCode="1208" android:installLocation="auto">
|
||||
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
|
||||
Remove the comment if you do not require these default permissions. -->
|
||||
<!-- %%INSERT_PERMISSIONS -->
|
||||
|
||||
BIN
src/android/libs/arm64-v8a/libc++_shared.so
Executable file
BIN
src/android/libs/arm64-v8a/libc++_shared.so
Executable file
Binary file not shown.
BIN
src/android/libs/armeabi-v7a/libc++_shared.so
Executable file
BIN
src/android/libs/armeabi-v7a/libc++_shared.so
Executable file
Binary file not shown.
BIN
src/android/libs/x86/libc++_shared.so
Executable file
BIN
src/android/libs/x86/libc++_shared.so
Executable file
Binary file not shown.
BIN
src/android/libs/x86_64/libc++_shared.so
Executable file
BIN
src/android/libs/x86_64/libc++_shared.so
Executable file
Binary file not shown.
@@ -7,9 +7,13 @@ import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import org.cagnulen.qdomyoszwift.QLog;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
private static MediaButtonReceiver instance;
|
||||
private static final int TARGET_VOLUME = 7; // Middle volume value for infinite gear changes
|
||||
private static boolean restoringVolume = false; // Flag to prevent recursion
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@@ -21,8 +25,30 @@ public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
|
||||
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
|
||||
|
||||
QLog.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
|
||||
QLog.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Previous: " + previousVolume + ", Max: " + maxVolume + ", Restoring: " + restoringVolume);
|
||||
|
||||
// If we're restoring volume, skip processing and reset flag
|
||||
if (restoringVolume) {
|
||||
QLog.d("MediaButtonReceiver", "Volume restore completed");
|
||||
restoringVolume = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the gear change
|
||||
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
|
||||
|
||||
// Auto-restore volume to middle value after a short delay to enable infinite gear changes
|
||||
if (currentVolume != TARGET_VOLUME) {
|
||||
final AudioManager am = audioManager;
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
QLog.d("MediaButtonReceiver", "Auto-restoring volume to: " + TARGET_VOLUME);
|
||||
restoringVolume = true;
|
||||
am.setStreamVolume(AudioManager.STREAM_MUSIC, TARGET_VOLUME, 0);
|
||||
}
|
||||
}, 100); // 100ms delay to ensure gear change is processed first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +80,25 @@ public class MediaButtonReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
QLog.d("MediaButtonReceiver", "Receiver registered successfully");
|
||||
|
||||
|
||||
// Initialize volume to target value for gear control
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
if (audioManager != null) {
|
||||
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
|
||||
if (currentVolume != TARGET_VOLUME) {
|
||||
QLog.d("MediaButtonReceiver", "Initializing volume to: " + TARGET_VOLUME);
|
||||
restoringVolume = true;
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, TARGET_VOLUME, 0);
|
||||
// Reset flag after initialization
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
restoringVolume = false;
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
QLog.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
|
||||
6
src/bluetoothdevicetype.h
Normal file
6
src/bluetoothdevicetype.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef BLUETOOTHDEVICETYPE_H
|
||||
#define BLUETOOTHDEVICETYPE_H
|
||||
|
||||
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE, STAIRCLIMBER };
|
||||
|
||||
#endif // BLUETOOTHDEVICETYPE_H
|
||||
@@ -5,7 +5,7 @@ CharacteristicNotifier2A53::CharacteristicNotifier2A53(bluetoothdevice *Bike, QO
|
||||
: CharacteristicNotifier(0x2a53, parent), Bike(Bike) {}
|
||||
|
||||
int CharacteristicNotifier2A53::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
value.append(0x02); // total distance
|
||||
uint16_t speed = Bike->currentSpeed().value() / 3.6 * 256;
|
||||
uint32_t distance = Bike->odometer() * 10000.0;
|
||||
|
||||
@@ -8,7 +8,7 @@ int CharacteristicNotifier2A63::notify(QByteArray &value) {
|
||||
if (normalizeWattage < 0)
|
||||
normalizeWattage = 0;
|
||||
|
||||
if (Bike->deviceType() == bluetoothdevice::BIKE) {
|
||||
if (Bike->deviceType() == BIKE) {
|
||||
/*
|
||||
// set measurement
|
||||
measurement[2] = power & 0xFF;
|
||||
|
||||
@@ -7,8 +7,8 @@ CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QO
|
||||
: CharacteristicNotifier(0x2acd, parent), Bike(Bike) {}
|
||||
|
||||
int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == TREADMILL || dt == ELLIPTICAL) {
|
||||
value.append(0x0C); // Inclination available and distance for peloton
|
||||
//value.append((char)0x01); // heart rate available
|
||||
value.append((char)0x05); // HeartRate(8) | ElapsedTime(10)
|
||||
@@ -46,7 +46,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
inclination /= gain;
|
||||
}
|
||||
|
||||
if (dt == bluetoothdevice::TREADMILL)
|
||||
if (dt == TREADMILL)
|
||||
normalizeIncline = (uint32_t)qRound(inclination * 10);
|
||||
a = (normalizeIncline >> 8) & 0XFF;
|
||||
b = normalizeIncline & 0XFF;
|
||||
@@ -54,7 +54,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
|
||||
inclineBytes.append(b);
|
||||
inclineBytes.append(a);
|
||||
double ramp = 0;
|
||||
if (dt == bluetoothdevice::TREADMILL)
|
||||
if (dt == TREADMILL)
|
||||
ramp = qRadiansToDegrees(qAtan(inclination / 100));
|
||||
int16_t normalizeRamp = (int32_t)qRound(ramp * 10);
|
||||
a = (normalizeRamp >> 8) & 0XFF;
|
||||
|
||||
@@ -8,12 +8,12 @@ CharacteristicNotifier2AD2::CharacteristicNotifier2AD2(bluetoothdevice *Bike, QO
|
||||
: CharacteristicNotifier(0x2ad2, parent), Bike(Bike) {}
|
||||
|
||||
int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
|
||||
QSettings settings;
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
bool rowerAsABike = !virtual_device_rower && dt == bluetoothdevice::ROWING;
|
||||
bool rowerAsABike = !virtual_device_rower && dt == ROWING;
|
||||
bool double_cadence = settings.value(QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double).toBool();
|
||||
double cadence_multiplier = 2.0;
|
||||
if (double_cadence)
|
||||
@@ -24,7 +24,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
if (normalizeWattage < 0)
|
||||
normalizeWattage = 0;
|
||||
|
||||
if (dt == bluetoothdevice::BIKE || rowerAsABike) {
|
||||
if (dt == BIKE || rowerAsABike) {
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
|
||||
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
|
||||
value.append((char)0x02); // heart rate
|
||||
@@ -44,7 +44,7 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
value.append(char(Bike->currentHeart().value())); // Actual value.
|
||||
value.append((char)0); // Bkool FTMS protocol HRM offset 1280 fix
|
||||
return CN_OK;
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL || dt == bluetoothdevice::ROWING) {
|
||||
} else if (dt == TREADMILL || dt == ELLIPTICAL || dt == ROWING) {
|
||||
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
|
||||
value.append((char)0x64); // speed, inst. cadence, resistance lvl, instant power
|
||||
value.append((char)0x02); // heart rate
|
||||
@@ -53,11 +53,11 @@ int CharacteristicNotifier2AD2::notify(QByteArray &value) {
|
||||
value.append((char)(normalizeSpeed >> 8) & 0xFF); // speed
|
||||
|
||||
uint16_t cadence = 0;
|
||||
if (dt == bluetoothdevice::ELLIPTICAL)
|
||||
if (dt == ELLIPTICAL)
|
||||
cadence = ((elliptical *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::TREADMILL)
|
||||
else if (dt == TREADMILL)
|
||||
cadence = ((treadmill *)Bike)->currentCadence().value();
|
||||
else if (dt == bluetoothdevice::ROWING)
|
||||
else if (dt == ROWING)
|
||||
cadence = ((rower *)Bike)->currentCadence().value();
|
||||
|
||||
value.append((char)((uint16_t)(cadence * cadence_multiplier) & 0xFF)); // cadence
|
||||
|
||||
@@ -10,7 +10,7 @@ CharacteristicWriteProcessor::CharacteristicWriteProcessor(double bikeResistance
|
||||
void CharacteristicWriteProcessor::changePower(uint16_t power) { Bike->changePower(power); }
|
||||
|
||||
void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr, uint8_t cw) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
QSettings settings;
|
||||
bool force_resistance =
|
||||
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
|
||||
@@ -64,7 +64,7 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
|
||||
qDebug() << "changeSlope CRR = " << fCRR << CRR_offset << "CW = " << fCW;
|
||||
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
if (dt == BIKE) {
|
||||
|
||||
// if the bike doesn't have the inclination by hardware, i'm simulating inclination with the value received
|
||||
// from Zwift
|
||||
@@ -82,9 +82,9 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
|
||||
Bike->changeResistance((resistance_t)(round(resistance * bikeResistanceGain)) + bikeResistanceOffset + 1 +
|
||||
CRR_offset + CW_offset); // resistance start from 1
|
||||
}
|
||||
} else if (dt == bluetoothdevice::TREADMILL) {
|
||||
} else if (dt == TREADMILL) {
|
||||
emit changeInclination(grade, percentage);
|
||||
} else if (dt == bluetoothdevice::ELLIPTICAL) {
|
||||
} else if (dt == ELLIPTICAL) {
|
||||
bool inclinationAvailableByHardware = ((elliptical *)Bike)->inclinationAvailableByHardware();
|
||||
qDebug() << "inclinationAvailableByHardware" << inclinationAvailableByHardware << "erg_mode" << erg_mode;
|
||||
emit changeInclination(grade, percentage);
|
||||
|
||||
@@ -13,8 +13,8 @@ CharacteristicWriteProcessor2AD9::CharacteristicWriteProcessor2AD9(double bikeRe
|
||||
|
||||
int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
|
||||
if (data.size()) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::BIKE || dt == bluetoothdevice::ROWING) {
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == BIKE || dt == ROWING) {
|
||||
QSettings settings;
|
||||
bool force_resistance =
|
||||
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
|
||||
@@ -82,7 +82,7 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
|
||||
reply.append((quint8)cmd);
|
||||
reply.append((quint8)FTMS_NOT_SUPPORTED);
|
||||
}
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
} else if (dt == TREADMILL || dt == ELLIPTICAL) {
|
||||
char a, b;
|
||||
if ((char)data.at(0) == 0x02) {
|
||||
// Set Target Speed
|
||||
@@ -91,7 +91,7 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
|
||||
|
||||
uint16_t uspeed = a + (((uint16_t)b) << 8);
|
||||
double requestSpeed = (double)uspeed / 100.0;
|
||||
if (dt == bluetoothdevice::TREADMILL) {
|
||||
if (dt == TREADMILL) {
|
||||
((treadmill *)Bike)->changeSpeed(requestSpeed);
|
||||
}
|
||||
qDebug() << QStringLiteral("new requested speed ") + QString::number(requestSpeed);
|
||||
@@ -103,10 +103,10 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
|
||||
int16_t sincline = a + (((int16_t)b) << 8);
|
||||
double requestIncline = (double)sincline / 10.0;
|
||||
|
||||
if (dt == bluetoothdevice::TREADMILL)
|
||||
if (dt == TREADMILL)
|
||||
((treadmill *)Bike)->changeInclination(requestIncline, requestIncline);
|
||||
// Resistance as incline on Sole E95s Elliptical #419
|
||||
else if (dt == bluetoothdevice::ELLIPTICAL) {
|
||||
else if (dt == ELLIPTICAL) {
|
||||
if(((elliptical *)Bike)->inclinationAvailableByHardware())
|
||||
((elliptical *)Bike)->changeInclination(requestIncline, requestIncline);
|
||||
else
|
||||
|
||||
@@ -13,8 +13,8 @@ CharacteristicWriteProcessorE005::CharacteristicWriteProcessorE005(double bikeRe
|
||||
|
||||
int CharacteristicWriteProcessorE005::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
|
||||
if (data.size()) {
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == bluetoothdevice::BIKE) {
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
if (dt == BIKE) {
|
||||
char cmd = data.at(0);
|
||||
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), data);
|
||||
if (cmd == wahookickrsnapbike::_setSimMode && data.count() >= 7) {
|
||||
@@ -35,7 +35,7 @@ int CharacteristicWriteProcessorE005::writeProcess(quint16 uuid, const QByteArra
|
||||
qDebug() << "erg mode" << watts;
|
||||
changePower(watts);
|
||||
}
|
||||
} else if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
|
||||
} else if (dt == TREADMILL || dt == ELLIPTICAL) {
|
||||
}
|
||||
reply.append((quint8)FTMS_RESPONSE_CODE);
|
||||
reply.append((quint8)data.at(0));
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
activiotreadmill::activiotreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
|
||||
double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
android_antbike::android_antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
antbike::antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -14,7 +14,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
apexbike::apexbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -19,7 +19,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
bhfitnesselliptical::bhfitnesselliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -211,7 +211,7 @@ resistance_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 1
|
||||
void bike::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
|
||||
void bike::powerSensor(uint16_t power) { m_watt.setValue(power, false); }
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
|
||||
BLUETOOTH_TYPE bike::deviceType() { return BIKE; }
|
||||
|
||||
void bike::clearStats() {
|
||||
|
||||
@@ -381,6 +381,8 @@ uint8_t bike::metrics_override_heartrate() {
|
||||
|
||||
bool bike::inclinationAvailableByHardware() { return false; }
|
||||
|
||||
bool bike::inclinationAvailableBySoftware() { return false; }
|
||||
|
||||
uint16_t bike::wattFromHR(bool useSpeedAndCadence) {
|
||||
QSettings settings;
|
||||
double watt = 0;
|
||||
|
||||
@@ -31,7 +31,7 @@ class bike : public bluetoothdevice {
|
||||
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
|
||||
virtual uint16_t powerFromResistanceRequest(resistance_t requestResistance);
|
||||
virtual bool ergManagedBySS2K() { return false; }
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
|
||||
BLUETOOTH_TYPE deviceType() override;
|
||||
metric pelotonResistance();
|
||||
void clearStats() override;
|
||||
void setLap() override;
|
||||
@@ -51,6 +51,7 @@ class bike : public bluetoothdevice {
|
||||
*/
|
||||
metric currentSteeringAngle() { return m_steeringAngle; }
|
||||
virtual bool inclinationAvailableByHardware();
|
||||
virtual bool inclinationAvailableBySoftware();
|
||||
bool ergModeSupportedAvailableByHardware() { return ergModeSupported; }
|
||||
virtual bool ergModeSupportedAvailableBySoftware() { return ergModeSupported; }
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
bkoolbike::bkoolbike(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
@@ -49,7 +49,12 @@ void bkoolbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
|
||||
}
|
||||
writeBuffer = new QByteArray((const char *)data, data_len);
|
||||
|
||||
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer);
|
||||
if (gattWriteCharCustomId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
|
||||
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer,
|
||||
QLowEnergyService::WriteWithoutResponse);
|
||||
} else {
|
||||
gattCustomService->writeCharacteristic(gattWriteCharCustomId, *writeBuffer);
|
||||
}
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
@@ -61,19 +66,28 @@ void bkoolbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStri
|
||||
|
||||
void bkoolbike::changePower(int32_t power) {
|
||||
RequestedPower = power;
|
||||
/*
|
||||
if (power < 0)
|
||||
power = 0;
|
||||
uint8_t p[] = {0xa4, 0x09, 0x4e, 0x05, 0x31, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x02, 0x00};
|
||||
p[10] = (uint8_t)((power * 4) & 0xFF);
|
||||
p[11] = (uint8_t)((power * 4) >> 8);
|
||||
for (uint8_t i = 0; i < sizeof(p) - 1; i++) {
|
||||
p[12] ^= p[i]; // the last byte is a sort of a checksum
|
||||
}
|
||||
|
||||
writeCharacteristic(p, sizeof(p), QStringLiteral("changePower"), false, false);*/
|
||||
if (power < 0) {
|
||||
power = 0;
|
||||
}
|
||||
|
||||
qDebug() << QStringLiteral("Changepower not implemented");
|
||||
forcePower(power);
|
||||
}
|
||||
|
||||
void bkoolbike::forcePower(int32_t power) {
|
||||
// FE-C "Set Target Power" command (page 0x31)
|
||||
// Power is sent in 1/4 watt units (0.25W resolution)
|
||||
// Bytes: [0x31][0x25][0xFF][0xFF][0xFF][0xFF][power_low][power_high]
|
||||
|
||||
uint16_t power_quarter_watts = (uint16_t)(power * 4);
|
||||
|
||||
uint8_t power_cmd[] = {0x31, 0x25, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00};
|
||||
power_cmd[6] = (uint8_t)(power_quarter_watts & 0xFF); // Low byte
|
||||
power_cmd[7] = (uint8_t)((power_quarter_watts >> 8) & 0xFF); // High byte
|
||||
|
||||
writeCharacteristic(power_cmd, sizeof(power_cmd),
|
||||
QStringLiteral("forcePower ") + QString::number(power) + QStringLiteral("W"),
|
||||
false, false);
|
||||
}
|
||||
|
||||
void bkoolbike::forceInclination(double inclination) {
|
||||
@@ -115,13 +129,27 @@ void bkoolbike::update() {
|
||||
uint8_t init1[] = {0x30, 0x25, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
|
||||
uint8_t init2[] = {0x32, 0x25, 0xff, 0xff, 0xff, 0x1e, 0x7f, 0x00};
|
||||
uint8_t init3[] = {0x33, 0x25, 0xff, 0xff, 0xff, 0x20, 0x4e, 0x00};
|
||||
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
|
||||
uint8_t init5[] = {0x37, 0xee, 0x16, 0xff, 0x80, 0x0c, 0x46, 0x21};
|
||||
writeCharacteristic(init1, sizeof(init1), QStringLiteral("init1"), false, false);
|
||||
writeCharacteristic(init2, sizeof(init2), QStringLiteral("init2"), false, false);
|
||||
writeCharacteristic(init3, sizeof(init3), QStringLiteral("init3"), false, false);
|
||||
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
|
||||
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
|
||||
|
||||
if (bkool_fitness_bike) {
|
||||
// BKOOLFITNESSBIKE specific init packets
|
||||
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
|
||||
uint8_t init5[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
|
||||
uint8_t init6[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
|
||||
uint8_t init7[] = {0x32, 0x25, 0xff, 0xff, 0xff, 0x25, 0x7f, 0x00};
|
||||
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
|
||||
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
|
||||
writeCharacteristic(init6, sizeof(init6), QStringLiteral("init6"), false, true);
|
||||
writeCharacteristic(init7, sizeof(init7), QStringLiteral("init7"), false, false);
|
||||
} else {
|
||||
// BKOOLSMARTPRO init packets
|
||||
uint8_t init4[] = {0x37, 0x4c, 0x1d, 0xff, 0x80, 0x0c, 0x46, 0x21};
|
||||
uint8_t init5[] = {0x37, 0xee, 0x16, 0xff, 0x80, 0x0c, 0x46, 0x21};
|
||||
writeCharacteristic(init4, sizeof(init4), QStringLiteral("init4"), false, true);
|
||||
writeCharacteristic(init5, sizeof(init5), QStringLiteral("init5"), false, true);
|
||||
}
|
||||
|
||||
} else if (bluetoothDevice.isValid() &&
|
||||
m_control->state() == QLowEnergyController::DiscoveredState //&&
|
||||
@@ -137,6 +165,13 @@ void bkoolbike::update() {
|
||||
// updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
// Send poll command for BKOOLFITNESSBIKE
|
||||
/*
|
||||
if (bkool_fitness_bike) {
|
||||
uint8_t poll[] = {0x37, 0xc8, 0x19, 0xff, 0xe0, 0x0a, 0x46, 0x21};
|
||||
writeCharacteristic(poll, sizeof(poll), QStringLiteral("poll"), false, false);
|
||||
}*/
|
||||
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance != currentResistance().value() || lastGearValue != gears()) {
|
||||
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
|
||||
@@ -146,10 +181,15 @@ void bkoolbike::update() {
|
||||
requestInclination = requestResistance / 10.0;
|
||||
}
|
||||
// forceResistance(requestResistance);;
|
||||
}
|
||||
lastGearValue = gears();
|
||||
}
|
||||
requestResistance = -1;
|
||||
}
|
||||
|
||||
if(lastGearValue != gears() && requestInclination == -100) {
|
||||
// if only gears changed, we need to update the inclination to match the gears
|
||||
requestInclination = lastRawRequestedInclinationValue;
|
||||
}
|
||||
|
||||
if (requestInclination != -100) {
|
||||
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
|
||||
forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
|
||||
@@ -157,6 +197,8 @@ void bkoolbike::update() {
|
||||
requestInclination = -100;
|
||||
}
|
||||
|
||||
lastGearValue = gears();
|
||||
|
||||
if (requestPower != -1) {
|
||||
changePower(requestPower);
|
||||
requestPower = -1;
|
||||
@@ -677,6 +719,12 @@ void bkoolbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
{
|
||||
bluetoothDevice = device;
|
||||
|
||||
// Check if this is BKOOLFITNESSBIKE model
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("BKOOLFITNESSBIKE"))) {
|
||||
bkool_fitness_bike = true;
|
||||
emit debug(QStringLiteral("BKOOLFITNESSBIKE model detected"));
|
||||
}
|
||||
|
||||
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
|
||||
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &bkoolbike::serviceDiscovered);
|
||||
connect(m_control, &QLowEnergyController::discoveryFinished, this, &bkoolbike::serviceScanDone);
|
||||
|
||||
@@ -45,6 +45,7 @@ class bkoolbike : public bike {
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void forceInclination(double inclination);
|
||||
void forcePower(int32_t power);
|
||||
uint16_t watts() override;
|
||||
double bikeResistanceToPeloton(double resistance);
|
||||
|
||||
@@ -70,6 +71,8 @@ class bkoolbike : public bike {
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
bool bkool_fitness_bike = false;
|
||||
uint16_t pollCounter = 0;
|
||||
|
||||
uint16_t oldLastCrankEventTime = 0;
|
||||
uint16_t oldCrankRevs = 0;
|
||||
|
||||
@@ -685,7 +685,19 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
|
||||
filter = (b.name().compare(filterDevice, Qt::CaseInsensitive) == 0);
|
||||
}
|
||||
if (b.name().startsWith(QStringLiteral("M3")) && !m3iBike && filter) {
|
||||
const QString deviceName = b.name();
|
||||
const QString upperDeviceName = deviceName.toUpper();
|
||||
bool isTrxAppGateUsbBikeTC = false;
|
||||
if (upperDeviceName.startsWith(QStringLiteral("TC")) && deviceName.length() == 5) {
|
||||
isTrxAppGateUsbBikeTC = true;
|
||||
for (int idx = 2; idx < deviceName.length(); ++idx) {
|
||||
if (!deviceName.at(idx).isDigit()) {
|
||||
isTrxAppGateUsbBikeTC = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deviceName.startsWith(QStringLiteral("M3")) && !m3iBike && filter) {
|
||||
|
||||
if (m3ibike::isCorrectUnit(b)) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1001,7 +1013,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(domyosRower);
|
||||
} else if ((b.name().startsWith(QStringLiteral("Domyos-Bike")) && (!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool())) &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && filter) {
|
||||
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosBike && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
domyosBike = new domyosbike(noWriteResistance, noHeartService, testResistance, bikeResistanceOffset,
|
||||
@@ -1016,7 +1028,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(domyosBike);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower &&
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("MRK-R11S-")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower)) &&
|
||||
!trxappgateusbRower && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1070,6 +1083,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(domyosElliptical);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SCH411/510E")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FEIER-EM-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("MX-AS ")) ||
|
||||
@@ -1079,7 +1093,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical) ||
|
||||
!b.name().compare(ftms_elliptical, Qt::CaseInsensitive)) && !ypooElliptical && filter) {
|
||||
!b.name().compare(ftms_elliptical, Qt::CaseInsensitive)) && !ypooElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
ypooElliptical =
|
||||
@@ -1230,6 +1244,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
this->signalBluetoothDeviceConnected(soleElliptical);
|
||||
} else if (b.name().startsWith(QStringLiteral("Domyos")) &&
|
||||
!b.name().startsWith(QStringLiteral("DomyosBr")) &&
|
||||
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKE-")) &&
|
||||
!b.name().toUpper().startsWith(QStringLiteral("DOMYOS-BIKING-")) && !domyos && !domyosElliptical && b.name().compare(ftms_treadmill, Qt::CaseInsensitive) &&
|
||||
!domyosBike && !domyosRower && !ftmsBike && !horizonTreadmill &&
|
||||
(!deviceHasService(b, QBluetoothUuid((quint16)0x1826)) || settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) &&
|
||||
@@ -1487,6 +1502,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("AB300S-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && !b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) || // FIT-1596 and sports tech f37s treadmill #2412
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT-TM-")) || // FIT-TM- treadmill with real inclination
|
||||
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
|
||||
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
|
||||
(b.name().toUpper().startsWith("SCHWINN 810")) ||
|
||||
@@ -1496,6 +1512,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("ANPIUS-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("KICKR RUN")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("SPERAX_RM-01")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
|
||||
(b.name().toUpper().startsWith(QStringLiteral("KS-AP-"))) || // Kingsmith WalkingPad R3 Hybrid+
|
||||
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
|
||||
@@ -1511,12 +1528,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("CITYSPORTS-LINKER")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15 && ftms_bike.contains(QZSettings::default_ftms_bike)) || // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("S77")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) || // FTMS
|
||||
b.name().toUpper().startsWith(QStringLiteral("TM XP_")) // FTMS
|
||||
) &&
|
||||
!horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1710,6 +1728,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("KETTLERBLE")) ||
|
||||
(b.name().toUpper().startsWith("JAS_C3")) ||
|
||||
(b.name().toUpper().startsWith("SCH_190U")) ||
|
||||
(b.name().toUpper().startsWith("SCH_290R")) ||
|
||||
(b.name().toUpper().startsWith("RAVE WHITE")) ||
|
||||
(b.name().toUpper().startsWith("DOMYOS-BIKING-")) ||
|
||||
(b.name().startsWith(QStringLiteral("Domyos-Bike")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool()) ||
|
||||
@@ -1754,21 +1773,27 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("MRK-S26S-")) ||
|
||||
(b.name().toUpper().startsWith("MRK-S26C-")) ||
|
||||
(b.name().toUpper().startsWith("ROBX")) ||
|
||||
(b.name().toUpper().startsWith("ORLAUF_ARES")) ||
|
||||
(b.name().toUpper().startsWith("SPEEDMAGPRO")) ||
|
||||
(b.name().toUpper().startsWith("XCX-")) ||
|
||||
(b.name().toUpper().startsWith("SMARTBIKE-")) ||
|
||||
(b.name().toUpper().startsWith("D500V2")) ||
|
||||
(b.name().toUpper().startsWith("NEO BIKE PLUS ")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("PM5")) && !b.name().toUpper().endsWith(QStringLiteral("SKI")) && !b.name().toUpper().endsWith(QStringLiteral("ROW"))) ||
|
||||
(b.name().toUpper().startsWith("L-") && b.name().length() == 11) ||
|
||||
(b.name().toUpper().startsWith("DMASUN-") && b.name().toUpper().endsWith("-BIKE")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
|
||||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
|
||||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
|
||||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
|
||||
(b.name().toUpper().startsWith("FS-YK-")) ||
|
||||
(b.name().toUpper().startsWith("ZUMO")) || (b.name().toUpper().startsWith("XS08-")) ||
|
||||
(b.name().toUpper().startsWith("B94")) || (b.name().toUpper().startsWith("STAGES BIKE")) ||
|
||||
(b.name().toUpper().startsWith("SUITO")) || (b.name().toUpper().startsWith("D2RIDE")) ||
|
||||
(b.name().toUpper().startsWith("DIRETO X")) || (b.name().toUpper().startsWith("MERACH-667-")) ||
|
||||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE")) ||
|
||||
(b.name().toUpper().startsWith("YPBM") && b.name().length() == 10)) &&
|
||||
ftms_rower.contains(QZSettings::default_ftms_rower) &&
|
||||
!ftmsBike && !ftmsRower && !snodeBike && !fitPlusBike && !stagesBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -1852,6 +1877,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith(QStringLiteral("RM")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DR")) && b.name().length() == 2) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("THINK A")) && b.name().length() == 18) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
|
||||
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
|
||||
!stagesBike && !ftmsBike && filter) {
|
||||
@@ -2064,7 +2090,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(apexBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
|
||||
apexBike->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(apexBike);
|
||||
} else if (b.name().toUpper().startsWith(QStringLiteral("BKOOLSMARTPRO")) && !bkoolBike && filter) {
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("BKOOLSMARTPRO")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BKOOLFBIKE")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BKOOLFITNESSBIKE"))) && !bkoolBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
bkoolBike = new bkoolbike(noWriteResistance, noHeartService);
|
||||
@@ -2444,15 +2472,15 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(activioTreadmill, &activiotreadmill::debug, this, &bluetooth::debug);
|
||||
activioTreadmill->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(activioTreadmill);
|
||||
} else if (((b.name().startsWith(QStringLiteral("TOORX"))) ||
|
||||
(b.name().startsWith(QStringLiteral("V-RUN"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("K80_"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ICONSOLE+"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("I-RUNNING"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("DKN RUN"))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("ADIDAS "))) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK")))) &&
|
||||
} else if (((deviceName.startsWith(QStringLiteral("TOORX"))) ||
|
||||
(deviceName.startsWith(QStringLiteral("V-RUN"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("K80_"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("I-CONSOLE+"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("ICONSOLE+"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("I-RUNNING"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("DKN RUN"))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("ADIDAS "))) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("REEBOK")))) &&
|
||||
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical && !iconsole_rower && ftms_elliptical.contains(QZSettings::default_ftms_elliptical) &&
|
||||
ftms_bike.contains(QZSettings::default_ftms_bike) &&
|
||||
filter) {
|
||||
@@ -2466,22 +2494,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
connect(trxappgateusb, &trxappgateusbtreadmill::debug, this, &bluetooth::debug);
|
||||
trxappgateusb->deviceDiscovered(b);
|
||||
this->signalBluetoothDeviceConnected(trxappgateusb);
|
||||
} else if ((b.name().toUpper().startsWith(QStringLiteral("TUN ")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FITHIWAY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("FIT HI WAY")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("BIKZU_")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("PASYOU-")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("VIRTUFIT")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("IBIKING+")) ||
|
||||
((b.name().startsWith(QStringLiteral("TOORX")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("ICONSOLE+")) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("VIFHTR2.1")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK"))) ||
|
||||
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
|
||||
(b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
|
||||
b.name().toUpper().startsWith(QStringLiteral("DKN MOTION"))) &&
|
||||
} else if (isTrxAppGateUsbBikeTC ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("TUN ")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("FITHIWAY")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("FIT HI WAY")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("BIKZU_")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("PASYOU-")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("VIRTUFIT")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("IBIKING+")) ||
|
||||
((deviceName.startsWith(QStringLiteral("TOORX")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("I-CONSOIE+")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("I-CONSOLE+")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("ICONSOLE+")) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("VIFHTR2.1")) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("REEBOK"))) ||
|
||||
upperDeviceName.contains(QStringLiteral("CR011R")) ||
|
||||
(upperDeviceName.startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
|
||||
upperDeviceName.startsWith(QStringLiteral("DKN MOTION"))) &&
|
||||
(toorx_bike))) &&
|
||||
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower && ftms_elliptical.contains(QZSettings::default_ftms_elliptical)) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
@@ -2587,7 +2616,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (((b.name().startsWith(QStringLiteral("FS-")) && fitplus_bike) ||
|
||||
(b.name().toUpper().startsWith("H9110 OSAKA")) ||
|
||||
b.name().startsWith(QStringLiteral("MRK-"))) &&
|
||||
!fitPlusBike && !ftmsBike && !ftmsRower && !snodeBike && filter) {
|
||||
!fitPlusBike && !ftmsBike && !ftmsRower && !snodeBike && !horizonTreadmill && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
fitPlusBike =
|
||||
@@ -2734,11 +2763,11 @@ void bluetooth::connectedAndDiscovered() {
|
||||
settings.value(QZSettings::fitmetria_fanfit_enable, QZSettings::default_fitmetria_fanfit_enable).toBool();
|
||||
|
||||
// only at the first very connection, setting the user default resistance
|
||||
if (device() && firstConnected && device()->deviceType() == bluetoothdevice::BIKE &&
|
||||
if (device() && firstConnected && device()->deviceType() == BIKE &&
|
||||
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt() != 1) {
|
||||
qobject_cast<bike *>(device())->changeResistance(
|
||||
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt());
|
||||
} else if (device() && firstConnected && device()->deviceType() == bluetoothdevice::ELLIPTICAL &&
|
||||
} else if (device() && firstConnected && device()->deviceType() == ELLIPTICAL &&
|
||||
settings.value(QZSettings::bike_resistance_start, QZSettings::default_bike_resistance_start).toUInt() !=
|
||||
1) {
|
||||
qobject_cast<elliptical *>(device())->changeResistance(
|
||||
@@ -2906,7 +2935,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
#else
|
||||
settings.setValue(QZSettings::power_sensor_address, b.deviceUuid().toString());
|
||||
#endif
|
||||
if (device() && device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
if (device() && device()->deviceType() == BIKE) {
|
||||
powerSensor = new stagesbike(false, false, true);
|
||||
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
|
||||
@@ -2915,7 +2944,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
connect(powerSensor, &bluetoothdevice::cadenceChanged, this->device(),
|
||||
&bluetoothdevice::cadenceSensor);
|
||||
powerSensor->deviceDiscovered(b);
|
||||
} else if (device() && device()->deviceType() == bluetoothdevice::TREADMILL) {
|
||||
} else if (device() && device()->deviceType() == TREADMILL) {
|
||||
powerSensorRun = new strydrunpowersensor(false, false, true);
|
||||
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
|
||||
@@ -2972,7 +3001,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().startsWith(eliteSterzoSmartName))) && !eliteSterzoSmart &&
|
||||
!eliteSterzoSmartName.startsWith(QStringLiteral("Disabled")) && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
this->device()->deviceType() == BIKE) {
|
||||
settings.setValue(QZSettings::elite_sterzo_smart_lastdevice_name, b.name());
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
@@ -2994,7 +3023,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
if(settings.value(QZSettings::sram_axs_controller, QZSettings::default_sram_axs_controller).toBool()) {
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().toUpper().startsWith("SRAM "))) && !sramAXSController && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
this->device()->deviceType() == BIKE) {
|
||||
|
||||
sramAXSController = new sramaxscontroller();
|
||||
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
@@ -3013,7 +3042,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
if(settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool()) {
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().toUpper().startsWith("ZWIFT CLICK"))) && !zwiftClickRemote && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
this->device()->deviceType() == BIKE) {
|
||||
|
||||
if(b.manufacturerData(2378).size() > 0) {
|
||||
qDebug() << "this should be 9. is it? " << int(b.manufacturerData(2378).at(0));
|
||||
@@ -3038,7 +3067,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().toUpper().startsWith("SQUARE"))) && !eliteSquareController && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
this->device()->deviceType() == BIKE) {
|
||||
|
||||
eliteSquareController = new elitesquarecontroller(this->device());
|
||||
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
@@ -3058,7 +3087,7 @@ void bluetooth::connectedAndDiscovered() {
|
||||
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
|
||||
this->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
this->device()->deviceType() == BIKE) {
|
||||
|
||||
if(b.manufacturerData(2378).size() > 0) {
|
||||
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
|
||||
@@ -3101,8 +3130,8 @@ void bluetooth::connectedAndDiscovered() {
|
||||
settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool(),
|
||||
settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool(),
|
||||
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool(),
|
||||
device()->deviceType() == bluetoothdevice::TREADMILL ||
|
||||
device()->deviceType() == bluetoothdevice::ELLIPTICAL,
|
||||
device()->deviceType() == TREADMILL ||
|
||||
device()->deviceType() == ELLIPTICAL,
|
||||
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool(),
|
||||
settings.value(QZSettings::technogym_group_cycle, QZSettings::default_technogym_group_cycle).toBool(),
|
||||
settings.value(QZSettings::ant_bike_device_number, QZSettings::default_ant_bike_device_number).toInt(),
|
||||
@@ -4115,7 +4144,7 @@ void bluetooth::stateFileUpdate() {
|
||||
if (!device()) {
|
||||
return;
|
||||
}
|
||||
if (device()->deviceType() != bluetoothdevice::TREADMILL) {
|
||||
if (device()->deviceType() != TREADMILL) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ bluetoothdevice::~bluetoothdevice() {
|
||||
}
|
||||
}
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE bluetoothdevice::deviceType() { return bluetoothdevice::UNKNOWN; }
|
||||
BLUETOOTH_TYPE bluetoothdevice::deviceType() { return UNKNOWN; }
|
||||
void bluetoothdevice::start() { requestStart = 1; lastStart = QDateTime::currentMSecsSinceEpoch(); }
|
||||
void bluetoothdevice::stop(bool pause) {
|
||||
requestStop = 1;
|
||||
@@ -177,7 +177,17 @@ bool bluetoothdevice::changeFanSpeed(uint8_t speed) {
|
||||
}
|
||||
bool bluetoothdevice::connected() { return false; }
|
||||
metric bluetoothdevice::elevationGain() { return elevationAcc; }
|
||||
void bluetoothdevice::heartRate(uint8_t heart) { Heart.setValue(heart); }
|
||||
void bluetoothdevice::heartRate(uint8_t heart) {
|
||||
Heart.setValue(heart);
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
// Write heart rate from Bluetooth to Apple Health during workout
|
||||
lockscreen h;
|
||||
if(heart > 0)
|
||||
h.setHeartRate(heart);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
void bluetoothdevice::coreBodyTemperature(double coreBodyTemperature) { CoreBodyTemperature.setValue(coreBodyTemperature); }
|
||||
void bluetoothdevice::skinTemperature(double skinTemperature) { SkinTemperature.setValue(skinTemperature); }
|
||||
void bluetoothdevice::heatStrainIndex(double heatStrainIndex) { HeatStrainIndex.setValue(heatStrainIndex); }
|
||||
@@ -237,7 +247,7 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts, const b
|
||||
!power_as_bike && !power_as_treadmill)
|
||||
watt_calc = false;
|
||||
|
||||
if(deviceType() == bluetoothdevice::BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
|
||||
if(deviceType() == BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
|
||||
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
|
||||
|
||||
if (!_firstUpdate && !paused) {
|
||||
@@ -332,7 +342,8 @@ void bluetoothdevice::update_hr_from_external() {
|
||||
double kcal = calories().value();
|
||||
if(kcal < 0)
|
||||
kcal = 0;
|
||||
h.workoutTrackingUpdate(Speed.value(), Cadence.value(), (uint16_t)m_watt.value(), kcal, StepCount.value(), deviceType(), odometer() * 1000.0, totalCalories().value());
|
||||
bool useMiles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
|
||||
h.workoutTrackingUpdate(Speed.value(), Cadence.value(), (uint16_t)m_watt.value(), kcal, StepCount.value(), deviceType(), odometer() * 1000.0, totalCalories().value(), useMiles);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef BLUETOOTHDEVICE_H
|
||||
#define BLUETOOTHDEVICE_H
|
||||
|
||||
#include "bluetoothdevicetype.h"
|
||||
#include "definitions.h"
|
||||
#include "metric.h"
|
||||
#include "qzsettings.h"
|
||||
@@ -451,7 +452,6 @@ class bluetoothdevice : public QObject {
|
||||
*/
|
||||
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
|
||||
|
||||
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE, STAIRCLIMBER };
|
||||
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ bowflext216treadmill::bowflext216treadmill(uint32_t pollDeviceTime, bool noConso
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -23,7 +23,7 @@ bowflextreadmill::bowflextreadmill(uint32_t pollDeviceTime, bool noConsole, bool
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
//#include <QtBluetooth/private/qlowenergyserviceprivate_p.h>
|
||||
|
||||
chronobike::chronobike(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
t_timeout = new QTimer(this);
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
target_watts.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
concept2skierg::concept2skierg(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -23,7 +23,7 @@ crossrope::crossrope(uint32_t pollDeviceTime, bool noConsole, bool noHeartServic
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -4,7 +4,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
csafeelliptical::csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice,
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
csaferower::csaferower(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
cscbike::cscbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
cycleopsphantombike::cycleopsphantombike(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
deerruntreadmill::deerruntreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
|
||||
double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
@@ -75,6 +75,14 @@ void deerruntreadmill::writeCharacteristic(const QLowEnergyCharacteristic charac
|
||||
}
|
||||
}
|
||||
|
||||
void deerruntreadmill::waitForAPacket() {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
connect(this, &deerruntreadmill::packetReceived, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(3000, &loop, SLOT(quit()));
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
void deerruntreadmill::writeUnlockCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
@@ -122,18 +130,70 @@ uint8_t deerruntreadmill::calculateXOR(uint8_t arr[], size_t size) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t deerruntreadmill::calculatePitPatChecksum(uint8_t arr[], size_t size) {
|
||||
uint8_t result = 0;
|
||||
|
||||
if (size < 5) {
|
||||
qDebug() << QStringLiteral("array too small for PitPat checksum");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// For PitPat protocol:
|
||||
// 1. XOR from byte 5 to byte (size - 3) for long messages (>= 7 bytes)
|
||||
// or from byte 2 to byte (size - 3) for short messages (< 7 bytes)
|
||||
// 2. XOR the result with byte 1
|
||||
size_t startIdx = (size < 7) ? 2 : 5;
|
||||
|
||||
for (size_t i = startIdx; i <= size - 3; i++) {
|
||||
result ^= arr[i];
|
||||
}
|
||||
|
||||
// XOR with byte 1 (command byte)
|
||||
result ^= arr[1];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void deerruntreadmill::forceSpeed(double requestSpeed) {
|
||||
QSettings settings;
|
||||
uint8_t writeSpeed[] = {0x4d, 0x00, 0xc9, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd8, 0x43};
|
||||
|
||||
writeSpeed[2] = pollCounter;
|
||||
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
|
||||
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
|
||||
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
|
||||
if (pitpat) {
|
||||
// PitPat speed template
|
||||
// Pattern: 6a 17 00 00 00 00 [speed_high] [speed_low] 01 00 8a 00 04 00 00 00 00 00 12 2e 0c [checksum] 43
|
||||
// Speed encoding: speed value * 1000 (e.g., 2.0 km/h = 2000 = 0x07d0)
|
||||
uint8_t writeSpeed[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x07, 0x6c, 0x01, 0x00, 0x8a, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x2e, 0x0c, 0xc3, 0x43};
|
||||
|
||||
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
|
||||
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
|
||||
uint16_t speed = (uint16_t)(requestSpeed * 1000.0);
|
||||
writeSpeed[6] = (speed >> 8) & 0xFF; // High byte
|
||||
writeSpeed[7] = speed & 0xFF; // Low byte
|
||||
writeSpeed[21] = calculatePitPatChecksum(writeSpeed, sizeof(writeSpeed)); // Checksum at byte 21
|
||||
|
||||
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
|
||||
QStringLiteral("forceSpeed PitPat speed=") + QString::number(requestSpeed), false, true);
|
||||
} else if (superun_ba04) {
|
||||
// Superun BA04 speed template
|
||||
uint8_t writeSpeed[] = {0x4d, 0x00, 0x14, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb5, 0x7c, 0xdb, 0x43};
|
||||
|
||||
writeSpeed[2] = pollCounter;
|
||||
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
|
||||
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
|
||||
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
|
||||
|
||||
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
|
||||
QStringLiteral("forceSpeed BA04 speed=") + QString::number(requestSpeed), false, false);
|
||||
} else {
|
||||
// Default speed template
|
||||
uint8_t writeSpeed[] = {0x4d, 0x00, 0xc9, 0x17, 0x6a, 0x17, 0x02, 0x00, 0x06, 0x40, 0x04, 0x4c, 0x01, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd8, 0x43};
|
||||
|
||||
writeSpeed[2] = pollCounter;
|
||||
writeSpeed[10] = ((int)((requestSpeed * 100)) >> 8) & 0xFF;
|
||||
writeSpeed[11] = ((int)((requestSpeed * 100))) & 0xFF;
|
||||
writeSpeed[25] = calculateXOR(writeSpeed, sizeof(writeSpeed));
|
||||
|
||||
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
|
||||
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void deerruntreadmill::forceIncline(double requestIncline) {
|
||||
@@ -216,8 +276,7 @@ void deerruntreadmill::update() {
|
||||
}
|
||||
|
||||
if (pitpat) {
|
||||
uint8_t startData[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x43};
|
||||
writeCharacteristic(gattWriteCharacteristic, startData, sizeof(startData), QStringLiteral("pitpat start"), false, true);
|
||||
forceSpeed(1.0);
|
||||
} else {
|
||||
// should be:
|
||||
// 0x49 = inited
|
||||
@@ -240,13 +299,16 @@ void deerruntreadmill::update() {
|
||||
emit tapeStarted();
|
||||
} else if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping... ") + paused);
|
||||
/*if (lastState == PAUSED) {
|
||||
uint8_t pause[] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x07};
|
||||
|
||||
writeCharacteristic(gattWriteCharacteristic, pause, sizeof(pause), QStringLiteral("pause"), false,
|
||||
true);
|
||||
|
||||
} else*/ {
|
||||
if (pitpat) {
|
||||
uint8_t stop[] = {
|
||||
0x6a, 0x17, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x05, 0x00,
|
||||
0x8a, 0x00, 0x02, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x12, 0x2e,
|
||||
0x0c, 0xaa, 0x43};
|
||||
writeCharacteristic(gattWriteCharacteristic, stop, sizeof(stop), QStringLiteral("stop"), false, true);
|
||||
} else {
|
||||
uint8_t stop[] = {0x4d, 0x00, 0x48, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x50, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x85, 0x11, 0xd6, 0x43};
|
||||
stop[2] = pollCounter;
|
||||
|
||||
@@ -392,15 +454,37 @@ void deerruntreadmill::btinit(bool startTape) {
|
||||
// PitPat treadmill initialization sequence
|
||||
uint8_t initData1[] = {0x6a, 0x05, 0xfd, 0xf8, 0x43};
|
||||
writeCharacteristic(gattWriteCharacteristic, initData1, sizeof(initData1), QStringLiteral("pitpat init 1"), false, true);
|
||||
|
||||
|
||||
uint8_t unlockData[] = {0x6b, 0x05, 0x9d, 0x98, 0x43};
|
||||
writeUnlockCharacteristic(unlockData, sizeof(unlockData), QStringLiteral("pitpat unlock"), false);
|
||||
|
||||
|
||||
uint8_t initData2[] = {0x6a, 0x05, 0xd7, 0xd2, 0x43};
|
||||
writeCharacteristic(gattWriteCharacteristic, initData2, sizeof(initData2), QStringLiteral("pitpat init 2"), false, true);
|
||||
|
||||
|
||||
uint8_t startData[] = {0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0x43};
|
||||
writeCharacteristic(gattWriteCharacteristic, startData, sizeof(startData), QStringLiteral("pitpat start"), false, true);
|
||||
} else if (superun_ba04) {
|
||||
// Superun BA04 treadmill initialization sequence
|
||||
// Wait for initial packet from treadmill before sending init
|
||||
emit debug(QStringLiteral("BA04: waiting for initial packet..."));
|
||||
waitForAPacket();
|
||||
|
||||
// Init 1: pollCounter = 0
|
||||
uint8_t initData1[] = {0x4d, 0x00, 0x00, 0x05, 0x6a, 0x05, 0xfd, 0xf8, 0x43};
|
||||
initData1[2] = 0; // pollCounter = 0
|
||||
writeCharacteristic(gattWriteCharacteristic, initData1, sizeof(initData1), QStringLiteral("BA04 init 1"), false, true);
|
||||
|
||||
uint8_t initData2[] = {0x4d, 0x00, 0x00, 0x05, 0x6a, 0x05, 0xfd, 0xf8, 0x43};
|
||||
initData1[2] = 1; // pollCounter = 0
|
||||
writeCharacteristic(gattWriteCharacteristic, initData2, sizeof(initData2), QStringLiteral("BA04 init 2"), false, true);
|
||||
|
||||
// Init 2: pollCounter = 1
|
||||
uint8_t initData3[] = {0x4d, 0x00, 0x01, 0x17, 0x6a, 0x17, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe8, 0x05, 0x00, 0x50, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xb5, 0x7c, 0x7c, 0x43};
|
||||
initData3[2] = 2; // pollCounter = 1
|
||||
writeCharacteristic(gattWriteCharacteristic, initData3, sizeof(initData3), QStringLiteral("BA04 init 3"), false, true);
|
||||
|
||||
// Start pollCounter from 2 after init
|
||||
pollCounter = 3;
|
||||
}
|
||||
initDone = true;
|
||||
}
|
||||
@@ -413,6 +497,8 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
QBluetoothUuid _gattNotifyCharacteristicId((quint16)0xfff2);
|
||||
QBluetoothUuid _pitpatWriteCharacteristicId((quint16)0xfba1);
|
||||
QBluetoothUuid _pitpatNotifyCharacteristicId((quint16)0xfba2);
|
||||
QBluetoothUuid _superunWriteCharacteristicId((quint16)0xff01);
|
||||
QBluetoothUuid _superunNotifyCharacteristicId((quint16)0xff02);
|
||||
QBluetoothUuid _unlockCharacteristicId((quint16)0x2b2a);
|
||||
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
|
||||
@@ -427,7 +513,7 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
qDebug() << QStringLiteral("unlock char uuid") << c.uuid() << QStringLiteral("handle") << c.handle()
|
||||
<< c.properties();
|
||||
}
|
||||
|
||||
|
||||
unlock_characteristic = unlock_service->characteristic(_unlockCharacteristicId);
|
||||
if (unlock_characteristic.isValid()) {
|
||||
emit debug(QStringLiteral("unlock characteristic found"));
|
||||
@@ -445,6 +531,9 @@ void deerruntreadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
if (pitpat) {
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_pitpatWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_pitpatNotifyCharacteristicId);
|
||||
} else if (superun_ba04) {
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_superunWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_superunNotifyCharacteristicId);
|
||||
} else {
|
||||
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
|
||||
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
|
||||
@@ -488,6 +577,7 @@ void deerruntreadmill::characteristicWritten(const QLowEnergyCharacteristic &cha
|
||||
void deerruntreadmill::serviceScanDone(void) {
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
|
||||
QBluetoothUuid _pitpatServiceId((quint16)0xfba0);
|
||||
QBluetoothUuid _superunServiceId((quint16)0xffff);
|
||||
QBluetoothUuid _unlockServiceId((quint16)0x1801);
|
||||
emit debug(QStringLiteral("serviceScanDone"));
|
||||
|
||||
@@ -503,6 +593,11 @@ void deerruntreadmill::serviceScanDone(void) {
|
||||
emit debug(QStringLiteral("Detected pitpat treadmill variant"));
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_pitpatServiceId);
|
||||
unlock_service = m_control->createServiceObject(_unlockServiceId);
|
||||
} else if (services_list.contains(_superunServiceId)) {
|
||||
superun_ba04 = true;
|
||||
pitpat = false;
|
||||
emit debug(QStringLiteral("Detected Superun BA04 treadmill variant"));
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_superunServiceId);
|
||||
} else {
|
||||
pitpat = false;
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
|
||||
@@ -41,6 +41,7 @@ class deerruntreadmill : public treadmill {
|
||||
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
|
||||
bool connected() override;
|
||||
double minStepInclination() override;
|
||||
double minStepSpeed() override { return 0.1; }
|
||||
|
||||
private:
|
||||
void forceSpeed(double requestSpeed);
|
||||
@@ -49,8 +50,10 @@ class deerruntreadmill : public treadmill {
|
||||
void writeCharacteristic(const QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len,
|
||||
const QString &info, bool disable_log = false, bool wait_for_response = false);
|
||||
void writeUnlockCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false);
|
||||
void waitForAPacket();
|
||||
void startDiscover();
|
||||
uint8_t calculateXOR(uint8_t arr[], size_t size);
|
||||
uint8_t calculatePitPatChecksum(uint8_t arr[], size_t size);
|
||||
bool noConsole = false;
|
||||
bool noHeartService = false;
|
||||
uint32_t pollDeviceTime = 200;
|
||||
@@ -70,8 +73,9 @@ class deerruntreadmill : public treadmill {
|
||||
|
||||
QLowEnergyService *unlock_service = nullptr;
|
||||
QLowEnergyCharacteristic unlock_characteristic;
|
||||
|
||||
|
||||
bool pitpat = false;
|
||||
bool superun_ba04 = false;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
|
||||
OP(CYCLING_POWER, 0x1818, WAHOO_KICKR, P1, P2, P3) \
|
||||
OP(CYCLING_SPEED_AND_CADENCE, 0x1816, WAHOO_KICKR, P1, P2, P3) \
|
||||
OP(RUNNING_SPEED_AND_CADENCE, 0x1814, WAHOO_TREADMILL, P1, P2, P3) \
|
||||
OP(HEART_RATE, 0x180D, WAHOO_BLUEHR, P1, P2, P3) \
|
||||
/* OP(HEART_RATE, 0x180D, WAHOO_BLUEHR, P1, P2, P3) */ \
|
||||
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3)
|
||||
|
||||
#define ZWIFT_ENABLED_OP(OP, DESC, UUID, MACHINE, P1, P2, P3) OP(DESC, UUID, MACHINE, P1, P2, P3)
|
||||
@@ -23,7 +23,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
#define DM_MACHINE_OP(OP, P1, P2, P3) \
|
||||
OP(WAHOO_KICKR, "Wahoo KICKR $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
|
||||
OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) \
|
||||
/* OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) */ \
|
||||
OP(WAHOO_RPM_SPEED, "Wahoo SPEED $uuid_hex$", DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
|
||||
OP(WAHOO_TREADMILL, "Wahoo TREAD $uuid_hex$", DM_MACHINE_TYPE_TREADMILL, P1, P2, P3)
|
||||
|
||||
@@ -43,7 +43,7 @@ using namespace std::chrono_literals;
|
||||
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0x2AD6, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x0A\x00\x96\x00\x0A\x00"), \
|
||||
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE | DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0xE005, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_E005, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0x2AD2, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_CYCLE, 0x2AD3, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x00\x01"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
@@ -51,7 +51,7 @@ using namespace std::chrono_literals;
|
||||
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_TREADMILL, 0x2AD6, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x0A\x00\x96\x00\x0A\x00"), \
|
||||
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
OP(FITNESS_MACHINE_TREADMILL, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9T, P1, P2, \
|
||||
OP(FITNESS_MACHINE_TREADMILL, 0x2AD9, DPKT_CHAR_PROP_FLAG_WRITE | DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x00"), DP_PROCESS_WRITE_2AD9T, P1, P2, \
|
||||
P3) \
|
||||
OP(FITNESS_MACHINE_TREADMILL, 0x2ACD, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, \
|
||||
P3) \
|
||||
@@ -77,7 +77,7 @@ using namespace std::chrono_literals;
|
||||
P3) \
|
||||
OP(RUNNING_SPEED_AND_CADENCE, 0x2A53, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, \
|
||||
P3) \
|
||||
OP(HEART_RATE, 0x2A37, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
/* OP(HEART_RATE, 0x2A37, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) */ \
|
||||
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0003, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_0003, P1, P2, P3) \
|
||||
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0002, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
|
||||
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0004, DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x02\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3)
|
||||
@@ -171,9 +171,9 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
|
||||
QSettings settings;
|
||||
DirconProcessorService *service;
|
||||
QList<DirconProcessorService *> services, proc_services;
|
||||
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
BLUETOOTH_TYPE dt = Bike->deviceType();
|
||||
bt = Bike;
|
||||
uint8_t type = dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
|
||||
uint8_t type = dt == TREADMILL || dt == ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
|
||||
: DM_MACHINE_TYPE_BIKE;
|
||||
qDebug() << "Building Dircom Manager";
|
||||
uint16_t server_base_port =
|
||||
@@ -236,7 +236,7 @@ double DirconManager::currentGear() {
|
||||
QSettings settings;
|
||||
if(settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool() && writeP0003)
|
||||
return writeP0003->currentGear();
|
||||
else if(bt && bt->deviceType() == bluetoothdevice::BIKE)
|
||||
else if(bt && bt->deviceType() == BIKE)
|
||||
return ((bike*)bt)->gears();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -96,11 +96,11 @@ int DirconPacket::parse(const QByteArray &buf, int last_seq_number) {
|
||||
} else
|
||||
return DPKT_PARSE_ERROR - rembuf;
|
||||
} else if (this->Identifier == DPKT_MSGID_ENABLE_CHARACTERISTIC_NOTIFICATIONS) {
|
||||
if (this->Length == 16 || this->Length == 17) {
|
||||
if (this->Length >= 16) {
|
||||
quint16 uuid = ((quint16)buf.at(DPKT_MESSAGE_HEADER_LENGTH + DPKT_POS_SH8)) << 8;
|
||||
uuid |= ((quint16)buf.at(DPKT_MESSAGE_HEADER_LENGTH + DPKT_POS_SH0)) & 0x00FF;
|
||||
this->uuid = uuid;
|
||||
if (this->Length == 17) {
|
||||
if (this->Length >= 17) {
|
||||
this->isRequest = true;
|
||||
this->additional_data = buf.mid(DPKT_MESSAGE_HEADER_LENGTH + 16, 1);
|
||||
}
|
||||
@@ -117,6 +117,12 @@ int DirconPacket::parse(const QByteArray &buf, int last_seq_number) {
|
||||
return rembuf;
|
||||
} else
|
||||
return DPKT_PARSE_ERROR - rembuf;
|
||||
} else if (this->Identifier == DPKT_MSGID_UNKNOWN_0x07) {
|
||||
if (this->Length == 0) {
|
||||
this->isRequest = this->checkIsRequest(last_seq_number);
|
||||
return DPKT_MESSAGE_HEADER_LENGTH;
|
||||
} else
|
||||
return DPKT_PARSE_ERROR - rembuf;
|
||||
} else
|
||||
return DPKT_PARSE_ERROR - rembuf;
|
||||
} else
|
||||
@@ -182,6 +188,10 @@ QByteArray DirconPacket::encode(int last_seq_number) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this->Identifier == DPKT_MSGID_UNKNOWN_0x07) {
|
||||
// Unknown message 0x07 - always respond with empty payload
|
||||
this->Length = 0;
|
||||
byteout.append(2, 0);
|
||||
} else if (this->Identifier == DPKT_MSGID_DISCOVER_CHARACTERISTICS && !this->isRequest) {
|
||||
this->Length = 16 + this->uuids.size() * 17;
|
||||
byteout.append((char)(this->Length >> 8)).append((char)(this->Length));
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#define DPKT_MSGID_WRITE_CHARACTERISTIC 0x04
|
||||
#define DPKT_MSGID_ENABLE_CHARACTERISTIC_NOTIFICATIONS 0x05
|
||||
#define DPKT_MSGID_UNSOLICITED_CHARACTERISTIC_NOTIFICATION 0x06
|
||||
#define DPKT_MSGID_UNKNOWN_0x07 0x07
|
||||
#define DPKT_RESPCODE_SUCCESS_REQUEST 0x00
|
||||
#define DPKT_RESPCODE_UNKNOWN_MESSAGE_TYPE 0x01
|
||||
#define DPKT_RESPCODE_UNEXPECTED_ERROR 0x02
|
||||
|
||||
@@ -187,7 +187,7 @@ DirconPacket DirconProcessor::processPacket(DirconProcessorClient *client, const
|
||||
foreach (cc, service->chars) {
|
||||
if (cc->uuid == pkt.uuid) {
|
||||
cfound = true;
|
||||
if (cc->type & DPKT_CHAR_PROP_FLAG_NOTIFY) {
|
||||
if (cc->type & (DPKT_CHAR_PROP_FLAG_NOTIFY | DPKT_CHAR_PROP_FLAG_INDICATE)) {
|
||||
int idx;
|
||||
char notif = pkt.additional_data.at(0);
|
||||
out.uuid = pkt.uuid;
|
||||
@@ -208,6 +208,9 @@ DirconPacket DirconProcessor::processPacket(DirconProcessorClient *client, const
|
||||
}
|
||||
if (!cfound)
|
||||
out.ResponseCode = DPKT_RESPCODE_CHARACTERISTIC_NOT_FOUND;
|
||||
} else if (pkt.Identifier == DPKT_MSGID_UNKNOWN_0x07) {
|
||||
// Unknown message 0x07 - respond with success
|
||||
out.ResponseCode = DPKT_RESPCODE_SUCCESS_REQUEST;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
@@ -225,7 +228,7 @@ bool DirconProcessor::sendCharacteristicNotification(quint16 uuid, const QByteAr
|
||||
pkt.uuid = uuid;
|
||||
for (QHash<QTcpSocket *, DirconProcessorClient *>::iterator i = clientsMap.begin(); i != clientsMap.end(); ++i) {
|
||||
client = i.value();
|
||||
/*if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool())*/ {
|
||||
if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool()) {
|
||||
socket = i.key();
|
||||
rvs = socket->write(pkt.encode(0)) < 0;
|
||||
if (rvs)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "homeform.h"
|
||||
#include "qzsettings.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
@@ -15,7 +17,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
domyosbike::domyosbike(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
@@ -593,6 +595,22 @@ void domyosbike::serviceScanDone(void) {
|
||||
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
|
||||
|
||||
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
|
||||
|
||||
if(!gattCommunicationChannelService) {
|
||||
// Main service not found, check if FTMS service is available
|
||||
QBluetoothUuid ftmsServiceId((quint16)0x1826);
|
||||
QLowEnergyService *ftmsService = m_control->createServiceObject(ftmsServiceId);
|
||||
if(ftmsService) {
|
||||
QSettings settings;
|
||||
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
|
||||
qDebug() << "forcing FTMS bike since it has FTMS service but not the main domyos service";
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change");
|
||||
delete ftmsService;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosbike::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
}
|
||||
@@ -601,6 +619,8 @@ void domyosbike::errorService(QLowEnergyService::ServiceError err) {
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
|
||||
qDebug() << QStringLiteral("domyosbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
|
||||
m_control->errorString();
|
||||
|
||||
m_control->disconnectFromDevice();
|
||||
}
|
||||
|
||||
void domyosbike::error(QLowEnergyController::Error err) {
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
domyoselliptical::domyoselliptical(bool noWriteResistance, bool noHeartService, bool testResistance,
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
domyosrower::domyosrower(bool noWriteResistance, bool noHeartService, bool testResistance, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ domyostreadmill::domyostreadmill(uint32_t pollDeviceTime, bool noConsole, bool n
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -23,7 +23,7 @@ echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartSer
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -23,7 +23,7 @@ echelonrower::echelonrower(bool noWriteResistance, bool noHeartService, int8_t b
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
speedRaw.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
echelonstairclimber::echelonstairclimber(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
|
||||
double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
echelonstride::echelonstride(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed,
|
||||
double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
eliterizer::eliterizer(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
elitesterzosmart::elitesterzosmart(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -22,11 +22,15 @@ void elliptical::update_metrics(bool watt_calc, const double watts) {
|
||||
WeightLoss = metric::calculateWeightLoss(KCal.value());
|
||||
WattKg = m_watt.value() / settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
|
||||
} else if (m_watt.value() > 0) {
|
||||
m_watt = 0;
|
||||
if (watt_calc) {
|
||||
m_watt = 0;
|
||||
}
|
||||
WattKg = 0;
|
||||
}
|
||||
} else if (m_watt.value() > 0) {
|
||||
m_watt = 0;
|
||||
if (watt_calc) {
|
||||
m_watt = 0;
|
||||
}
|
||||
WattKg = 0;
|
||||
}
|
||||
|
||||
@@ -137,7 +141,7 @@ metric elliptical::currentInclination() { return Inclination; }
|
||||
uint8_t elliptical::fanSpeed() { return FanSpeed; }
|
||||
bool elliptical::connected() { return false; }
|
||||
|
||||
bluetoothdevice::BLUETOOTH_TYPE elliptical::deviceType() { return bluetoothdevice::ELLIPTICAL; }
|
||||
BLUETOOTH_TYPE elliptical::deviceType() { return ELLIPTICAL; }
|
||||
|
||||
void elliptical::clearStats() {
|
||||
moving.clear(true);
|
||||
|
||||
@@ -24,7 +24,7 @@ class elliptical : public bluetoothdevice {
|
||||
virtual int pelotonToEllipticalResistance(int pelotonResistance);
|
||||
virtual bool inclinationAvailableByHardware();
|
||||
virtual bool inclinationSeparatedFromResistance();
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
|
||||
BLUETOOTH_TYPE deviceType() override;
|
||||
void clearStats() override;
|
||||
void setPaused(bool p) override;
|
||||
void setLap() override;
|
||||
|
||||
@@ -71,7 +71,7 @@ class CRC8
|
||||
|
||||
eslinkertreadmill::eslinkertreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
fakebike::fakebike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
fakeelliptical::fakeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
fakerower::fakerower(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -36,6 +36,24 @@ void fakerower::update() {
|
||||
|
||||
update_metrics(false, watts());
|
||||
|
||||
if (RequestedPower.value() != -1) {
|
||||
m_watt = (double)RequestedPower.value() * (1.0 + (((double)rand() / RAND_MAX) * 0.4 - 0.2));
|
||||
if(RequestedPower.value())
|
||||
Cadence = 50 + (static_cast<double>(rand()) / RAND_MAX) * 50;
|
||||
else
|
||||
Cadence = 0;
|
||||
emit debug(QStringLiteral("writing power ") + QString::number(RequestedPower.value()));
|
||||
//requestPower = -1;
|
||||
// bepo70: Disregard the current inclination for calculating speed. When the video
|
||||
// has a high inclination you have to give many power to get the desired playback speed,
|
||||
// if inclination is very low little more power gives a quite high speed jump.
|
||||
// Speed = metric::calculateSpeedFromPower(m_watt.value(), Inclination.value(),
|
||||
// Speed.value(),fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), speedLimit());
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), 0);
|
||||
}
|
||||
|
||||
|
||||
Distance += ((Speed.value() / (double)3600.0) /
|
||||
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
faketreadmill::faketreadmill(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -30,7 +30,7 @@ fitplusbike::fitplusbike(bool noWriteResistance, bool noHeartService, int8_t bik
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
}
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -19,7 +19,7 @@ fitshowtreadmill::fitshowtreadmill(uint32_t pollDeviceTime, bool noConsole, bool
|
||||
double forceInitInclination) {
|
||||
Q_UNUSED(noConsole)
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
flywheelbike::flywheelbike(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -24,7 +24,7 @@ focustreadmill::focustreadmill(uint32_t pollDeviceTime, bool noConsole, bool noH
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = false;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -26,7 +26,7 @@ using namespace std::chrono_literals;
|
||||
ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -251,10 +251,16 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceResistance ") + QString::number(requestResistance));
|
||||
} else {
|
||||
if(SL010)
|
||||
|
||||
if(requestResistance < 0) {
|
||||
qDebug() << "Negative resistance detected:" << requestResistance << "using fallback value 1";
|
||||
requestResistance = 1;
|
||||
}
|
||||
|
||||
if(SL010 || SPORT01)
|
||||
Resistance = requestResistance;
|
||||
|
||||
if(JFBK5_0 || DIRETO_XR) {
|
||||
if(JFBK5_0 || DIRETO_XR || YPBM || FIT_BK) {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00, 0x00};
|
||||
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
|
||||
write[2] = ((uint16_t)requestResistance * 10) >> 8;
|
||||
@@ -271,6 +277,26 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsbike::forceInclination(double requestInclination) {
|
||||
// FTMS SET_INDOOR_BIKE_SIMULATION_PARAMS command
|
||||
// Byte 0: OpCode
|
||||
// Byte 1-2: Wind Speed (sint16, 0.001 m/s)
|
||||
// Byte 3-4: Grade/Inclination (sint16, 0.01%)
|
||||
// Byte 5-6: Coefficient of Rolling Resistance (uint8, 0.0001)
|
||||
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
|
||||
|
||||
// Convert inclination to FTMS format (multiply by 100 for 0.01% units)
|
||||
int16_t inclination = (int16_t)(requestInclination * 100.0);
|
||||
|
||||
// Pack Grade in bytes 3-4 as little-endian sint16
|
||||
write[3] = ((uint16_t)inclination) & 0xFF;
|
||||
write[4] = ((uint16_t)inclination) >> 8;
|
||||
|
||||
writeCharacteristic(write, sizeof(write),
|
||||
QStringLiteral("forceInclination ") + QString::number(requestInclination));
|
||||
}
|
||||
|
||||
void ftmsbike::update() {
|
||||
|
||||
QSettings settings;
|
||||
@@ -282,8 +308,6 @@ void ftmsbike::update() {
|
||||
|
||||
if (initRequest) {
|
||||
zwiftPlayInit();
|
||||
if(ICSE)
|
||||
requestResistance = 1; // to force the engine to send every second a target inclination
|
||||
|
||||
// when we are emulating the zwift protocol, zwift doesn't senf the start simulation frames, so we have to send them
|
||||
if(settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool())
|
||||
@@ -340,10 +364,23 @@ void ftmsbike::update() {
|
||||
forceResistance(rR);
|
||||
}
|
||||
}
|
||||
if(!ICSE)
|
||||
requestResistance = -1;
|
||||
requestResistance = -1;
|
||||
}
|
||||
|
||||
// gpx scenario for example
|
||||
if(!virtualBike || !virtualBike->ftmsDeviceConnected()) {
|
||||
if ((requestInclination != -100 || lastGearValue != gears())) {
|
||||
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
|
||||
forceInclination(requestInclination + gears()); // since this bike doesn't have the concept of resistance,
|
||||
// i'm using the gears in the inclination
|
||||
requestInclination = -100;
|
||||
} else if(lastGearValue != gears() && lastRawRequestedInclinationValue != -100) {
|
||||
// in order to send the new gear value ASAP
|
||||
forceInclination(lastRawRequestedInclinationValue + gears()); // since this bike doesn't have the concept of resistance,
|
||||
// i'm using the gears in the inclination
|
||||
}
|
||||
}
|
||||
|
||||
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
|
||||
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
|
||||
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
|
||||
@@ -543,7 +580,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
};
|
||||
|
||||
// clean time in case for a long period we don't receive values
|
||||
if(lastRefreshCharacteristicChanged2AD2.secsTo(now) > 5) {
|
||||
if(lastRefreshCharacteristicChanged2AD2.secsTo(now) > secondsToResetTimer) {
|
||||
qDebug() << "clearing lastRefreshCharacteristicChanged2AD2" << lastRefreshCharacteristicChanged2AD2 << now;
|
||||
lastRefreshCharacteristicChanged2AD2 = now;
|
||||
}
|
||||
@@ -574,6 +611,13 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
100.0;
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed));
|
||||
// Use average speed if instant speed is not available (moreData flag set)
|
||||
if (Flags.moreData) {
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
Speed = avgSpeed;
|
||||
emit debug(QStringLiteral("Current Speed (from average): ") + QString::number(Speed.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Flags.instantCadence) {
|
||||
@@ -595,6 +639,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
2.0;
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence));
|
||||
// Use average cadence if instant cadence is not available
|
||||
if (!Flags.instantCadence) {
|
||||
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
Cadence = avgCadence;
|
||||
emit debug(QStringLiteral("Current Cadence (from average): ") + QString::number(Cadence.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Flags.totDistance) {
|
||||
@@ -622,7 +675,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
if(BIKE_)
|
||||
d = d / 10.0;
|
||||
// for this bike, i will use the resistance that I set directly because the bike sends a different ratio.
|
||||
if(!SL010 && !TITAN_7000)
|
||||
if(!SL010 && !TITAN_7000 && !SPORT01)
|
||||
Resistance = d;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit resistanceRead(Resistance.value());
|
||||
@@ -638,18 +691,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
double cr = 97.62165482;
|
||||
|
||||
if (Cadence.value() && m_watt.value()) {
|
||||
if(YS_G1MPLUS) {
|
||||
m_pelotonResistance = Resistance.value(); // 1:1 ratio
|
||||
} else {
|
||||
m_pelotonResistance =
|
||||
(((sqrt(pow(br, 2.0) - 4.0 * ar *
|
||||
(cr - (m_watt.value() * 132.0 /
|
||||
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
|
||||
br) /
|
||||
(2.0 * ar)) *
|
||||
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
}
|
||||
m_pelotonResistance =
|
||||
(((sqrt(pow(br, 2.0) - 4.0 * ar *
|
||||
(cr - (m_watt.value() * 132.0 /
|
||||
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
|
||||
br) /
|
||||
(2.0 * ar)) *
|
||||
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
|
||||
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
|
||||
|
||||
if (!resistance_received && !DU30_bike && !SL010) {
|
||||
Resistance = m_pelotonResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
@@ -663,6 +713,31 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
if(DU30_bike) {
|
||||
m_watt = wattsFromResistance(Resistance.value());
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
|
||||
} else if (SPORT01) {
|
||||
// Custom power calculation for SPORT01
|
||||
// Resistance multipliers for levels 1-10
|
||||
const double k[10] = {0.60, 0.75, 0.85, 0.95, 1.00, 1.18, 1.40, 1.70, 2.00, 2.40};
|
||||
|
||||
// Baseline power curve coefficients (MyWhoosh cadence-power at resistance 5)
|
||||
double ac = 0.01243107769;
|
||||
double bc = 1.145964912;
|
||||
double cc = -23.50977444;
|
||||
|
||||
// Calculate baseline power from cadence (resistance level 5 baseline)
|
||||
double baseline_watt = ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc;
|
||||
|
||||
// Get current resistance level (1-10) and apply multiplier
|
||||
int resistance_level = (int)Resistance.value();
|
||||
if(resistance_level < 1) resistance_level = 1;
|
||||
if(resistance_level > 10) resistance_level = 10;
|
||||
|
||||
// Apply resistance multiplier
|
||||
m_watt = baseline_watt * k[resistance_level - 1];
|
||||
|
||||
if(m_watt.value() < 0) m_watt = 0;
|
||||
|
||||
emit debug(QStringLiteral("Current Watt (SPORT01 formula - R%1 x%2): %3")
|
||||
.arg(resistance_level).arg(k[resistance_level - 1]).arg(m_watt.value()));
|
||||
} else if (MRK_S26C) {
|
||||
m_watt = Cadence.value() * (Resistance.value() * 1.16);
|
||||
emit debug(QStringLiteral("Current Watt (MRK-S26C formula): ") + QString::number(m_watt.value()));
|
||||
@@ -693,6 +768,15 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
|
||||
// Use average power if instant power is zero or not available
|
||||
if ((!Flags.instantPower || m_watt.value() == 0) && avgPower > 0) {
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"))) {
|
||||
m_watt = avgPower;
|
||||
emit debug(QStringLiteral("Current Watt (from average): ") + QString::number(m_watt.value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Flags.expEnergy && newValue.length() > index + 1) {
|
||||
@@ -1212,7 +1296,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || DOMYOS || SMB1 || FIT_BK) {
|
||||
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || SCH_290R || DOMYOS || SMB1 || FIT_BK) {
|
||||
QBluetoothUuid ftmsService((quint16)0x1826);
|
||||
if (s->serviceUuid() != ftmsService) {
|
||||
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
|
||||
@@ -1292,7 +1376,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
}
|
||||
|
||||
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
|
||||
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SMB1 || FIT_BK)) {
|
||||
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_290R || SMB1 || FIT_BK)) {
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -1432,6 +1516,20 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
} else if(b.at(0) == FTMS_SET_TARGET_POWER && ((zwiftPlayService != nullptr && gears_zwift_ratio) || !ergModeSupported)) {
|
||||
qDebug() << "discarding";
|
||||
return;
|
||||
} else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
|
||||
// handling watt gain and offset for erg mode
|
||||
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
|
||||
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
|
||||
|
||||
if (watt_gain != 1.0 || watt_offset != 0) {
|
||||
uint16_t powerRequested = (((uint8_t)b.at(1)) + (b.at(2) << 8));
|
||||
qDebug() << "applying watt_gain/watt_offset from" << powerRequested;
|
||||
powerRequested = ((powerRequested / watt_gain) - watt_offset);
|
||||
qDebug() << "to" << powerRequested;
|
||||
|
||||
b[1] = powerRequested & 0xFF;
|
||||
b[2] = powerRequested >> 8;
|
||||
}
|
||||
}
|
||||
// gears on erg mode is quite useless and it's confusing
|
||||
/* else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
|
||||
@@ -1453,10 +1551,14 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
|
||||
}
|
||||
|
||||
void ftmsbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
static bool connectedAndDiscoveredOk = false;
|
||||
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
|
||||
|
||||
initRequest = true;
|
||||
emit connectedAndDiscovered();
|
||||
if(!connectedAndDiscoveredOk) {
|
||||
connectedAndDiscoveredOk = true;
|
||||
emit connectedAndDiscovered();
|
||||
}
|
||||
}
|
||||
|
||||
void ftmsbike::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
|
||||
@@ -1541,6 +1643,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("ICSE") && bluetoothDevice.name().length() == 4)) {
|
||||
qDebug() << QStringLiteral("ICSE found");
|
||||
ICSE = true;
|
||||
secondsToResetTimer = 15;
|
||||
autoResistanceEnable = false; // Disable auto resistance for ICSE bikes
|
||||
qDebug() << QStringLiteral("ICSE: autoResistance disabled by default");
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
|
||||
qDebug() << QStringLiteral("DOMYOS found");
|
||||
resistance_lvl_mode = true;
|
||||
@@ -1553,6 +1658,10 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
qDebug() << QStringLiteral("SCH_190U found");
|
||||
SCH_190U = true;
|
||||
max_resistance = 100;
|
||||
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_290R"))) {
|
||||
qDebug() << QStringLiteral("SCH_290R found");
|
||||
SCH_290R = true;
|
||||
max_resistance = 100;
|
||||
} else if(bluetoothDevice.name().toUpper().startsWith("D2RIDE")) {
|
||||
qDebug() << QStringLiteral("D2RIDE found");
|
||||
D2RIDE = true;
|
||||
@@ -1633,9 +1742,26 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if(device.name().toUpper().startsWith("HAMMER")) {
|
||||
qDebug() << QStringLiteral("HAMMER found");
|
||||
HAMMER = true;
|
||||
} else if(device.name().toUpper().startsWith("YPBM") && device.name().length() == 10) {
|
||||
qDebug() << QStringLiteral("YPBM found");
|
||||
YPBM = true;
|
||||
resistance_lvl_mode = true;
|
||||
ergModeSupported = false;
|
||||
max_resistance = 32;
|
||||
} else if(device.name().toUpper().startsWith("SPORT01")) {
|
||||
qDebug() << QStringLiteral("SPORT01 found");
|
||||
SPORT01 = true;
|
||||
resistance_lvl_mode = true;
|
||||
ergModeSupported = false;
|
||||
max_resistance = 10;
|
||||
Resistance = 1; // Initialize resistance to 1 for SPORT01
|
||||
} else if(device.name().toUpper().startsWith("FS-YK-")) {
|
||||
qDebug() << QStringLiteral("FS-YK- found");
|
||||
FS_YK = true;
|
||||
ergModeSupported = false; // this bike doesn't have ERG mode natively
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
|
||||
resistance_lvl_mode = true;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ class ftmsbike : public bike {
|
||||
|
||||
// true because or the bike supports it by hardware or because QZ is emulating this in this module
|
||||
bool ergModeSupportedAvailableBySoftware() override { return true; }
|
||||
bool inclinationAvailableBySoftware() override { return !resistance_lvl_mode; }
|
||||
|
||||
private:
|
||||
bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
@@ -94,6 +95,7 @@ class ftmsbike : public bike {
|
||||
void init();
|
||||
void forceResistance(resistance_t requestResistance);
|
||||
void forcePower(int16_t requestPower);
|
||||
void forceInclination(double requestInclination);
|
||||
uint16_t wattsFromResistance(double resistance);
|
||||
|
||||
QTimer *refresh;
|
||||
@@ -138,6 +140,7 @@ class ftmsbike : public bike {
|
||||
bool DOMYOS = false;
|
||||
bool _3G_Cardio_RB = false;
|
||||
bool SCH_190U = false;
|
||||
bool SCH_290R = false;
|
||||
bool D2RIDE = false;
|
||||
bool WATTBIKE = false;
|
||||
bool VFSPINBIKE = false;
|
||||
@@ -161,6 +164,11 @@ class ftmsbike : public bike {
|
||||
bool MAGNUS = false;
|
||||
bool MRK_S26C = false;
|
||||
bool HAMMER = false;
|
||||
bool YPBM = false;
|
||||
bool SPORT01 = false;
|
||||
bool FS_YK = false;
|
||||
|
||||
uint8_t secondsToResetTimer = 5;
|
||||
|
||||
int16_t T2_lastGear = 0;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "devices/ftmsrower/ftmsrower.h"
|
||||
#include "devices/ftmsbike/ftmsbike.h"
|
||||
#include "virtualdevices/virtualbike.h"
|
||||
#include "virtualdevices/virtualtreadmill.h"
|
||||
#include <QBluetoothLocalDevice>
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
@@ -19,7 +20,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
ftmsrower::ftmsrower(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -359,12 +360,12 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
}
|
||||
|
||||
if (Flags.totDistance) {
|
||||
Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
|
||||
/*Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
|
||||
(uint32_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint32_t)((uint8_t)newValue.at(index)))) /
|
||||
1000.0;
|
||||
1000.0;*/
|
||||
index += 3;
|
||||
} else {
|
||||
}/* else */{
|
||||
Distance += ((Speed.value() / 3600000.0) *
|
||||
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
|
||||
}
|
||||
@@ -380,8 +381,10 @@ void ftmsrower::characteristicChanged(const QLowEnergyCharacteristic &characteri
|
||||
emit debug(QStringLiteral("Current Pace: ") + QString::number(instantPace));
|
||||
|
||||
if((DFIT_L_R && Cadence.value() > 0) || !DFIT_L_R) {
|
||||
Speed = (60.0 / instantPace) *
|
||||
30.0; // translating pace (min/500m) to km/h in order to match the pace function in the rower.cpp
|
||||
if(instantPace == 0)
|
||||
Speed = 0;
|
||||
else
|
||||
Speed = (60.0 / instantPace) * 30.0; // translating pace (min/500m) to km/h in order to match the pace function in the rower.cpp
|
||||
}
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
}
|
||||
@@ -620,6 +623,8 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_rower =
|
||||
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
|
||||
bool virtual_device_force_treadmill =
|
||||
settings.value(QZSettings::virtual_device_force_treadmill, QZSettings::default_virtual_device_force_treadmill).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
@@ -637,7 +642,13 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
#endif
|
||||
{
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_rower) {
|
||||
if (virtual_device_force_treadmill) {
|
||||
emit debug(QStringLiteral("creating virtual treadmill interface..."));
|
||||
|
||||
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
|
||||
connect(virtualTreadmill, &virtualtreadmill::debug, this, &ftmsrower::debug);
|
||||
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
} else if (!virtual_device_rower) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
|
||||
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
|
||||
|
||||
@@ -64,11 +64,25 @@ void heartratebelt::disconnectBluetooth() {
|
||||
|
||||
void heartratebelt::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
Q_UNUSED(characteristic);
|
||||
emit packetReceived();
|
||||
|
||||
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
|
||||
|
||||
// Handle Battery Service
|
||||
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) {
|
||||
if(newValue.length() > 0) {
|
||||
uint8_t battery = (uint8_t)newValue.at(0);
|
||||
if(battery != battery_level) {
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(bluetoothDevice.name() + QStringLiteral(" Battery Level ") + QString::number(battery) + " %");
|
||||
}
|
||||
battery_level = battery;
|
||||
qDebug() << QStringLiteral("battery: ") << battery;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Heart Rate Measurement
|
||||
if (newValue.length() > 1) {
|
||||
Heart = (uint8_t)newValue[1];
|
||||
emit heartRate((uint8_t)Heart.value());
|
||||
@@ -82,6 +96,35 @@ void heartratebelt::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
|
||||
|
||||
if (state == QLowEnergyService::ServiceDiscovered) {
|
||||
// Check if this is the Battery Service
|
||||
QLowEnergyService* service = qobject_cast<QLowEnergyService*>(sender());
|
||||
if (service && service == gattBatteryService) {
|
||||
// Handle Battery Service
|
||||
auto characteristics_list = gattBatteryService->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
emit debug(QStringLiteral("battery characteristic ") + c.uuid().toString());
|
||||
}
|
||||
|
||||
connect(gattBatteryService, &QLowEnergyService::characteristicChanged, this,
|
||||
&heartratebelt::characteristicChanged);
|
||||
connect(gattBatteryService,
|
||||
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
|
||||
this, &heartratebelt::errorService);
|
||||
|
||||
// Enable notifications for battery level
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
|
||||
QByteArray descriptor;
|
||||
descriptor.append((char)0x01);
|
||||
descriptor.append((char)0x00);
|
||||
gattBatteryService->writeDescriptor(
|
||||
c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Original code for Heart Rate Service
|
||||
auto characteristics_list = gattCommunicationChannelService->characteristics();
|
||||
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
|
||||
emit debug(QStringLiteral("characteristic ") + c.uuid().toString());
|
||||
@@ -134,7 +177,13 @@ void heartratebelt::serviceScanDone(void) {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
|
||||
&heartratebelt::stateChanged);
|
||||
gattCommunicationChannelService->discoverDetails();
|
||||
return;
|
||||
}
|
||||
else if (s == QBluetoothUuid::BatteryService) {
|
||||
QBluetoothUuid _gattBatteryServiceId(QBluetoothUuid::BatteryService);
|
||||
gattBatteryService = m_control->createServiceObject(_gattBatteryServiceId);
|
||||
connect(gattBatteryService, &QLowEnergyService::stateChanged, this,
|
||||
&heartratebelt::stateChanged);
|
||||
gattBatteryService->discoverDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,12 @@ class heartratebelt : public treadmill {
|
||||
|
||||
private:
|
||||
QLowEnergyService *gattCommunicationChannelService = nullptr;
|
||||
QLowEnergyService *gattBatteryService = nullptr;
|
||||
QLowEnergyCharacteristic gattNotifyCharacteristic;
|
||||
QDateTime connectingTime; // Timestamp when entering connecting state
|
||||
static const int CONNECTION_TIMEOUT = 10000; // 10 seconds in milliseconds
|
||||
QTimer* updateTimer; // Timer for periodic updates
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
horizongr7bike::horizongr7bike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -27,7 +27,7 @@ horizontreadmill::horizontreadmill(bool noWriteResistance, bool noHeartService)
|
||||
|
||||
testProfileCRC();
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -948,7 +948,7 @@ void horizontreadmill::update() {
|
||||
requestInclination = treadmillInclinationOverrideReverse(requestInclination);
|
||||
|
||||
// this treadmill doesn't send the incline, so i'm forcing it manually
|
||||
if(schwinn_810_treadmill) {
|
||||
if(schwinn_810_treadmill || FIT_TM) {
|
||||
Inclination = requestInclination;
|
||||
}
|
||||
|
||||
@@ -1624,6 +1624,18 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
|
||||
Speed = 0;
|
||||
horizonPaused = true;
|
||||
qDebug() << "stop from the treadmill";
|
||||
} else if (TP1 && characteristic.uuid() == QBluetoothUuid((quint16)0x2ADA) && newValue.length() == 2 &&
|
||||
(uint8_t)newValue.at(0) == 0x02 && (uint8_t)newValue.at(1) == 0x01) {
|
||||
// TP1 treadmill start command received
|
||||
qDebug() << "TP1 treadmill: received start packet from treadmill, sending start command";
|
||||
emit debug(QStringLiteral("TP1 treadmill: received start packet from treadmill, sending start command"));
|
||||
|
||||
if (gattFTMSService) {
|
||||
uint8_t write[] = {FTMS_REQUEST_CONTROL};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false, false);
|
||||
write[0] = {FTMS_START_RESUME};
|
||||
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "start TP1", false, false);
|
||||
}
|
||||
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
|
||||
union flags {
|
||||
struct {
|
||||
@@ -2537,8 +2549,15 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
HORIZON_78AT_treadmill = true;
|
||||
qDebug() << QStringLiteral("HORIZON_7.8AT workaround ON!");
|
||||
} else if(device.name().toUpper().startsWith("T01_")) {
|
||||
ICONCEPT_FTMS_treadmill = true;
|
||||
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
|
||||
QSettings settings;
|
||||
iconcept_ftms_treadmill_inclination_table = settings.value(QZSettings::iconcept_ftms_treadmill_inclination_table, QZSettings::default_iconcept_ftms_treadmill_inclination_table).toBool();
|
||||
if(iconcept_ftms_treadmill_inclination_table) {
|
||||
ICONCEPT_FTMS_treadmill = true;
|
||||
qDebug() << QStringLiteral("ICONCEPT_FTMS_treadmill workaround ON!");
|
||||
} else {
|
||||
if(homeform::singleton())
|
||||
homeform::singleton()->setToastRequested(QStringLiteral("T01_ device detected. If you see strange inclination values, enable 'IConcept FTMS Treadmill' in Treadmill Options settings."));
|
||||
}
|
||||
} else if ((device.name().toUpper().startsWith("DOMYOS"))) {
|
||||
qDebug() << QStringLiteral("DOMYOS found");
|
||||
DOMYOS = true;
|
||||
@@ -2551,6 +2570,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("MX-TM "))) {
|
||||
qDebug() << QStringLiteral("MX-TM found");
|
||||
MX_TM = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-TM-"))) {
|
||||
qDebug() << QStringLiteral("FIT-TM- found (real inclination)");
|
||||
FIT_TM = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-"))) {
|
||||
qDebug() << QStringLiteral("FIT- found");
|
||||
FIT = true;
|
||||
@@ -2560,6 +2582,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("3G ELITE "))) {
|
||||
qDebug() << QStringLiteral("3G ELITE");
|
||||
T3G_ELITE = true;
|
||||
} else if (device.name().toUpper().startsWith(QStringLiteral("TP1")) && device.name().length() == 3) {
|
||||
qDebug() << QStringLiteral("TP1 treadmill found");
|
||||
TP1 = true;
|
||||
}
|
||||
|
||||
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
|
||||
|
||||
@@ -102,14 +102,17 @@ class horizontreadmill : public treadmill {
|
||||
bool disableAutoPause = false;
|
||||
bool HORIZON_78AT_treadmill = false;
|
||||
bool ICONCEPT_FTMS_treadmill = false;
|
||||
bool iconcept_ftms_treadmill_inclination_table = false;
|
||||
bool DOMYOS = false;
|
||||
bool SW_TREADMILL = false;
|
||||
bool BOWFLEX_T9 = false;
|
||||
bool YPOO_MINI_PRO = false;
|
||||
bool MX_TM = false;
|
||||
bool FIT = false;
|
||||
bool FIT_TM = false;
|
||||
bool T3G_PRO = false;
|
||||
bool T3G_ELITE = false;
|
||||
bool TP1 = false;
|
||||
|
||||
void testProfileCRC();
|
||||
void updateProfileCRC();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
iconceptbike::iconceptbike() {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
|
||||
@@ -15,7 +15,7 @@ iconceptelliptical::iconceptelliptical(bool noWriteResistance, bool noHeartServi
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
|
||||
//#include <QtBluetooth/private/qlowenergyserviceprivate_p.h>
|
||||
|
||||
inspirebike::inspirebike(bool noWriteResistance, bool noHeartService) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
t_timeout = new QTimer(this);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
jumprope::jumprope() {}
|
||||
|
||||
bool jumprope::connected() { return false; }
|
||||
bluetoothdevice::BLUETOOTH_TYPE jumprope::deviceType() { return bluetoothdevice::JUMPROPE; }
|
||||
BLUETOOTH_TYPE jumprope::deviceType() { return JUMPROPE; }
|
||||
|
||||
|
||||
uint16_t jumprope::watts(double weight) {
|
||||
|
||||
@@ -11,7 +11,7 @@ class jumprope : public bluetoothdevice {
|
||||
jumprope();
|
||||
bool connected() override;
|
||||
virtual uint16_t watts(double weight);
|
||||
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
|
||||
BLUETOOTH_TYPE deviceType() override;
|
||||
void clearStats() override;
|
||||
void setLap() override;
|
||||
void setPaused(bool p) override;
|
||||
|
||||
@@ -23,7 +23,7 @@ keepbike::keepbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -23,7 +23,7 @@ kineticinroadbike::kineticinroadbike(bool noWriteResistance, bool noHeartService
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
kingsmithr1protreadmill::kingsmithr1protreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -15,7 +15,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
kingsmithr2treadmill::kingsmithr2treadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -28,7 +28,7 @@ lifefitnesstreadmill::lifefitnesstreadmill(bool noWriteResistance, bool noHeartS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -12,7 +12,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
lifespantreadmill::lifespantreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
|
||||
double forceInitSpeed, double forceInitInclination) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -260,7 +260,7 @@ m3ibike::m3ibike(bool noWriteResistance, bool noHeartService) {
|
||||
heartRateBeltDisabled = settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled"));
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -21,7 +21,7 @@ mcfbike::mcfbike(bool noWriteResistance, bool noHeartService, int8_t bikeResista
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -21,7 +21,7 @@ mepanelbike::mepanelbike(bool noWriteResistance, bool noHeartService, int8_t bik
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
nautilusbike::nautilusbike(bool noWriteResistance, bool noHeartService, bool testResistance,
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
nautiluselliptical::nautiluselliptical(bool noWriteResistance, bool noHeartService, bool testResistance,
|
||||
int8_t bikeResistanceOffset, double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ nautilustreadmill::nautilustreadmill(uint32_t pollDeviceTime, bool noConsole, bo
|
||||
#ifdef Q_OS_IOS
|
||||
QZ_EnableDiscoveryCharsAndDescripttors = true;
|
||||
#endif
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
this->noConsole = noConsole;
|
||||
this->noHeartService = noHeartService;
|
||||
|
||||
@@ -19,7 +19,7 @@ using namespace std::chrono_literals;
|
||||
|
||||
nordictrackelliptical::nordictrackelliptical(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
|
||||
@@ -114,7 +114,7 @@ nordictrackifitadbbike::nordictrackifitadbbike(bool noWriteResistance, bool noHe
|
||||
bool nordictrack_ifit_adb_remote =
|
||||
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
|
||||
.toBool();
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
m_watt.setType(metric::METRIC_WATT, deviceType());
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
@@ -300,62 +300,54 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
|
||||
.toBool();
|
||||
double inclination_delay_seconds = settings.value(QZSettings::inclination_delay_seconds, QZSettings::default_inclination_delay_seconds).toDouble();
|
||||
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
|
||||
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
|
||||
|
||||
// only resistance
|
||||
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
|
||||
if (nordictrack_ifit_adb_remote) {
|
||||
if (requestResistance != -1) {
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
int x1 = 950;
|
||||
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
|
||||
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
|
||||
// Handle resistance and inclination changes when ADB is enabled
|
||||
if (nordictrack_ifit_adb_remote) {
|
||||
// Handle resistance changes with delay
|
||||
if (requestResistance != -1 && lastResistanceChanged.secsTo(now) > inclination_delay_seconds) {
|
||||
if (requestResistance != currentResistance().value()) {
|
||||
int x1 = 950;
|
||||
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
|
||||
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
|
||||
|
||||
if(proform_tdf_10_0) {
|
||||
x1 = 1175;
|
||||
y2 = (int)(590 - (15.91 * requestResistance));
|
||||
y1Resistance = (int)(590 - (15.91 * currentResistance().value()));
|
||||
Resistance = requestResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
}
|
||||
else if(!proform_studio_NTEX71021) { // s22i default
|
||||
x1 = 1920 - 75;
|
||||
y2 = (int)(803 - (23.777 * requestResistance));
|
||||
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
|
||||
Resistance = requestResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
}
|
||||
|
||||
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
|
||||
QString::number(x1) + " " + QString::number(y2) + " 200";
|
||||
qDebug() << " >> " + lastCommand;
|
||||
if(proform_tdf_10_0) {
|
||||
x1 = 1175;
|
||||
y2 = (int)(590 - (15.91 * requestResistance));
|
||||
y1Resistance = (int)(590 - (15.91 * currentResistance().value()));
|
||||
}
|
||||
else if(!proform_studio_NTEX71021) { // s22i default
|
||||
x1 = 1920 - 75;
|
||||
y2 = (int)(803 - (23.777 * requestResistance));
|
||||
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
|
||||
}
|
||||
|
||||
Resistance = requestResistance;
|
||||
emit resistanceRead(Resistance.value());
|
||||
|
||||
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
|
||||
QString::number(x1) + " " + QString::number(y2) + " 200";
|
||||
qDebug() << " >> RESISTANCE: " + lastCommand;
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
|
||||
"sendCommand", "(Ljava/lang/String;)V",
|
||||
command.object<jstring>());
|
||||
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
|
||||
"sendCommand", "(Ljava/lang/String;)V",
|
||||
command.object<jstring>());
|
||||
#elif defined(Q_OS_WIN)
|
||||
if (logcatAdbThread)
|
||||
logcatAdbThread->runCommand("shell " + lastCommand);
|
||||
if (logcatAdbThread)
|
||||
logcatAdbThread->runCommand("shell " + lastCommand);
|
||||
#elif defined Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
h->adb_sendcommand(lastCommand.toStdString().c_str());
|
||||
h->adb_sendcommand(lastCommand.toStdString().c_str());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
lastResistanceChanged = now;
|
||||
}
|
||||
|
||||
requestResistance = -1;
|
||||
}
|
||||
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
|
||||
requestResistance = -1;
|
||||
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
|
||||
qDebug() << QString::number(ret) + " >> " + message;
|
||||
}
|
||||
// since the motor of the bike is slow, let's filter the inclination changes to more than 4 seconds
|
||||
else if (lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
|
||||
lastInclinationChanged = now;
|
||||
if (nordictrack_ifit_adb_remote) {
|
||||
}
|
||||
|
||||
// Handle inclination changes with delay
|
||||
if (requestInclination != -100 && lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
|
||||
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
|
||||
if (requestInclination != -100 && erg_mode && requestResistance != -100) {
|
||||
qDebug() << "forcing inclination based on the erg mode resistance request of" << requestResistance;
|
||||
@@ -365,7 +357,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
if (requestInclination != -100) {
|
||||
double inc = qRound(requestInclination / 0.5) * 0.5;
|
||||
if (inc != currentInclination().value()) {
|
||||
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
|
||||
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
|
||||
int x1 = 75;
|
||||
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
|
||||
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
|
||||
@@ -382,7 +374,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
|
||||
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
|
||||
QString::number(x1) + " " + QString::number(y2) + " 200";
|
||||
qDebug() << " >> " + lastCommand;
|
||||
qDebug() << " >> INCLINATION: " + lastCommand;
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
|
||||
@@ -396,43 +388,28 @@ void nordictrackifitadbbike::processPendingDatagrams() {
|
||||
h->adb_sendcommand(lastCommand.toStdString().c_str());
|
||||
#endif
|
||||
#endif
|
||||
// this bike has both inclination and resistance, let's try to handle both
|
||||
// the Original Poster doesn't want anymore, but maybe it will be useful in the future
|
||||
/*
|
||||
if(freemotion_coachbike_b22_7) {
|
||||
int x1 = 75;
|
||||
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
|
||||
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
|
||||
|
||||
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
|
||||
QString::number(x1) + " " + QString::number(y2) + " 200";
|
||||
qDebug() << " >> " + lastCommand;
|
||||
#ifdef Q_OS_ANDROID
|
||||
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
|
||||
"sendCommand", "(Ljava/lang/String;)V",
|
||||
command.object<jstring>());
|
||||
#elif defined(Q_OS_WIN)
|
||||
if (logcatAdbThread)
|
||||
logcatAdbThread->runCommand("shell " + lastCommand);
|
||||
#elif defined Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
h->adb_sendcommand(lastCommand.toStdString().c_str());
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
requestInclination = -100;
|
||||
}
|
||||
|
||||
double r = currentResistance().value() + difficult() + gears(); // the inclination here is like the resistance for the other bikes
|
||||
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
|
||||
requestInclination = -100;
|
||||
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
|
||||
qDebug() << QString::number(ret) + " >> " + message;
|
||||
} else {
|
||||
// Non-ADB mode: handle via UDP socket as before
|
||||
// only resistance
|
||||
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
|
||||
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
|
||||
requestResistance = -1;
|
||||
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
|
||||
qDebug() << QString::number(ret) + " >> " + message;
|
||||
}
|
||||
// inclination
|
||||
else if (lastInclinationChanged.secsTo(now) > inclination_delay_seconds) {
|
||||
lastInclinationChanged = now;
|
||||
double r = currentResistance().value() + difficult() + gears();
|
||||
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
|
||||
requestInclination = -100;
|
||||
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
|
||||
qDebug() << QString::number(ret) + " >> " + message;
|
||||
}
|
||||
}
|
||||
|
||||
if (watts())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user