Compare commits

...

60 Commits

Author SHA1 Message Date
Roberto Viola
81150953f1 Update project.pbxproj 2024-10-18 09:02:19 +02:00
Roberto Viola
c5170b5b42 fixing gears formatting 2024-10-18 09:01:45 +02:00
Roberto Viola
52ca1787ba Update virtualbike_zwift.swift 2024-10-18 08:49:02 +02:00
Roberto Viola
e76ad7a6e8 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-18 08:29:43 +02:00
Roberto Viola
2f1612205b Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-17 17:04:23 +02:00
Roberto Viola
b0c1054ed8 Update virtualbike_zwift.swift 2024-10-17 17:02:15 +02:00
Roberto Viola
993a00a57b Update project.pbxproj 2024-10-17 16:59:47 +02:00
Roberto Viola
e6fec94f55 adding setting for enabling it 2024-10-17 16:58:23 +02:00
Roberto Viola
891c2fb4f7 fixing starting gears with the new zwift version 2024-10-16 12:25:02 +02:00
Roberto Viola
f49fe67b5a fixing android 2024-10-16 11:50:57 +02:00
Roberto Viola
727dde137f Update virtualbike.cpp 2024-10-16 10:53:07 +02:00
Roberto Viola
bebe505152 merging modification on android/linux 2024-10-16 10:38:04 +02:00
Roberto Viola
a0d60efab9 changing gears quickly 2024-10-16 08:15:06 +02:00
Roberto Viola
6eb2463259 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-16 08:11:03 +02:00
Roberto Viola
7ed1b3f498 trying to use the wahoo service for gears also on dircon 2024-10-08 17:14:18 +02:00
Roberto Viola
48880a4962 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-08 16:55:41 +02:00
Roberto Viola
0c7ad157de fixing difficulty 2024-10-08 15:13:41 +02:00
Roberto Viola
2f4d80dda1 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-08 14:43:09 +02:00
Roberto Viola
28e1bf2abf Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-08 14:26:58 +02:00
Roberto Viola
3ee47b9b22 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-08 09:08:13 +02:00
Roberto Viola
be19850f31 handling slope 2024-10-06 15:09:26 +02:00
Roberto Viola
2c12f7cd20 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-10-06 06:19:39 +02:00
Roberto Viola
916a664706 Update project.pbxproj 2024-09-29 07:48:22 +02:00
Roberto Viola
cf38aab0af Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-09-28 12:03:03 +02:00
Roberto Viola
70ecbacd6a increasing UI speed for the gear changing 2024-09-26 15:20:54 +02:00
Roberto Viola
d4d3a9ceb3 reverting torque not necessary 2024-09-26 14:26:41 +02:00
Roberto Viola
e58c7c6888 the gearing is working! 2024-09-26 11:40:16 +02:00
Roberto Viola
614690aec3 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-09-26 10:46:31 +02:00
Roberto Viola
ea0221d20d accolumulated torque but it doesn't seem necessary 2024-09-26 09:05:13 +02:00
Roberto Viola
8920cf4981 Update virtualbike_zwift.swift 2024-09-26 08:26:59 +02:00
Roberto Viola
a013c73ced seems to work apart the wattage to zwift 2024-09-26 08:09:23 +02:00
Roberto Viola
3000b8e6d9 Merge branch 'Zwift-Play-Emulator-#2391' of https://github.com/cagnulein/qdomyos-zwift into Zwift-Play-Emulator-#2391 2024-09-25 11:14:59 +02:00
Roberto Viola
f8b2975558 Update virtualbike_zwift.swift 2024-09-25 11:14:46 +02:00
Roberto Viola
ba27677cae Update virtualbike_zwift.swift 2024-09-25 11:01:55 +02:00
Roberto Viola
b77a12b895 Update virtualbike_zwift.swift 2024-09-25 10:46:00 +02:00
Roberto Viola
4a8ffa343e Update virtualbike_zwift.swift 2024-09-25 10:42:19 +02:00
Roberto Viola
bb40f2475d Update virtualbike_zwift.swift 2024-09-25 10:06:46 +02:00
Roberto Viola
2f554750a5 Update virtualbike_zwift.swift 2024-09-25 10:00:47 +02:00
Roberto Viola
ea3ea7b570 zwift play ask 1 passed! 2024-09-25 09:43:02 +02:00
Roberto Viola
a0e86b9270 Update virtualbike_zwift.swift 2024-09-24 11:32:48 +02:00
Roberto Viola
ea4b93d96f Update virtualbike_zwift.swift 2024-09-24 07:46:35 +02:00
Roberto Viola
6cefcdc011 Update virtualbike.cpp 2024-09-23 16:20:37 +02:00
Roberto Viola
eb6438f591 Update virtualbike.cpp 2024-09-23 15:32:03 +02:00
Roberto Viola
da047d5e5b Update virtualbike.cpp 2024-09-23 14:13:51 +02:00
Roberto Viola
a5c3ca388d Update virtualbike.cpp 2024-09-23 13:41:51 +02:00
Roberto Viola
5fb771b9f0 Update virtualbike.cpp 2024-09-23 13:01:36 +02:00
Roberto Viola
936bb56574 porting to android too 2024-09-23 08:56:09 +02:00
Roberto Viola
d6e9227360 fixing iOS UUID? 2024-09-23 08:55:59 +02:00
Roberto Viola
6a6efebfcb Update virtualbike_zwift.swift 2024-09-22 14:23:27 +02:00
Roberto Viola
1674ec85a1 Merge branch 'master' into Zwift-Play-Emulator-#2391 2024-09-20 15:56:03 +02:00
Roberto Viola
71618d0a16 Update virtualbike_zwift.swift 2024-09-20 15:54:43 +02:00
Roberto Viola
05666dea67 adding zwift play service 2024-09-20 15:37:33 +02:00
Roberto Viola
f2c3c0c63c Merge branch 'master' into Gear-UI-on-Zwift-#2391 2024-09-06 11:30:16 +02:00
Roberto Viola
e6a7210609 Update virtualbike.cpp 2024-09-06 11:28:14 +02:00
Roberto Viola
afd1953117 gears works on android too! 2024-08-20 08:33:17 +02:00
Roberto Viola
5a18a00295 adding the android part 2024-08-20 07:29:46 +02:00
Roberto Viola
d0acc202df trying also on dircon but it doesn't work 2024-08-19 10:26:19 +02:00
Roberto Viola
97702e8ae2 dynamic gears 2024-08-19 09:19:28 +02:00
Roberto Viola
ef017273d9 Update virtualbike_zwift.swift 2024-08-18 20:29:33 +02:00
Roberto Viola
66dc8bbb6f trying on ios 2024-08-18 16:12:23 +02:00
16 changed files with 585 additions and 40 deletions

View File

@@ -1320,6 +1320,7 @@
87A0770E29B641D500A368BF /* wahookickrheadwind.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wahookickrheadwind.h; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.h; sourceTree = "<group>"; };
87A0770F29B641D500A368BF /* wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = wahookickrheadwind.cpp; path = ../src/devices/wahookickrheadwind/wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A0771129B6420200A368BF /* moc_wahookickrheadwind.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_wahookickrheadwind.cpp; sourceTree = "<group>"; };
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier2ad9.h; path = ../src/characteristics/characteristicnotifier2ad9.h; sourceTree = "<group>"; };
87A0C4B7262329A600121A76 /* npecablebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = npecablebike.cpp; path = ../src/devices/npecablebike/npecablebike.cpp; sourceTree = "<group>"; };
87A0C4B8262329A600121A76 /* cscbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cscbike.h; path = ../src/devices/cscbike/cscbike.h; sourceTree = "<group>"; };
87A0C4B9262329A600121A76 /* cscbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cscbike.cpp; path = ../src/devices/cscbike/cscbike.cpp; sourceTree = "<group>"; };
@@ -2078,6 +2079,7 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */,
@@ -4066,7 +4068,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4257,7 +4259,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4484,7 +4486,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4580,7 +4582,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4672,7 +4674,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4786,7 +4788,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 905;
CURRENT_PROJECT_VERSION = 906;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -1,6 +1,7 @@
#include "devices/bike.h"
#include "qdebugfixup.h"
#include "homeform.h"
#include <QSettings>
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
@@ -106,6 +107,9 @@ void bike::setGears(double gears) {
return;
}
m_gears = gears;
if(homeform::singleton()) {
homeform::singleton()->updateGearsValue();
}
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);

View File

@@ -3906,6 +3906,15 @@ QString homeform::startIcon() {
return QLatin1String("");
}
void homeform::updateGearsValue() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if (settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble() == 1.0 || gears_zwift_ratio)
this->gears->setValue(QString::number(((bike *)bluetoothManager->device())->gears()));
else
this->gears->setValue(QString::number(((bike *)bluetoothManager->device())->gears(), 'f', 1));
}
QString homeform::signal() {
if (!bluetoothManager) {
return QStringLiteral("icons/icons/signal-1.png");
@@ -4398,11 +4407,7 @@ void homeform::update() {
this->target_power->setValue(
QString::number(((bike *)bluetoothManager->device())->lastRequestedPower().value(), 'f', 0));
this->resistance->setValue(QString::number(resistance, 'f', 0));
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if (settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble() == 1.0 || gears_zwift_ratio)
this->gears->setValue(QString::number(((bike *)bluetoothManager->device())->gears()));
else
this->gears->setValue(QString::number(((bike *)bluetoothManager->device())->gears(), 'f', 1));
updateGearsValue();
this->resistance->setSecondLine(
QStringLiteral("AVG: ") +

View File

@@ -196,6 +196,7 @@ class homeform : public QObject {
public:
static homeform *singleton() { return m_singleton; }
bluetooth *bluetoothManager;
QByteArray currentPelotonImage();
Q_INVOKABLE void save_screenshot() {
@@ -603,6 +604,7 @@ class homeform : public QObject {
QString getStravaAuthUrl() { return stravaAuthUrl; }
bool stravaWebVisible() { return stravaAuthWebVisible; }
trainprogram *trainingProgram() { return trainProgram; }
void updateGearsValue();
DataObject *speed;
DataObject *inclination;
@@ -677,7 +679,6 @@ class homeform : public QObject {
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
QList<QObject *> dataList;
QList<SessionLine> Session;
bluetooth *bluetoothManager;
QQmlApplicationEngine *engine;
trainprogram *trainProgram = nullptr;
trainprogram *previewTrainProgram = nullptr;

View File

@@ -27,6 +27,12 @@ let PowerFeatureCharacteristicUUID = CBUUID(string: "0x2A65")
let PowerSensorLocationCharacteristicUUID = CBUUID(string: "0x2A5D")
let PowerMeasurementCharacteristicUUID = CBUUID(string: "0x2A63")
// Zwift Play
let ZwiftPlayServiceUUID = CBUUID(string: "00000001-19ca-4651-86e5-fa29dcdd09d1")
let ZwiftPlayWriteUUID = CBUUID(string: "00000003-19ca-4651-86e5-fa29dcdd09d1")
let ZwiftPlayReadUUID = CBUUID(string: "00000002-19ca-4651-86e5-fa29dcdd09d1")
let ZwiftPlayIndicateUUID = CBUUID(string:"00000004-19ca-4651-86e5-fa29dcdd09d1")
@objc public class virtualbike_ios_swift: NSObject {
private var peripheralManager: BLEPeripheralManager!

View File

@@ -18,14 +18,14 @@ class lockscreen {
void virtualbike_setHeartRate(unsigned char heartRate);
void virtualbike_setCadence(unsigned short crankRevolutions, unsigned short lastCrankEventTime);
void virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility);
void virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility, bool zwift_play_emulator);
double virtualbike_getCurrentSlope();
double virtualbike_getCurrentCRR();
double virtualbike_getCurrentCW();
double virtualbike_getPowerRequested();
bool virtualbike_updateFTMS(unsigned short normalizeSpeed, unsigned char currentResistance,
unsigned short currentCadence, unsigned short currentWatt,
unsigned short CrankRevolutions, unsigned short LastCrankEventTime);
unsigned short CrankRevolutions, unsigned short LastCrankEventTime, signed short Gears);
int virtualbike_getLastFTMSMessage(unsigned char *message);
// virtualrower

View File

@@ -101,9 +101,9 @@ void lockscreen::virtualbike_setCadence(unsigned short crankRevolutions, unsigne
[_virtualbike updateCadenceWithCrankRevolutions:crankRevolutions LastCrankEventTime:lastCrankEventTime];
}
void lockscreen::virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility)
void lockscreen::virtualbike_zwift_ios(bool disable_hr, bool garmin_bluetooth_compatibility, bool zwift_play_emulator)
{
_virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr:disable_hr garmin_bluetooth_compatibility:garmin_bluetooth_compatibility];
_virtualbike_zwift = [[virtualbike_zwift alloc] initWithDisable_hr:disable_hr garmin_bluetooth_compatibility:garmin_bluetooth_compatibility zwift_play_emulator:zwift_play_emulator];
}
void lockscreen::virtualrower_ios()
@@ -147,10 +147,10 @@ double lockscreen::virtualbike_getPowerRequested()
return 0;
}
bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime)
bool lockscreen::virtualbike_updateFTMS(UInt16 normalizeSpeed, UInt8 currentResistance, UInt16 currentCadence, UInt16 currentWatt, UInt16 CrankRevolutions, UInt16 LastCrankEventTime, signed short Gears)
{
if(_virtualbike_zwift != nil)
return [_virtualbike_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt CrankRevolutions:CrankRevolutions LastCrankEventTime:LastCrankEventTime];
return [_virtualbike_zwift updateFTMSWithNormalizeSpeed:normalizeSpeed currentCadence:currentCadence currentResistance:currentResistance currentWatt:currentWatt CrankRevolutions:CrankRevolutions LastCrankEventTime:LastCrankEventTime Gears:Gears];
return 0;
}

View File

@@ -13,7 +13,9 @@
@interface swiftDebug : NSObject
- (void)qtDebug:(NSString *)inputString;;
- (void)qtDebug:(NSString *)inputString;
- (void)gearUp;
- (void)gearDown;
@end

View File

@@ -5,6 +5,8 @@
// Created by Roberto Viola on 14/12/23.
//
#import "swiftDebug.h"
#import "homeform.h"
#import "bike.h"
#include <QDebug>
@implementation swiftDebug
@@ -13,4 +15,12 @@
qDebug() << inputString;
}
- (void)gearUp {
((bike*)(homeform::singleton()->bluetoothManager->device()))->gearUp();
}
- (void)gearDown {
((bike*)(homeform::singleton()->bluetoothManager->device()))->gearDown();
}
@end

View File

@@ -11,9 +11,9 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
@objc public class virtualbike_zwift: NSObject {
private var peripheralManager: BLEPeripheralManagerZwift!
@objc public init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) {
@objc public init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool, zwift_play_emulator: Bool) {
super.init()
peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr, garmin_bluetooth_compatibility: garmin_bluetooth_compatibility)
peripheralManager = BLEPeripheralManagerZwift(disable_hr: disable_hr, garmin_bluetooth_compatibility: garmin_bluetooth_compatibility, zwift_play_emulator: zwift_play_emulator)
}
@objc public func updateHeartRate(HeartRate: UInt8)
@@ -41,7 +41,7 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
return peripheralManager.PowerRequested;
}
@objc public func updateFTMS(normalizeSpeed: UInt16, currentCadence: UInt16, currentResistance: UInt8, currentWatt: UInt16, CrankRevolutions: UInt16, LastCrankEventTime: UInt16) -> Bool
@objc public func updateFTMS(normalizeSpeed: UInt16, currentCadence: UInt16, currentResistance: UInt8, currentWatt: UInt16, CrankRevolutions: UInt16, LastCrankEventTime: UInt16, Gears: Int16) -> Bool
{
peripheralManager.NormalizeSpeed = normalizeSpeed
peripheralManager.CurrentCadence = currentCadence
@@ -49,6 +49,7 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
peripheralManager.CurrentWatt = currentWatt
peripheralManager.lastCrankEventTime = LastCrankEventTime
peripheralManager.crankRevolutions = CrankRevolutions
peripheralManager.CurrentGears = Gears
return peripheralManager.connected;
}
@@ -62,6 +63,7 @@ let TrainingStatusUuid = CBUUID(string: "0x2AD3");
class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
private var garmin_bluetooth_compatibility: Bool = false
private var zwift_play_emulator: Bool = false
private var disable_hr: Bool = false
private var peripheralManager: CBPeripheralManager!
@@ -84,6 +86,7 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
public var CurrentCadence: UInt16! = 0
public var CurrentResistance: UInt8! = 0
public var CurrentWatt: UInt16! = 0
public var CurrentZwiftGear: UInt8! = 8
private var CSCService: CBMutableService!
private var CSCFeatureCharacteristic: CBMutableCharacteristic!
@@ -98,23 +101,33 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
private var PowerSensorLocationCharacteristic: CBMutableCharacteristic!
private var PowerMeasurementCharacteristic: CBMutableCharacteristic!
private var ZwiftPlayService: CBMutableService!
private var ZwiftPlayReadCharacteristic: CBMutableCharacteristic!
private var ZwiftPlayWriteCharacteristic: CBMutableCharacteristic!
private var ZwiftPlayIndicateCharacteristic: CBMutableCharacteristic!
public var LastFTMSMessageReceived: Data?
public var LastFTMSMessageReceivedAndPassed: Data?
public var serviceToggle: UInt8 = 0
public var CurrentGears: Int16 = 0
public var connected: Bool = false
private var notificationTimer: Timer! = nil
var updateQueue: [(characteristic: CBMutableCharacteristic, data: Data)] = []
let SwiftDebug = swiftDebug()
//var delegate: BLEPeripheralManagerDelegate?
init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool) {
init(disable_hr: Bool, garmin_bluetooth_compatibility: Bool, zwift_play_emulator: Bool) {
super.init()
self.disable_hr = disable_hr
self.garmin_bluetooth_compatibility = garmin_bluetooth_compatibility
self.zwift_play_emulator = zwift_play_emulator
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
@@ -254,12 +267,54 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
PowerMeasurementCharacteristic]
self.peripheralManager.add(PowerService)
// ZwiftPlay
if(self.zwift_play_emulator) {
self.ZwiftPlayService = CBMutableService(type: ZwiftPlayServiceUUID, primary: true)
let ZwiftPlayReadProperties: CBCharacteristicProperties = [.notify, .read]
let ZwiftPlayReadPermissions: CBAttributePermissions = [.readable]
self.ZwiftPlayReadCharacteristic = CBMutableCharacteristic(type: ZwiftPlayReadUUID,
properties: ZwiftPlayReadProperties,
value: nil,
permissions: ZwiftPlayReadPermissions)
let ZwiftPlayWriteProperties: CBCharacteristicProperties = [.write]
let ZwiftPlayWritePermissions: CBAttributePermissions = [.writeable]
self.ZwiftPlayWriteCharacteristic = CBMutableCharacteristic(type: ZwiftPlayWriteUUID,
properties: ZwiftPlayWriteProperties,
value: nil,
permissions: ZwiftPlayWritePermissions)
let ZwiftPlayIndicateProperties: CBCharacteristicProperties = [.indicate]
let ZwiftPlayIndicatePermissions: CBAttributePermissions = [.readable]
self.ZwiftPlayIndicateCharacteristic = CBMutableCharacteristic(type: ZwiftPlayIndicateUUID,
properties: ZwiftPlayIndicateProperties,
value: nil,
permissions: ZwiftPlayIndicatePermissions)
ZwiftPlayService.characteristics = [ZwiftPlayReadCharacteristic,
ZwiftPlayWriteCharacteristic,
ZwiftPlayIndicateCharacteristic]
self.peripheralManager.add(ZwiftPlayService)
}
default:
print("Peripheral manager is down")
}
}
func sendUpdates() -> Bool {
guard !updateQueue.isEmpty else { return false }
let update = updateQueue.removeFirst()
let hexString = update.data.map { String(format: "%02x", $0) }.joined(separator: " ")
let debugMessage = "virtualbike_zwift send: " + String(describing: update.characteristic) + " " + hexString
SwiftDebug.qtDebug(debugMessage)
peripheralManager.updateValue(update.data, for: update.characteristic, onSubscribedCentrals: nil)
return true
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
if let uwError = error {
print("Failed to add service with error: \(uwError.localizedDescription)")
@@ -297,8 +352,13 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
if requests.first!.characteristic == self.FitnessMachineControlPointCharacteristic {
SwiftDebug.qtDebug("virtualbike_zwift didReceiveWrite: " + String(describing: requests.first!.value))
if let value = requests.first?.value {
let hexString = value.map { String(format: "%02x", $0) }.joined(separator: " ")
let debugMessage = "virtualbike_zwift didReceiveWrite: " + String(describing: requests.first!.characteristic) + " " + hexString
SwiftDebug.qtDebug(debugMessage)
}
if requests.first!.characteristic == self.FitnessMachineControlPointCharacteristic {
if(LastFTMSMessageReceived == nil || LastFTMSMessageReceived?.count == 0) {
LastFTMSMessageReceived = requests.first!.value
}
@@ -323,10 +383,208 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
let responseData = Data(bytes: &response, count: 3)
self.peripheralManager.updateValue(responseData, for: self.FitnessMachineControlPointCharacteristic, onSubscribedCentrals: nil)
} else if requests.first!.characteristic == ZwiftPlayWriteCharacteristic && zwift_play_emulator {
let receivedData = requests.first!.value ?? Data()
let expectedHexArray: [UInt8] = [0x52, 0x69, 0x64, 0x65, 0x4F, 0x6E, 0x02, 0x01]
let expectedHexArray2: [UInt8] = [0x41, 0x08, 0x05]
let expectedHexArray3: [UInt8] = [0x00, 0x08, 0x88, 0x04]
let expectedHexArray4: [UInt8] = [0x04, 0x2a, 0x0a, 0x10, 0xc0, 0xbb, 0x01, 0x20]
let expectedHexArray5: [UInt8] = [0x04, 0x22]
let expectedHexArray6: [UInt8] = [0x04, 0x2a, 0x04, 0x10]
let expectedHexArray7: [UInt8] = [0x04, 0x2a, 0x03, 0x10]
let receivedBytes = [UInt8](receivedData.prefix(expectedHexArray.count))
if receivedBytes == expectedHexArray {
SwiftDebug.qtDebug("Zwift Play Ask 1")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [0x2a, 0x08, 0x03, 0x12, 0x11, 0x22, 0x0f, 0x41, 0x54, 0x58, 0x20, 0x30, 0x34, 0x2c, 0x20, 0x53, 0x54, 0x58, 0x20, 0x30, 0x34, 0x00]
var responseData = Data(bytes: &response, count: 22)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
response = [0x2a, 0x08, 0x03, 0x12, 0x0d, 0x22, 0x0b, 0x52, 0x49, 0x44, 0x45, 0x5f, 0x4f, 0x4e, 0x28, 0x32, 0x29, 0x00]
responseData = Data(bytes: &response, count: 18)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
response = [0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e, 0x02, 0x00]
responseData = Data(bytes: &response, count: 8)
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
}
let receivedBytes2 = [UInt8](receivedData.prefix(expectedHexArray2.count))
if receivedBytes2 == expectedHexArray2 {
SwiftDebug.qtDebug("Zwift Play Ask 2")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [0x3c, 0x08, 0x00, 0x12, 0x32, 0x0a, 0x30, 0x08, 0x80, 0x04, 0x12, 0x04, 0x05, 0x00, 0x05, 0x01, 0x1a, 0x0b, 0x4b, 0x49, 0x43, 0x4b, 0x52, 0x20, 0x43, 0x4f, 0x52, 0x45, 0x00, 0x32, 0x0f, 0x34, 0x30, 0x32, 0x34, 0x31, 0x38, 0x30, 0x30, 0x39, 0x38, 0x34, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x01, 0x31, 0x42, 0x04, 0x08, 0x01, 0x10, 0x14 ]
var responseData = Data(bytes: &response, count: 55)
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
}
let receivedBytes3 = [UInt8](receivedData.prefix(expectedHexArray3.count))
if receivedBytes3 == expectedHexArray3 {
SwiftDebug.qtDebug("Zwift Play Ask 3")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [0x3c, 0x08, 0x88, 0x04, 0x12, 0x06, 0x0a, 0x04, 0x40, 0xc0, 0xbb, 0x01 ]
var responseData = Data(bytes: &response, count: 12)
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
}
let receivedBytes4 = [UInt8](receivedData.prefix(expectedHexArray4.count))
if receivedBytes4 == expectedHexArray4 {
SwiftDebug.qtDebug("Zwift Play Ask 4")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [ 0x03, 0x08, 0x00, 0x10, 0x00, 0x18, 0x59, 0x20, 0x00, 0x28, 0x00, 0x30, 0x9b, 0xed, 0x01]
var responseData = Data(bytes: &response, count: 15)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
response = [ 0x2a, 0x08, 0x03, 0x12, 0x27, 0x22, 0x25, 0x67, 0x61, 0x70, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x28, 0x32, 0x29, 0x3a, 0x20, 0x37, 0x32, 0x2c, 0x20, 0x37, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x36, 0x30, 0x30, 0x00 ]
responseData = Data(bytes: &response, count: 44)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
}
let receivedBytes5 = [UInt8](receivedData.prefix(expectedHexArray5.count))
if receivedBytes5 == expectedHexArray5 {
SwiftDebug.qtDebug("Zwift Play Ask 5")
peripheral.respond(to: requests.first!, withResult: .success)
// 04 22 02 10 1a TODO
var slope: [UInt8] = [ receivedBytes[4], 0x00 ]
if receivedBytes[2] == 0x03 {
slope[1] = receivedBytes[5]
}
self.CurrentSlope = Double(UInt16(slope[0]) + ((UInt16(slope[1]) << 8) & 0xFF00)) / 4.0
slope[0] = UInt8(UInt16(self.CurrentSlope) & 0xFF)
slope[1] = UInt8((UInt16(self.CurrentSlope) >> 8) & 0x00FF)
LastFTMSMessageReceived = Data([0x11, 0x00, 0x00, slope[0], slope[1], 0x00, 0x00])
var response: [UInt8] = [ 0x3c, 0x08, 0x88, 0x04, 0x12, 0x06, 0x0a, 0x04, 0x40, 0xc0, 0xbb, 0x01 ]
var responseData = Data(bytes: &response, count: 12)
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
}
let receivedBytes6 = [UInt8](receivedData.prefix(expectedHexArray6.count))
if receivedBytes6 == expectedHexArray6 {
SwiftDebug.qtDebug("Zwift Play Ask 6")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [ 0x3c, 0x08, 0x88, 0x04, 0x12, 0x06, 0x0a, 0x04, 0x40, 0xc0, 0xbb, 0x01 ]
response[9] = receivedData[4]
response[10] = receivedData[5]
response[11] = receivedData[6]
handleZwiftGear(receivedData[4...])
var responseData = Data(bytes: &response, count: 12)
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
response = [0x03, 0x08, 0x00, 0x10, 0x00, 0x18, 0xe7, 0x02, 0x20, 0x00, 0x28, 0x96, 0x14, 0x30, 0x9b, 0xed, 0x01]
responseData = Data(bytes: &response, count: 17)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
}
let receivedBytes7 = [UInt8](receivedData.prefix(expectedHexArray7.count))
if receivedBytes7 == expectedHexArray7 {
SwiftDebug.qtDebug("Zwift Play Ask 7")
peripheral.respond(to: requests.first!, withResult: .success)
var response: [UInt8] = [0x03, 0x08, 0x00, 0x10, 0x00, 0x18, 0xe7, 0x02, 0x20, 0x00, 0x28, 0x00, 0x30, 0x9b, 0xed, 0x01]
var responseData = Data(bytes: &response, count: 17)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
response = [ 0x3c, 0x08, 0x88, 0x04, 0x12, 0x05, 0x0a, 0x03, 0x40, 0x8c, 0x60 ]
response[9] = receivedData[4]
response[10] = receivedData[5]
responseData = Data(bytes: &response, count: 11)
handleZwiftGear(receivedData[4...])
updateQueue.append((ZwiftPlayIndicateCharacteristic, responseData))
}
}
}
func handleZwiftGear(_ array: Data) {
var g : UInt8 = 0;
if(array[4] == 0xCC && array[5] == 0x3a) {
g = 1;
} else if(array[4] == 0xFC && array[5] == 0x43) {
g = 2;
} else if(array[4] == 0xac && array[5] == 0x4d) {
g = 3;
} else if(array[4] == 0xd5 && array[5] == 0x56) {
g = 4;
} else if(array[4] == 0x8c && array[5] == 0x60) {
g = 5;
} else if(array[4] == 0xe8 && array[5] == 0x6b) {
g = 6;
} else if(array[4] == 0xc4 && array[5] == 0x77) {
g = 7;
} else if(array[4] == 0xa0 && array[5] == 0x83 && array[6] == 0x01) {
g = 8;
} else if(array[4] == 0xa8 && array[5] == 0x91 && array[6] == 0x01) {
g = 9;
} else if(array[4] == 0xb0 && array[5] == 0x9f && array[6] == 0x01) {
g = 10;
} else if(array[4] == 0xb8 && array[5] == 0xad && array[6] == 0x01) {
g = 11;
} else if(array[4] == 0xc0 && array[5] == 0xbb && array[6] == 0x01) {
g = 12;
} else if(array[4] == 0xf3 && array[5] == 0xcb && array[6] == 0x01) {
g = 13;
} else if(array[4] == 0xa8 && array[5] == 0xdc && array[6] == 0x01) {
g = 14;
} else if(array[4] == 0xdc && array[5] == 0xec && array[6] == 0x01) {
g = 15;
} else if(array[4] == 0x90 && array[5] == 0xfd && array[6] == 0x01) {
g = 16;
} else if(array[4] == 0xd4 && array[5] == 0x90 && array[6] == 0x02) {
g = 17;
} else if(array[4] == 0x98 && array[5] == 0xa4 && array[6] == 0x02) {
g = 18;
} else if(array[4] == 0xdc && array[5] == 0xb7 && array[6] == 0x02) {
g = 19;
} else if(array[4] == 0x9f && array[5] == 0xcb && array[6] == 0x02) {
g = 20;
} else if(array[4] == 0xd8 && array[5] == 0xe2 && array[6] == 0x02) {
g = 21;
} else if(array[4] == 0x90 && array[5] == 0xfa && array[6] == 0x02) {
g = 22;
} else if(array[4] == 0xc8 && array[5] == 0x91 && array[6] == 0x03) {
g = 23;
} else if(array[4] == 0xf3 && array[5] == 0xac && array[6] == 0x03) {
g = 24;
}
if (g < self.CurrentZwiftGear) {
for _ in 0..<(self.CurrentZwiftGear - g) {
SwiftDebug.gearDown()
}
} else if (g > self.CurrentZwiftGear) {
for _ in 0..<(g - self.CurrentZwiftGear) {
SwiftDebug.gearUp()
}
}
self.CurrentZwiftGear = g
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
SwiftDebug.qtDebug("virtualbike_zwift didReceiveRead: " + String(describing: request.characteristic))
if request.characteristic == self.heartRateCharacteristic {
request.value = self.calculateHeartRate()
self.peripheralManager.respond(to: request, withResult: .success)
@@ -478,13 +736,24 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
}
@objc func updateSubscribers() {
if(self.serviceToggle == 3 || garmin_bluetooth_compatibility)
{
let powerData = self.calculatePower()
let ok = self.peripheralManager.updateValue(powerData, for: self.PowerMeasurementCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = 0
}
if(self.serviceToggle == 4 || garmin_bluetooth_compatibility || (self.serviceToggle == 3 && !zwift_play_emulator))
{
let powerData = self.calculatePower()
let ok = self.peripheralManager.updateValue(powerData, for: self.PowerMeasurementCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = 0
}
} else if(self.serviceToggle == 3) {
if(!sendUpdates()) {
let ZwiftPlayArray : [UInt8] = [ 0x03, 0x08, 0x00, 0x10, 0x00, 0x18, 0xe7, 0x02, 0x20, 0x00, 0x28, 0x00, 0x30, 0x9b, 0xed, 0x01 ]
let ZwiftPlayData = Data(bytes: ZwiftPlayArray, count: 16)
let ok = self.peripheralManager.updateValue(ZwiftPlayData, for: self.ZwiftPlayReadCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = self.serviceToggle + 1
}
} else {
self.serviceToggle = self.serviceToggle + 1
}
} else if(self.serviceToggle == 2) {
let cadenceData = self.calculateCadence()
let ok = self.peripheralManager.updateValue(cadenceData, for: self.CSCMeasurementCharacteristic, onSubscribedCentrals: nil)

0
src/qdomyos-zwift.pri Executable file → Normal file
View File

View File

@@ -768,8 +768,9 @@ const QString QZSettings::peloton_date_format = QStringLiteral("peloton_date_for
const QString QZSettings::default_peloton_date_format = QStringLiteral("MM/dd/yy");
const QString QZSettings::force_resistance_instead_inclination = QStringLiteral("force_resistance_instead_inclination");
const QString QZSettings::proform_treadmill_575i = QStringLiteral("proform_treadmill_575i");
const QString QZSettings::zwift_play_emulator = QStringLiteral("zwift_play_emulator");
const uint32_t allSettingsCount = 650;
const uint32_t allSettingsCount = 651;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1426,6 +1427,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::peloton_date_format, QZSettings::default_peloton_date_format},
{QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination},
{QZSettings::proform_treadmill_575i, QZSettings::default_proform_treadmill_575i},
{QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {

View File

@@ -2150,6 +2150,9 @@ class QZSettings {
static const QString proform_treadmill_575i;
static constexpr bool default_proform_treadmill_575i = false;
static const QString zwift_play_emulator;
static constexpr bool default_zwift_play_emulator = false;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

View File

@@ -984,6 +984,9 @@ import QtQuick.Dialogs 1.0
property string peloton_date_format: "MM/dd/yy"
property bool force_resistance_instead_inclination: false
property bool proform_treadmill_575i: false
// from version 2.18.1
property bool zwift_play_emulator: false
}
function paddingZeros(text, limit) {
@@ -4843,6 +4846,34 @@ import QtQuick.Dialogs 1.0
color: Material.color(Material.Lime)
}
SwitchDelegate {
text: qsTr("Get Gears from Zwift")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.zwift_play_emulator
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.zwift_play_emulator = checked; window.settings_restart_to_apply = true; }
}
Label {
text: qsTr("This setting bring virtual gearing from zwift to all the bikes directly from the Zwift interface. You have to configure zwift: Wahoo virtual device from QZ as for power and cadence, and your QZ device as resistance. Default: disabled.")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
RowLayout {
spacing: 10
Label {

View File

@@ -29,6 +29,7 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
settings.value(QZSettings::virtual_device_echelon, QZSettings::default_virtual_device_echelon).toBool();
bool ifit = settings.value(QZSettings::virtual_device_ifit, QZSettings::default_virtual_device_ifit).toBool();
bool garmin_bluetooth_compatibility = settings.value(QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility).toBool();
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
if (settings.value(QZSettings::dircon_yes, QZSettings::default_dircon_yes).toBool()) {
dirconManager = new DirconManager(Bike, bikeResistanceOffset, bikeResistanceGain, this);
@@ -59,7 +60,7 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
qDebug() << "ios_zwift_workaround activated!";
h = new lockscreen();
h->virtualbike_zwift_ios(
settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool(), garmin_bluetooth_compatibility);
settings.value(QZSettings::bike_heartrate_service, QZSettings::default_bike_heartrate_service).toBool(), garmin_bluetooth_compatibility, zwift_play_emulator);
} else
#endif
@@ -196,6 +197,33 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
serviceDataFIT.addCharacteristic(charDataFIT4);
serviceDataFIT.addCharacteristic(charDataFIT5);
serviceDataFIT.addCharacteristic(charDataFIT6);
if(zwift_play_emulator) {
QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1")));
charData.setProperties(QLowEnergyCharacteristic::Write | QLowEnergyCharacteristic::WriteNoResponse);
QLowEnergyCharacteristicData charData2;
charData2.setUuid(QBluetoothUuid(QStringLiteral("00000002-19ca-4651-86e5-fa29dcdd09d1")));
charData2.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig2(QBluetoothUuid::ClientCharacteristicConfiguration, descriptor);
charData2.addDescriptor(clientConfig2);
QLowEnergyCharacteristicData charData3;
charData3.setUuid(QBluetoothUuid(QStringLiteral("00000004-19ca-4651-86e5-fa29dcdd09d1")));
charData3.setProperties(QLowEnergyCharacteristic::Indicate);
QByteArray descriptorIndicate;
descriptorIndicate.append((char)0x02);
descriptorIndicate.append((char)0x00);
const QLowEnergyDescriptorData clientConfig3(QBluetoothUuid::ClientCharacteristicConfiguration, descriptorIndicate);
charData3.addDescriptor(clientConfig3);
serviceDataZwiftPlayBike.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceDataZwiftPlayBike.setUuid(QBluetoothUuid(QStringLiteral("00000001-19ca-4651-86e5-fa29dcdd09d1")));
serviceDataZwiftPlayBike.addCharacteristic(charData);
serviceDataZwiftPlayBike.addCharacteristic(charData2);
serviceDataZwiftPlayBike.addCharacteristic(charData3);
}
} else if (power) {
QLowEnergyCharacteristicData charData;
@@ -393,6 +421,10 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
if (!cadence && !power) {
serviceFIT = leController->addService(serviceDataFIT);
if(zwift_play_emulator) {
QThread::msleep(100); // give time to Android to add the service async.ly
serviceZwiftPlayBike = leController->addService(serviceDataZwiftPlayBike);
}
} else {
service = leController->addService(serviceData);
}
@@ -420,6 +452,8 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
if (!cadence && !power) {
QObject::connect(serviceFIT, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
QObject::connect(serviceZwiftPlayBike, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
} else {
QObject::connect(service, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
@@ -463,6 +497,7 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QByteArray reply;
QSettings settings;
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
bool echelon =
settings.value(QZSettings::virtual_device_echelon, QZSettings::default_virtual_device_echelon).toBool();
bool ifit = settings.value(QZSettings::virtual_device_ifit, QZSettings::default_virtual_device_ifit).toBool();
@@ -474,14 +509,13 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
qDebug() << QStringLiteral("characteristicChanged ") + QString::number(characteristic.uuid().toUInt16()) +
QStringLiteral(" ") + newValue.toHex(' ');
if (!echelon && !ifit) {
lastFTMSFrameReceived = QDateTime::currentMSecsSinceEpoch();
emit ftmsCharacteristicChanged(characteristic, newValue);
}
switch (characteristic.uuid().toUInt16()) {
case 0x2AD9: // Fitness Machine Control Point
if (!echelon && !ifit) {
lastFTMSFrameReceived = QDateTime::currentMSecsSinceEpoch();
emit ftmsCharacteristicChanged(characteristic, newValue);
}
if (writeP2AD9->writeProcess(0x2AD9, newValue, reply) == CP_OK) {
QLowEnergyCharacteristic characteristic =
@@ -801,6 +835,121 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
}
}
//********************ZWIFT PLAY**************
if(characteristic.uuid().toString().contains(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1")) && zwift_play_emulator) {
static const QByteArray expectedHexArray = QByteArray::fromHex("52696465 4F6E0201");
static const QByteArray expectedHexArray2 = QByteArray::fromHex("410805");
static const QByteArray expectedHexArray3 = QByteArray::fromHex("00088804");
static const QByteArray expectedHexArray4 = QByteArray::fromHex("042A0A10 C0BB0120");
static const QByteArray expectedHexArray5 = QByteArray::fromHex("0422");
static const QByteArray expectedHexArray6 = QByteArray::fromHex("042A0410");
static const QByteArray expectedHexArray7 = QByteArray::fromHex("042A0310");
QByteArray receivedData = newValue;
QLowEnergyCharacteristic zwiftPlayRead =
serviceZwiftPlayBike->characteristic(QBluetoothUuid(QStringLiteral("00000002-19ca-4651-86e5-fa29dcdd09d1")));
QLowEnergyCharacteristic zwiftPlayIndicate =
serviceZwiftPlayBike->characteristic(QBluetoothUuid(QStringLiteral("00000004-19ca-4651-86e5-fa29dcdd09d1")));
if (receivedData.startsWith(expectedHexArray)) {
qDebug() << "Zwift Play Ask 1";
QByteArray response = QByteArray::fromHex("2a08031211220f4154582030342c2053545820303400");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
response = QByteArray::fromHex("2a0803120d220b524944455f4f4e28322900");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
response = QByteArray::fromHex("526964654f6e0200");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
}
else if (receivedData.startsWith(expectedHexArray2)) {
qDebug() << "Zwift Play Ask 2";
QByteArray response = QByteArray::fromHex("3c080012320a3008800412040500050"
"11a0b4b49434b5220434f524500320f"
"3430323431383030393834000000003a01314204080110140");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
}
else if (receivedData.startsWith(expectedHexArray3)) {
qDebug() << "Zwift Play Ask 3";
QByteArray response = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
}
else if (receivedData.startsWith(expectedHexArray4)) {
qDebug() << "Zwift Play Ask 4";
QByteArray response = QByteArray::fromHex("0308001000185920002800309bed01");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
response = QByteArray::fromHex("2a08031227222567"
"61705f706172616d735f6368616e6765"
"2832293a2037322c2037322c20302c20"
"36303000");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
}
else if (receivedData.startsWith(expectedHexArray5)) {
qDebug() << "Zwift Play Ask 5";
QByteArray slope(2, 0);
slope[0] = receivedData[4];
if (receivedData.at(2) == (uint8_t)0x03) {
slope[1] = receivedData[5];
}
double CurrentSlope = (qint16(slope[0]) + ((qint16(slope[1]) << 8) & 0xFF00)) / 4.0;
slope[0] = quint8(qint16(CurrentSlope) & 0xFF);
slope[1] = quint8((qint16(CurrentSlope) >> 8) & 0x00FF);
QBluetoothUuid targetUuid = QBluetoothUuid(quint16(0x2ad9));
QLowEnergyCharacteristic targetCharacteristic;
for (const QLowEnergyCharacteristic &characteristic : serviceFIT->characteristics()) {
if (characteristic.uuid() == targetUuid) {
targetCharacteristic = characteristic;
break; // Abbiamo trovato la caratteristica, usciamo dal ciclo
}
}
if (targetCharacteristic.isValid()) {
characteristicChanged(targetCharacteristic, QByteArray::fromHex("110000") + slope + QByteArray::fromHex("0000"));
QByteArray response = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
} else {
qDebug() << "ERROR! Zwift Play Ask 5 without answer!";
}
}
else if (receivedData.startsWith(expectedHexArray6)) {
qDebug() << "Zwift Play Ask 6";
QByteArray response = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
response[9] = receivedData[4];
response[10] = receivedData[5];
response[11] = receivedData[6];
handleZwiftGear(receivedData.mid(4));
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
response = QByteArray::fromHex("03080010001827e7 20002896143093ed01");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
}
else if (receivedData.startsWith(expectedHexArray7)) {
qDebug() << "Zwift Play Ask 7";
QByteArray response = QByteArray::fromHex("03080010001827e7 2000 28 00 3093ed01");
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
response = QByteArray::fromHex("3c088804120503408c60");
response[9] = receivedData[4];
response[10] = receivedData[5];
handleZwiftGear(receivedData.mid(4));
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
}
}
//******************** ECHELON ***************
if (characteristic.uuid().toString().contains(QStringLiteral("0bf669f2-45f2-11e7-9598-0800200c9a66"))) {
QLowEnergyCharacteristic characteristic =
@@ -884,6 +1033,48 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
}
}
void virtualbike::handleZwiftGear(const QByteArray &array)
{
uint8_t g = 0;
if (array.size() >= 3) {
if ((uint8_t)array[0] == (uint8_t)0xCC && (uint8_t)array[1] == (uint8_t)0x3A) g = 1;
else if ((uint8_t)array[0] == (uint8_t)0xFC && (uint8_t)array[1] == (uint8_t)0x43) g = 2;
else if ((uint8_t)array[0] == (uint8_t)0xAC && (uint8_t)array[1] == (uint8_t)0x4D) g = 3;
else if ((uint8_t)array[0] == (uint8_t)0xD5 && (uint8_t)array[1] == (uint8_t)0x56) g = 4;
else if ((uint8_t)array[0] == (uint8_t)0x8C && (uint8_t)array[1] == (uint8_t)0x60) g = 5;
else if ((uint8_t)array[0] == (uint8_t)0xE8 && (uint8_t)array[1] == (uint8_t)0x6B) g = 6;
else if ((uint8_t)array[0] == (uint8_t)0xC4 && (uint8_t)array[1] == (uint8_t)0x77) g = 7;
else if ((uint8_t)array[0] == (uint8_t)0xA0 && (uint8_t)array[1] == (uint8_t)0x83 && (uint8_t)array[2] == (uint8_t)0x01) g = 8;
else if ((uint8_t)array[0] == (uint8_t)0xA8 && (uint8_t)array[1] == (uint8_t)0x91 && (uint8_t)array[2] == (uint8_t)0x01) g = 9;
else if ((uint8_t)array[0] == (uint8_t)0xB0 && (uint8_t)array[1] == (uint8_t)0x9F && (uint8_t)array[2] == (uint8_t)0x01) g = 10;
else if ((uint8_t)array[0] == (uint8_t)0xB8 && (uint8_t)array[1] == (uint8_t)0xAD && (uint8_t)array[2] == (uint8_t)0x01) g = 11;
else if ((uint8_t)array[0] == (uint8_t)0xC0 && (uint8_t)array[1] == (uint8_t)0xBB && (uint8_t)array[2] == (uint8_t)0x01) g = 12;
else if ((uint8_t)array[0] == (uint8_t)0xF3 && (uint8_t)array[1] == (uint8_t)0xCB && (uint8_t)array[2] == (uint8_t)0x01) g = 13;
else if ((uint8_t)array[0] == (uint8_t)0xA8 && (uint8_t)array[1] == (uint8_t)0xDC && (uint8_t)array[2] == (uint8_t)0x01) g = 14;
else if ((uint8_t)array[0] == (uint8_t)0xDC && (uint8_t)array[1] == (uint8_t)0xEC && (uint8_t)array[2] == (uint8_t)0x01) g = 15;
else if ((uint8_t)array[0] == (uint8_t)0x90 && (uint8_t)array[1] == (uint8_t)0xFD && (uint8_t)array[2] == (uint8_t)0x01) g = 16;
else if ((uint8_t)array[0] == (uint8_t)0xD4 && (uint8_t)array[1] == (uint8_t)0x90 && (uint8_t)array[2] == (uint8_t)0x02) g = 17;
else if ((uint8_t)array[0] == (uint8_t)0x98 && (uint8_t)array[1] == (uint8_t)0xA4 && (uint8_t)array[2] == (uint8_t)0x02) g = 18;
else if ((uint8_t)array[0] == (uint8_t)0xDC && (uint8_t)array[1] == (uint8_t)0xB7 && (uint8_t)array[2] == (uint8_t)0x02) g = 19;
else if ((uint8_t)array[0] == (uint8_t)0x9F && (uint8_t)array[1] == (uint8_t)0xCB && (uint8_t)array[2] == (uint8_t)0x02) g = 20;
else if ((uint8_t)array[0] == (uint8_t)0xD8 && (uint8_t)array[1] == (uint8_t)0xE2 && (uint8_t)array[2] == (uint8_t)0x02) g = 21;
else if ((uint8_t)array[0] == (uint8_t)0x90 && (uint8_t)array[1] == (uint8_t)0xFA && (uint8_t)array[2] == (uint8_t)0x02) g = 22;
else if ((uint8_t)array[0] == (uint8_t)0xC8 && (uint8_t)array[1] == (uint8_t)0x91 && (uint8_t)array[2] == (uint8_t)0x03) g = 23;
else if ((uint8_t)array[0] == (uint8_t)0xF3 && (uint8_t)array[1] == (uint8_t)0xAC && (uint8_t)array[2] == (uint8_t)0x03) g = 24;
}
if (g < CurrentZwiftGear) {
for (int i = 0; i < CurrentZwiftGear - g; ++i) {
((bike*)Bike)->gearDown();
}
} else if (g > CurrentZwiftGear) {
for (int i = 0; i < g - CurrentZwiftGear; ++i) {
((bike*)Bike)->gearUp();
}
}
CurrentZwiftGear = g;
}
int virtualbike::iFit_pelotonToBikeResistance(int pelotonResistance) {
if (pelotonResistance <= 10) {
return 2;
@@ -996,6 +1187,7 @@ void virtualbike::reconnect() {
return;
}
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool battery = settings.value(QZSettings::battery_service, QZSettings::default_battery_service).toBool();
bool power = settings.value(QZSettings::bike_power_sensor, QZSettings::default_bike_power_sensor).toBool();
@@ -1019,6 +1211,10 @@ void virtualbike::reconnect() {
if (!cadence && !power) {
serviceFIT = leController->addService(serviceDataFIT);
if(zwift_play_emulator) {
QThread::msleep(100); // give time to Android to add the service async.ly
serviceZwiftPlayBike = leController->addService(serviceDataZwiftPlayBike);
}
} else {
service = leController->addService(serviceData);
}
@@ -1048,6 +1244,7 @@ void virtualbike::reconnect() {
void virtualbike::bikeProvider() {
QSettings settings;
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool battery = settings.value(QZSettings::battery_service, QZSettings::default_battery_service).toBool();
bool power = settings.value(QZSettings::bike_power_sensor, QZSettings::default_bike_power_sensor).toBool();
@@ -1070,7 +1267,7 @@ void virtualbike::bikeProvider() {
// really connected to a device
if (h->virtualbike_updateFTMS(normalizeSpeed, (char)Bike->currentResistance().value(),
(uint16_t)Bike->currentCadence().value() * 2, (uint16_t)normalizeWattage,
Bike->currentCrankRevolutions(), Bike->lastCrankEventTime())) {
Bike->currentCrankRevolutions(), Bike->lastCrankEventTime(), ((bike*)Bike)->gears())) {
h->virtualbike_setHeartRate(Bike->currentHeart().value());
uint8_t ftms_message[255];
@@ -1157,6 +1354,14 @@ void virtualbike::bikeProvider() {
return;
}
writeCharacteristic(serviceFIT, characteristic, value);
if(zwift_play_emulator) {
QLowEnergyCharacteristic characteristic1 =
serviceZwiftPlayBike->characteristic(QBluetoothUuid(QStringLiteral("00000002-19ca-4651-86e5-fa29dcdd09d1")));
const uint8_t v[] = {0x03, 0x08, 0x00, 0x10, 0x00, 0x18, 0xe7, 0x02, 0x20, 0x00, 0x28, 0x00, 0x30, 0x9b, 0xed, 0x01};
value = QByteArray::fromRawData((char*)v, 16);
writeCharacteristic(serviceZwiftPlayBike, characteristic1, value);
}
}
} else if (power) {
value.clear();

View File

@@ -50,6 +50,7 @@ class virtualbike : public virtualdevice {
QLowEnergyService *serviceFIT = nullptr;
QLowEnergyService *service = nullptr;
QLowEnergyService *serviceChanged = nullptr;
QLowEnergyService *serviceZwiftPlayBike = nullptr;
QLowEnergyAdvertisingData advertisingData;
QLowEnergyServiceData serviceDataHR;
QLowEnergyServiceData serviceDataBattery;
@@ -57,6 +58,7 @@ class virtualbike : public virtualdevice {
QLowEnergyServiceData serviceData;
QLowEnergyServiceData serviceDataChanged;
QLowEnergyServiceData serviceEchelon;
QLowEnergyServiceData serviceDataZwiftPlayBike;
QTimer bikeTimer;
bluetoothdevice *Bike;
CharacteristicWriteProcessor2AD9 *writeP2AD9 = 0;
@@ -73,6 +75,7 @@ class virtualbike : public virtualdevice {
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
DirconManager *dirconManager = 0;
uint8_t CurrentZwiftGear = 8;
int iFit_pelotonToBikeResistance(int pelotonResistance);
int iFit_resistanceToIfit(int ifitResistance);
qint64 iFit_timer = 0;
@@ -81,6 +84,8 @@ class virtualbike : public virtualdevice {
resistance_t iFit_LastResistanceRequested = 0;
bool iFit_Stop = false;
void handleZwiftGear(const QByteArray &array);
bool echelonInitDone = false;
void echelonWriteResistance();
void echelonWriteStatus();