Compare commits

...

63 Commits

Author SHA1 Message Date
Roberto Viola
6a04908812 Update project.pbxproj 2025-03-21 08:42:08 +01:00
Roberto Viola
1460c6190e Merge branch 'master' into victory-dircon 2025-03-21 08:34:34 +01:00
Roberto Viola
32fcb198f8 Update project.pbxproj 2025-03-20 15:14:08 +01:00
Roberto Viola
3f04bb2ff0 Merge branch 'master' into victory-dircon 2025-03-20 15:13:14 +01:00
Roberto Viola
fec16ee496 Merge branch 'master' into victory-dircon 2025-03-20 15:12:55 +01:00
Roberto Viola
4f0ac7e6ea Update project.pbxproj 2025-03-20 13:57:44 +01:00
Roberto Viola
ba79162a28 Merge branch 'master' into victory-dircon 2025-03-20 13:54:24 +01:00
Roberto Viola
93477f4008 Update project.pbxproj 2025-03-20 13:53:45 +01:00
Roberto Viola
6e0b958e56 Merge branch 'master' into victory-dircon 2025-03-20 09:39:19 +01:00
Roberto Viola
f3f219309a Update project.pbxproj 2025-03-19 11:55:38 +01:00
Roberto Viola
860e296ae3 Merge branch 'master' into victory-dircon 2025-03-19 11:48:56 +01:00
Roberto Viola
505cd193d3 Merge branch 'master' into victory-dircon 2025-03-18 16:11:23 +01:00
Roberto Viola
f5ed321def build 1043 2025-03-07 09:24:08 +01:00
Roberto Viola
490039c9a0 Merge branch 'master' into victory-dircon 2025-03-07 08:55:50 +01:00
Roberto Viola
88eaf9d73c Update project.pbxproj 2025-03-07 03:03:15 +01:00
Roberto Viola
05979de55a Merge branch 'master' into victory-dircon 2025-03-07 03:02:42 +01:00
Roberto Viola
fb3c34f91f Merge branch 'master' into victory-dircon 2025-03-07 02:54:44 +01:00
Roberto Viola
a5616561d3 Update project.pbxproj 2025-03-05 08:00:49 +01:00
Roberto Viola
835a8395bf Merge branch 'master' into victory-dircon 2025-03-05 07:58:08 +01:00
Roberto Viola
d61a55394b fixing memory leak 2025-03-04 10:07:57 +01:00
Roberto Viola
e2233d0a86 Update project.pbxproj 2025-03-04 07:58:34 +01:00
Roberto Viola
075a0dc368 Gears don't work for mid-work free ride segment (Issue #2897)
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2692370530
2025-03-03 11:36:30 +01:00
Roberto Viola
5f18c9f81c Gears don't work for mid-work free ride segment (Issue #2897)
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2692178928
2025-03-03 11:12:31 +01:00
Roberto Viola
2f2c084db3 Update settings.qml 2025-03-02 13:01:35 +01:00
Roberto Viola
68337ee54d Update project.pbxproj 2025-02-27 12:30:15 +01:00
Roberto Viola
e69ddb8a26 Merge branch 'master' into victory-dircon 2025-02-27 12:29:33 +01:00
Roberto Viola
69b227373a fixing build 2025-02-26 17:23:56 +01:00
Roberto Viola
fbb9524afd Merge branch 'master' into victory-dircon 2025-02-26 17:12:04 +01:00
Roberto Viola
937f98c77a fixing bluetooth with get gears on on android? not tested 2025-02-26 16:44:24 +01:00
Roberto Viola
0ec7619408 fixing bluetooth on ios with get gears from zwift enabled 2025-02-26 16:25:50 +01:00
Roberto Viola
344640fe69 Update project.pbxproj 2025-02-26 12:36:44 +01:00
Roberto Viola
26effcd6b2 Merge branch 'master' into victory-dircon 2025-02-26 12:16:14 +01:00
Roberto Viola
206e6e6fec Gears don't work for mid-work free ride segment (Issue #2897) 2025-02-24 15:50:29 +01:00
Roberto Viola
d2ba73b148 Merge branch 'master' into victory-dircon 2025-02-19 09:12:22 +01:00
Roberto Viola
df8ac18a0f Merge branch 'master' into victory-dircon 2025-02-19 09:08:25 +01:00
Roberto Viola
cfcf86adcc Update dirconmanager.cpp
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2666126808
2025-02-18 16:58:21 +01:00
Roberto Viola
957d2934db fixing gears on startup alinged with zwift 2025-02-18 15:22:35 +01:00
Roberto Viola
7c73975c34 Merge branch 'master' into victory-dircon 2025-02-18 14:49:55 +01:00
Roberto Viola
999ce8022a Update project.pbxproj 2025-02-15 20:58:57 +01:00
Roberto Viola
c3fa23159a Log on Thread 2025-02-15 19:51:09 +00:00
Roberto Viola
6b674be421 Merge remote-tracking branch 'origin/master' into victory-dircon 2025-02-15 19:46:39 +00:00
Roberto Viola
7af98b4992 handling unhandled case 2025-02-10 10:34:26 +01:00
Roberto Viola
8380827494 Merge branch 'master' into victory-dircon 2025-02-10 10:19:55 +01:00
Roberto Viola
a3a8825d48 simulating a fake cadence randomly 2025-02-01 08:15:41 +01:00
Roberto Viola
d8b1dce8de Merge branch 'victory-dircon' of https://github.com/cagnulein/qdomyos-zwift into victory-dircon 2025-01-31 08:38:54 +01:00
Roberto Viola
6e141bf83e Update fakebike.cpp 2025-01-31 08:38:32 +01:00
Roberto Viola
f32ad41917 Merge branch 'master' into victory-dircon 2025-01-31 08:10:10 +01:00
Roberto Viola
e75a5bcb0a Merge branch 'victory-dircon' of https://github.com/cagnulein/qdomyos-zwift into victory-dircon 2025-01-31 08:10:03 +01:00
Roberto Viola
0a017ca4e6 Merge branch 'master' into victory-dircon 2025-01-31 08:09:51 +01:00
Roberto Viola
3ceb256272 Update dirconmanager.cpp 2025-01-20 15:26:35 +01:00
Roberto Viola
6b8bb96aad Merge branch 'master' into victory-dircon 2025-01-20 15:02:55 +01:00
Roberto Viola
71cb562b05 Update characteristicwriteprocessor0003.h 2025-01-07 17:31:40 +01:00
Roberto Viola
de77f2aa4e Horizon 5.0 Bike Compatibility #3001 2025-01-07 11:19:17 +01:00
Roberto Viola
316c34213d Merge branch 'master' into victory-dircon 2025-01-07 09:47:40 +01:00
Roberto Viola
860489616c improving wattage also for all bluetooth, but it's not perfect yet 2025-01-01 18:56:11 +01:00
Roberto Viola
730c16f7b4 dircon works perfectly on ios! 2025-01-01 18:41:53 +01:00
Roberto Viola
2bdbeed5f4 wahoo rgt setting is not useful anymore 2025-01-01 14:51:39 +01:00
Roberto Viola
1946b46665 it works! 2025-01-01 14:45:36 +01:00
Roberto Viola
288cb3974b kind of works (no unhandled frames) 2024-12-31 15:00:51 +01:00
Roberto Viola
345b0d2f74 adding 0004 notifier 2024-12-31 14:20:34 +01:00
Roberto Viola
c8355184f2 i'm getting the 0003 but i need to notify the 0002
it doesn't enter into the sendCharacteristicNotification loop
2024-12-30 18:12:10 +01:00
Roberto Viola
4f7c7fa7c9 it's working for asking the UUID! 2024-12-30 16:39:45 +01:00
Roberto Viola
c75b6ae5f0 starting 2024-12-30 16:29:31 +01:00
22 changed files with 855 additions and 76 deletions

View File

@@ -523,6 +523,12 @@
87D5DC4228230496008CCDE7 /* moc_truetreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D5DC4128230496008CCDE7 /* moc_truetreadmill.cpp */; };
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D91F992800B9970026D43C /* proformwifibike.cpp */; };
87D91F9C2800B9B90026D43C /* moc_proformwifibike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87D91F9B2800B9B90026D43C /* moc_proformwifibike.cpp */; };
87DA62A42D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62A22D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp */; };
87DA62A52D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62A32D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp */; };
87DA62AA2D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62A92D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp */; };
87DA62AB2D2305F5008ADA0F /* characteristicnotifier0002.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */; };
87DA62AF2D2426F2008ADA0F /* characteristicnotifier0004.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62AD2D2426F2008ADA0F /* characteristicnotifier0004.cpp */; };
87DA62B02D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA62AE2D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp */; };
87DA76502848F98200A71B64 /* libQt5TextToSpeech.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */; };
87DA8465284933D200B550E9 /* fakeelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA8464284933D200B550E9 /* fakeelliptical.cpp */; };
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DA8466284933DE00B550E9 /* moc_fakeelliptical.cpp */; };
@@ -1546,6 +1552,12 @@
87D91F982800B9970026D43C /* proformwifibike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifibike.h; path = ../src/devices/proformwifibike/proformwifibike.h; sourceTree = "<group>"; };
87D91F992800B9970026D43C /* proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifibike.cpp; path = ../src/devices/proformwifibike/proformwifibike.cpp; sourceTree = "<group>"; };
87D91F9B2800B9B90026D43C /* moc_proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformwifibike.cpp; sourceTree = "<group>"; };
87DA62A22D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicnotifier0002.cpp; sourceTree = "<group>"; };
87DA62A32D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicwriteprocessor0003.cpp; sourceTree = "<group>"; };
87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier0002.cpp; path = ../src/characteristics/characteristicnotifier0002.cpp; sourceTree = SOURCE_ROOT; };
87DA62A92D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessor0003.cpp; path = ../src/characteristics/characteristicwriteprocessor0003.cpp; sourceTree = SOURCE_ROOT; };
87DA62AD2D2426F2008ADA0F /* characteristicnotifier0004.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier0004.cpp; path = ../src/characteristics/characteristicnotifier0004.cpp; sourceTree = SOURCE_ROOT; };
87DA62AE2D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicnotifier0004.cpp; sourceTree = "<group>"; };
87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5TextToSpeech.a; path = ../../Qt/5.15.2/ios/lib/libQt5TextToSpeech.a; sourceTree = "<group>"; };
87DA8463284933D200B550E9 /* fakeelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fakeelliptical.h; path = ../src/devices/fakeelliptical/fakeelliptical.h; sourceTree = "<group>"; };
87DA8464284933D200B550E9 /* fakeelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fakeelliptical.cpp; path = ../src/devices/fakeelliptical/fakeelliptical.cpp; sourceTree = "<group>"; };
@@ -2827,6 +2839,19 @@
name = AppleWatchToIpad;
sourceTree = "<group>";
};
8798FDCA2D6F338200CF8EE8 /* Recovered References */ = {
isa = PBXGroup;
children = (
87DA62A92D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp */,
87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */,
87DA62A22D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp */,
87DA62A32D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp */,
87DA62AD2D2426F2008ADA0F /* characteristicnotifier0004.cpp */,
87DA62AE2D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp */,
);
name = "Recovered References";
sourceTree = "<group>";
};
87DF60DE337FB58864343E39 /* Resources */ = {
isa = PBXGroup;
children = (
@@ -3245,6 +3270,7 @@
AF39DD055C3EF8226FBE929D /* Frameworks */,
858FCAB0EB1F29CF8B07677C /* Bundle Data */,
FE0A091FDBFB3E9C31B7A1BD /* Products */,
8798FDCA2D6F338200CF8EE8 /* Recovered References */,
);
name = qdomyoszwift;
sourceTree = "<group>";
@@ -3461,6 +3487,8 @@
873824BC27E64707004F1B46 /* moc_resolver.cpp in Compile Sources */,
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */,
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */,
87DA62AA2D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp in Compile Sources */,
87DA62AB2D2305F5008ADA0F /* characteristicnotifier0002.cpp in Compile Sources */,
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */,
20A50533946A39CBD2C89104 /* bluetoothdevice.cpp in Compile Sources */,
87C5F0D126285E7E0067A1B5 /* moc_stagesbike.cpp in Compile Sources */,
@@ -3804,6 +3832,8 @@
1FBBC7C86C436CAAAFD37E56 /* moc_domyostreadmill.cpp in Compile Sources */,
876BFCA027BE35D8001D7645 /* moc_proformelliptical.cpp in Compile Sources */,
873824BD27E64707004F1B46 /* moc_resolver_p.cpp in Compile Sources */,
87DA62A42D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp in Compile Sources */,
87DA62A52D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp in Compile Sources */,
876ED21A25C3E9010065F3DC /* moc_material.cpp in Compile Sources */,
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */,
87D10552290996EA00B3935B /* mepanelbike.cpp in Compile Sources */,
@@ -3845,6 +3875,8 @@
873824B227E64706004F1B46 /* moc_hostname.cpp in Compile Sources */,
873824EB27E647A8004F1B46 /* prober.cpp in Compile Sources */,
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */,
87DA62AF2D2426F2008ADA0F /* characteristicnotifier0004.cpp in Compile Sources */,
87DA62B02D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp in Compile Sources */,
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */,
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */,
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */,
@@ -4255,7 +4287,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4449,7 +4481,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4679,7 +4711,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4775,7 +4807,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4867,7 +4899,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4983,7 +5015,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1037;
CURRENT_PROJECT_VERSION = 1050;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

188
helpers/dircon-parser.py Normal file
View File

@@ -0,0 +1,188 @@
from dataclasses import dataclass
from typing import List, Optional, Tuple
import re
@dataclass
class HubRidingData:
power: Optional[int] = None
cadence: Optional[int] = None
speed_x100: Optional[int] = None
hr: Optional[int] = None
unknown1: Optional[int] = None
unknown2: Optional[int] = None
def __str__(self):
return (f"Power={self.power}W Cadence={self.cadence}rpm "
f"Speed={self.speed_x100/100 if self.speed_x100 else 0:.1f}km/h "
f"HR={self.hr}bpm Unknown1={self.unknown1} Unknown2={self.unknown2}")
def parse_protobuf_varint(data: bytes, offset: int = 0) -> Tuple[int, int]:
result = 0
shift = 0
while offset < len(data):
byte = data[offset]
result |= (byte & 0x7F) << shift
offset += 1
if not (byte & 0x80):
break
shift += 7
return result, offset
def parse_hub_riding_data(data: bytes) -> Optional[HubRidingData]:
try:
riding_data = HubRidingData()
offset = 0
while offset < len(data):
key, new_offset = parse_protobuf_varint(data, offset)
wire_type = key & 0x07
field_number = key >> 3
offset = new_offset
if wire_type == 0:
value, offset = parse_protobuf_varint(data, offset)
if field_number == 1:
riding_data.power = value
elif field_number == 2:
riding_data.cadence = value
elif field_number == 3:
riding_data.speed_x100 = value
elif field_number == 4:
riding_data.hr = value
elif field_number == 5:
riding_data.unknown1 = value
elif field_number == 6:
riding_data.unknown2 = value
return riding_data
except Exception as e:
print(f"Error parsing protobuf: {e}")
return None
@dataclass
class DirconPacket:
message_version: int = 1
identifier: int = 0xFF
sequence_number: int = 0
response_code: int = 0
length: int = 0
uuid: int = 0
uuids: List[int] = None
additional_data: bytes = b''
is_request: bool = False
riding_data: Optional[HubRidingData] = None
def __str__(self):
uuids_str = ','.join(f'{u:04x}' for u in (self.uuids or []))
base_str = (f"vers={self.message_version} Id={self.identifier} sn={self.sequence_number} "
f"resp={self.response_code} len={self.length} req?={self.is_request} "
f"uuid={self.uuid:04x} dat={self.additional_data.hex()} uuids=[{uuids_str}]")
if self.riding_data:
base_str += f"\nRiding Data: {self.riding_data}"
return base_str
def parse_dircon_packet(data: bytes, offset: int = 0) -> Tuple[Optional[DirconPacket], int]:
if len(data) - offset < 6:
return None, 0
packet = DirconPacket()
packet.message_version = data[offset]
packet.identifier = data[offset + 1]
packet.sequence_number = data[offset + 2]
packet.response_code = data[offset + 3]
packet.length = (data[offset + 4] << 8) | data[offset + 5]
total_length = 6 + packet.length
if len(data) - offset < total_length:
return None, 0
curr_offset = offset + 6
if packet.identifier == 0x01: # DPKT_MSGID_DISCOVER_SERVICES
if packet.length == 0:
packet.is_request = True
elif packet.length % 16 == 0:
packet.uuids = []
while curr_offset + 16 <= offset + total_length:
uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
packet.uuids.append(uuid)
curr_offset += 16
elif packet.identifier == 0x02: # DPKT_MSGID_DISCOVER_CHARACTERISTICS
if packet.length >= 16:
packet.uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
if packet.length == 16:
packet.is_request = True
elif (packet.length - 16) % 17 == 0:
curr_offset += 16
packet.uuids = []
packet.additional_data = b''
while curr_offset + 17 <= offset + total_length:
uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
packet.uuids.append(uuid)
packet.additional_data += bytes([data[curr_offset + 16]])
curr_offset += 17
elif packet.identifier in [0x03, 0x04, 0x05, 0x06]: # READ/WRITE/NOTIFY characteristics
if packet.length >= 16:
packet.uuid = (data[curr_offset + 2] << 8) | data[curr_offset + 3]
if packet.length > 16:
packet.additional_data = data[curr_offset + 16:offset + total_length]
if packet.uuid == 0x0002:
packet.riding_data = parse_hub_riding_data(packet.additional_data)
if packet.identifier != 0x06:
packet.is_request = True
return packet, total_length
def extract_bytes_from_c_array(content: str) -> List[Tuple[str, bytes]]:
packets = []
pattern = r'static const unsigned char (\w+)\[\d+\] = \{([^}]+)\};'
matches = re.finditer(pattern, content)
for match in matches:
name = match.group(1)
hex_str = match.group(2)
hex_values = []
for line in hex_str.split('\n'):
line = line.split('//')[0]
values = re.findall(r'0x[0-9a-fA-F]{2}', line)
hex_values.extend(values)
byte_data = bytes([int(x, 16) for x in hex_values])
packets.append((name, byte_data))
return packets
def get_tcp_payload(data: bytes) -> bytes:
ip_header_start = 14 # Skip Ethernet header
ip_header_len = (data[ip_header_start] & 0x0F) * 4
tcp_header_start = ip_header_start + ip_header_len
tcp_header_len = ((data[tcp_header_start + 12] >> 4) & 0x0F) * 4
payload_start = tcp_header_start + tcp_header_len
return data[payload_start:]
def parse_file(filename: str):
with open(filename, 'r') as f:
content = f.read()
packets = extract_bytes_from_c_array(content)
for name, data in packets:
print(f"\nPacket {name}:")
payload = get_tcp_payload(data)
print(f"Dircon payload: {payload.hex()}")
offset = 0
while offset < len(payload):
packet, consumed = parse_dircon_packet(payload, offset)
if packet is None or consumed == 0:
break
print(f"Frame: {packet}")
offset += consumed
if __name__ == "__main__":
import sys
if len(sys.argv) != 2:
print("Usage: python script.py <input_file>")
sys.exit(1)
parse_file(sys.argv[1])

View File

@@ -0,0 +1,23 @@
#include "characteristicnotifier0002.h"
#include "bike.h"
#include <QDebug>
#include <QList>
CharacteristicNotifier0002::CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent)
: CharacteristicNotifier(0x0002, parent) {
Bike = bike;
answerList = QList<QByteArray>(); // Initialize empty list
}
void CharacteristicNotifier0002::addAnswer(const QByteArray &newAnswer) {
answerList.append(newAnswer);
}
int CharacteristicNotifier0002::notify(QByteArray &value) {
if(!answerList.isEmpty()) {
value.append(answerList.first()); // Get first item
answerList.removeFirst(); // Remove it from list
return CN_OK;
}
return CN_INVALID;
}

View File

@@ -0,0 +1,19 @@
#ifndef CHARACTERISTICNOTIFIER0002_H
#define CHARACTERISTICNOTIFIER0002_H
#include "bluetoothdevice.h"
#include "characteristicnotifier.h"
#include <QList>
class CharacteristicNotifier0002 : public CharacteristicNotifier {
Q_OBJECT
bluetoothdevice* Bike = nullptr;
QList<QByteArray> answerList;
public:
explicit CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent = nullptr);
int notify(QByteArray &value) override;
void addAnswer(const QByteArray &newAnswer);
};
#endif // CHARACTERISTICNOTIFIER0002_H

View File

@@ -0,0 +1,23 @@
#include "characteristicnotifier0004.h"
#include "bike.h"
#include <QDebug>
#include <QList>
CharacteristicNotifier0004::CharacteristicNotifier0004(bluetoothdevice *bike, QObject *parent)
: CharacteristicNotifier(0x0004, parent) {
Bike = bike;
answerList = QList<QByteArray>();
}
void CharacteristicNotifier0004::addAnswer(const QByteArray &newAnswer) {
answerList.append(newAnswer);
}
int CharacteristicNotifier0004::notify(QByteArray &value) {
if(!answerList.isEmpty()) {
value.append(answerList.first());
answerList.removeFirst();
return CN_OK;
}
return CN_INVALID;
}

View File

@@ -0,0 +1,19 @@
#ifndef CHARACTERISTICNOTIFIER0004_H
#define CHARACTERISTICNOTIFIER0004_H
#include "bluetoothdevice.h"
#include "characteristicnotifier.h"
#include <QList>
class CharacteristicNotifier0004 : public CharacteristicNotifier {
Q_OBJECT
bluetoothdevice* Bike = nullptr;
QList<QByteArray> answerList;
public:
explicit CharacteristicNotifier0004(bluetoothdevice *bike, QObject *parent = nullptr);
int notify(QByteArray &value) override;
void addAnswer(const QByteArray &newAnswer);
};
#endif // CHARACTERISTICNOTIFIER0004_H

View File

@@ -0,0 +1,308 @@
#include "bike.h"
#include "characteristicwriteprocessor0003.h"
#include <QDebug>
CharacteristicWriteProcessor0003::CharacteristicWriteProcessor0003(double bikeResistanceGain,
int8_t bikeResistanceOffset,
bluetoothdevice *bike,
CharacteristicNotifier0002 *notifier0002,
CharacteristicNotifier0004 *notifier0004,
QObject *parent)
: CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier0002(notifier0002), notifier0004(notifier0004) {
}
CharacteristicWriteProcessor0003::VarintResult CharacteristicWriteProcessor0003::decodeVarint(const QByteArray& bytes, int startIndex) {
qint64 result = 0;
int shift = 0;
int bytesRead = 0;
for (int i = startIndex; i < bytes.size(); i++) {
quint8 byte = static_cast<quint8>(bytes.at(i));
result |= static_cast<qint64>(byte & 0x7F) << shift;
bytesRead++;
if ((byte & 0x80) == 0) {
break;
}
shift += 7;
}
return {result, bytesRead};
}
qint32 CharacteristicWriteProcessor0003::decodeSInt(const QByteArray& bytes) {
if (static_cast<quint8>(bytes.at(0)) != 0x22) {
qFatal("Invalid field header");
}
int length = static_cast<quint8>(bytes.at(1));
if (static_cast<quint8>(bytes.at(2)) != 0x10) {
qFatal("Invalid inner header");
}
VarintResult varint = decodeVarint(bytes, 3);
qint32 decoded = (varint.value >> 1) ^ -(varint.value & 1);
return decoded;
}
void CharacteristicWriteProcessor0003::handleZwiftGear(const QByteArray &array) {
uint8_t g = 0;
if (array.size() >= 2) {
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)0xDC && (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 (array.size() >= 3) {
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;
else { return; }
}
else { return; }
}
QSettings settings;
if(settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool()) {
int actGear = ((bike*)Bike)->gears();
if (g < actGear) {
for (int i = 0; i < actGear - g; i++) {
((bike*)Bike)->gearDown();
}
} else if (g > actGear) {
for (int i = 0; i < g - actGear; i++) {
((bike*)Bike)->gearUp();
}
}
} else {
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;
}
}
QByteArray CharacteristicWriteProcessor0003::encodeHubRidingData(
uint32_t power,
uint32_t cadence,
uint32_t speedX100,
uint32_t hr,
uint32_t unknown1,
uint32_t unknown2
) {
QByteArray buffer;
buffer.append(char(0x03));
auto encodeVarInt32 = [](QByteArray& buf, uint32_t value) {
do {
uint8_t byte = value & 0x7F;
value >>= 7;
if (value) byte |= 0x80;
buf.append(char(byte));
} while (value);
};
encodeVarInt32(buffer, (1 << 3) | 0);
encodeVarInt32(buffer, power);
encodeVarInt32(buffer, (2 << 3) | 0);
encodeVarInt32(buffer, cadence);
encodeVarInt32(buffer, (3 << 3) | 0);
encodeVarInt32(buffer, speedX100);
encodeVarInt32(buffer, (4 << 3) | 0);
encodeVarInt32(buffer, hr);
encodeVarInt32(buffer, (5 << 3) | 0);
encodeVarInt32(buffer, unknown1);
encodeVarInt32(buffer, (6 << 3) | 0);
encodeVarInt32(buffer, unknown2);
return buffer;
}
static uint32_t lastUnknown1 = 836; // Starting value from logs
static uint32_t baseValue = 19000; // Base value from original code
uint32_t CharacteristicWriteProcessor0003::calculateUnknown1(uint16_t power) {
// Increment by a value between 400-800 based on current power
uint32_t increment = 400 + (power * 2);
if (increment > 800) increment = 800;
// Adjust based on power changes
if (power > 0) {
lastUnknown1 += increment;
} else {
// For zero power, larger increments
lastUnknown1 += 600;
}
// Keep within observed range (800-24000)
if (lastUnknown1 > 24000) lastUnknown1 = baseValue;
return lastUnknown1;
}
int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArray &data, QByteArray &reply) {
static const QByteArray expectedHexArray = QByteArray::fromHex("52696465 4F6E02");
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 expectedHexArray4b = QByteArray::fromHex("042A0A10 A0830120");
static const QByteArray expectedHexArray5 = QByteArray::fromHex("0422");
static const QByteArray expectedHexArray6 = QByteArray::fromHex("042A0410");
static const QByteArray expectedHexArray7 = QByteArray::fromHex("042A0310");
static const QByteArray expectedHexArray8 = QByteArray::fromHex("0418");
static const QByteArray expectedHexArray9 = QByteArray::fromHex("042a0810");
static const QByteArray expectedHexArray10 = QByteArray::fromHex("000800");
QByteArray receivedData = data;
if (receivedData.startsWith(expectedHexArray)) {
qDebug() << "Zwift Play Processor: Initial connection request";
reply = QByteArray::fromHex("2a08031211220f4154582030342c2053545820303400");
notifier0002->addAnswer(reply);
reply = QByteArray::fromHex("2a0803120d220b524944455f4f4e28322900");
notifier0002->addAnswer(reply);
reply = QByteArray::fromHex("526964654f6e0200");
notifier0004->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray2)) {
qDebug() << "Zwift Play Processor: Device info request";
reply = QByteArray::fromHex("3c080012320a3008800412040500050"
"11a0b4b49434b5220434f524500320f"
"3430323431383030393834000000003a01314204080110140");
notifier0004->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray3)) {
qDebug() << "Zwift Play Processor: Status request";
reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
notifier0004->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray4) || receivedData.startsWith(expectedHexArray4b)) {
qDebug() << "Zwift Play Ask 4";
reply = QByteArray::fromHex("0308001000185920002800309bed01");
notifier0002->addAnswer(reply);
reply = QByteArray::fromHex("2a08031227222567"
"61705f706172616d735f6368616e6765"
"2832293a2037322c2037322c20302c20"
"36303000");
notifier0002->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray5)) {
qDebug() << "Zwift Play Processor: Slope change request";
double slopefloat = decodeSInt(receivedData.mid(1));
QByteArray slope(2, 0);
slope[0] = quint8(qint16(slopefloat) & 0xFF);
slope[1] = quint8((qint16(slopefloat) >> 8) & 0x00FF);
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(),
QByteArray::fromHex("116901") + slope + QByteArray::fromHex("3228"));
changeSlope(slopefloat, 0 /* TODO */, 0 /* TODO */);
reply = encodeHubRidingData(
Bike->wattsMetric().value(),
Bike->currentCadence().value(),
0,
Bike->wattsMetric().value(),
calculateUnknown1(Bike->wattsMetric().value()),
0
);
notifier0002->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray6)) {
qDebug() << "Zwift Play Ask 6";
reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
reply[9] = receivedData[4];
reply[10] = receivedData[5];
reply[11] = receivedData[6];
handleZwiftGear(receivedData.mid(4));
notifier0004->addAnswer(reply);
reply = QByteArray::fromHex("03080010001827e7 20002896143093ed01");
notifier0002->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray7)) {
qDebug() << "Zwift Play Ask 7";
reply = QByteArray::fromHex("03080010001827e7 2000 28 00 3093ed01");
notifier0002->addAnswer(reply);
reply = QByteArray::fromHex("3c088804120503408c60");
reply[8] = receivedData[4];
reply[9] = receivedData[5];
handleZwiftGear(receivedData.mid(4));
notifier0004->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray8)) {
qDebug() << "Zwift Play Processor: Power request";
VarintResult Power = decodeVarint(receivedData, 2);
QByteArray power(2, 0);
power[0] = quint8(qint16(Power.value) & 0xFF);
power[1] = quint8((qint16(Power.value) >> 8) & 0x00FF);
emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(),
QByteArray::fromHex("05") + power);
reply = encodeHubRidingData(
Bike->wattsMetric().value(),
Bike->currentCadence().value(),
0,
Bike->wattsMetric().value(),
calculateUnknown1(Bike->wattsMetric().value()),
0
);
notifier0002->addAnswer(reply);
changePower(Power.value);
}
else if (receivedData.startsWith(expectedHexArray9)) {
qDebug() << "Zwift Play Ask 9";
reply = QByteArray::fromHex("050a08400058b60560fc26");
notifier0004->addAnswer(reply);
}
else if (receivedData.startsWith(expectedHexArray10)) {
qDebug() << "Zwift Play Ask 10";
reply = QByteArray::fromHex("3c0800122408800412040004000c1a00320f42412d4534333732443932374244453a00420408011053");
notifier0004->addAnswer(reply);
}
else {
qDebug() << "Zwift Play Processor: Unhandled request:" << receivedData.toHex();
return -1;
}
return 0;
}

View File

@@ -0,0 +1,43 @@
#ifndef CHARACTERISTICWRITEPROCESSOR0003_H
#define CHARACTERISTICWRITEPROCESSOR0003_H
#include "characteristicnotifier0002.h"
#include "characteristicnotifier0004.h"
#include "characteristicwriteprocessor.h"
class CharacteristicWriteProcessor0003 : public CharacteristicWriteProcessor {
Q_OBJECT
CharacteristicNotifier0002 *notifier0002 = nullptr;
CharacteristicNotifier0004 *notifier0004 = nullptr;
public:
explicit CharacteristicWriteProcessor0003(double bikeResistanceGain, int8_t bikeResistanceOffset,
bluetoothdevice *bike, CharacteristicNotifier0002 *notifier0002,
CharacteristicNotifier0004 *notifier0004,
QObject *parent = nullptr);
int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) override;
static QByteArray encodeHubRidingData(uint32_t power,
uint32_t cadence,
uint32_t speedX100,
uint32_t hr,
uint32_t unknown1,
uint32_t unknown2);
static uint32_t calculateUnknown1(uint16_t power);
private:
struct VarintResult {
qint64 value;
int bytesRead;
};
VarintResult decodeVarint(const QByteArray& bytes, int startIndex);
qint32 decodeSInt(const QByteArray& bytes);
void handleZwiftGear(const QByteArray &array);
int currentZwiftGear = 8;
signals:
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // CHARACTERISTICWRITEPROCESSOR0003_H

View File

@@ -14,7 +14,8 @@ 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) \
OP(ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3)
#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) \
@@ -22,6 +23,7 @@ using namespace std::chrono_literals;
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)
#define DP_PROCESS_WRITE_0003() writeP0003
#define DP_PROCESS_WRITE_2AD9() writeP2AD9
#define DP_PROCESS_WRITE_2AD9T() writeP2AD9
#define DP_PROCESS_WRITE_E005() writePE005
@@ -71,7 +73,17 @@ 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) \
OP(ZWIFT_PLAY_EMULATOR, 0x0003, \
DPKT_CHAR_PROP_FLAG_WRITE, \
DM_BT("\x00"), DP_PROCESS_WRITE_0003, P1, P2, P3) \
OP(ZWIFT_PLAY_EMULATOR, 0x0002, \
DPKT_CHAR_PROP_FLAG_NOTIFY, \
DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(ZWIFT_PLAY_EMULATOR, 0x0004, \
/* CHECK THE INDICATE*/ \
DPKT_CHAR_PROP_FLAG_INDICATE, \
DM_BT("\x02\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3)
#define DM_MACHINE_ENUM_OP(DESC, NAME, TYPE, P1, P2, P3) DM_MACHINE_##DESC,
@@ -160,6 +172,7 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_BUILD_OP, Bike, 0, 0)
writeP2AD9 = new CharacteristicWriteProcessor2AD9(bikeResistanceGain, bikeResistanceOffset, Bike, notif2AD9, this);
writePE005 = new CharacteristicWriteProcessorE005(bikeResistanceGain, bikeResistanceOffset, Bike, this);
writeP0003 = new CharacteristicWriteProcessor0003(bikeResistanceGain, bikeResistanceOffset, Bike, notif0002, notif0004, this);
DM_CHAR_OP(DM_CHAR_INIT_OP, services, service, 0)
connect(writeP2AD9, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writeP2AD9, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
@@ -167,11 +180,14 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
connect(writePE005, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writePE005, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
connect(writeP0003, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writeP0003, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
QObject::connect(&bikeTimer, &QTimer::timeout, this, &DirconManager::bikeProvider);
QString mac = getMacAddress();
DM_MACHINE_OP(DM_MACHINE_INIT_OP, services, proc_services, type)
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool())
bikeTimer.start(100ms);
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool() || settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool())
bikeTimer.start(50ms);
else
bikeTimer.start(1s);
}

View File

@@ -12,6 +12,7 @@
#include "characteristics/characteristicnotifier2ad9.h"
#include "characteristics/characteristicwriteprocessor2ad9.h"
#include "characteristics/characteristicwriteprocessore005.h"
#include "characteristics/characteristicwriteprocessor0003.h"
#include "devices/dircon/dirconpacket.h"
#include "devices/dircon/dirconprocessor.h"
#include <QObject>
@@ -20,7 +21,8 @@
OP(2AD2, P1, P2, P3) \
OP(2A63, P1, P2, P3) \
OP(2A37, P1, P2, P3) OP(2A5B, P1, P2, P3) OP(2A53, P1, P2, P3) OP(2ACD, P1, P2, P3) OP(2ACC, P1, P2, P3) \
OP(2AD9, P1, P2, P3)
OP(2AD9, P1, P2, P3) \
OP(0002, P1, P2, P3) OP(0004, P1, P2, P3)
#define DM_CHAR_NOTIF_DEFINE_OP(UUID, P1, P2, P3) CharacteristicNotifier##UUID *notif##UUID = 0;
@@ -28,6 +30,7 @@ class DirconManager : public QObject {
Q_OBJECT
QTimer bikeTimer;
CharacteristicWriteProcessor2AD9 *writeP2AD9 = 0;
CharacteristicWriteProcessor0003 *writeP0003 = 0;
CharacteristicWriteProcessorE005 *writePE005 = 0;
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_DEFINE_OP, 0, 0, 0)
QList<DirconProcessor *> processors;

View File

@@ -171,22 +171,40 @@ QByteArray DirconPacket::encode(int last_seq_number) {
this->Length = this->uuids.size() * 16;
byteout.append((char)(this->Length >> 8)).append((char)(this->Length));
foreach (u, this->uuids) {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
if(u >= 1 && u <= 4) {
this->uuid_bytes_zwift_play[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes_zwift_play[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes_zwift_play, 16);
} else {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
}
}
}
} 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));
u = this->uuid;
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
foreach (u, this->uuids) {
if(u >= 1 && u <= 4) {
this->uuid_bytes_zwift_play[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes_zwift_play[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes_zwift_play, 16);
} else {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
}
foreach (u, this->uuids) {
if(u >= 1 && u <= 4) {
this->uuid_bytes_zwift_play[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes_zwift_play[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes_zwift_play, 16);
} else {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
}
byteout.append(this->additional_data.at(i++));
}
} else if (((this->Identifier == DPKT_MSGID_READ_CHARACTERISTIC ||
@@ -196,9 +214,15 @@ QByteArray DirconPacket::encode(int last_seq_number) {
this->Length = 16;
byteout.append((char)(this->Length >> 8)).append((char)(this->Length));
u = this->uuid;
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
if(u >= 1 && u <= 4) {
this->uuid_bytes_zwift_play[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes_zwift_play[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes_zwift_play, 16);
} else {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
}
} else if (this->Identifier == DPKT_MSGID_WRITE_CHARACTERISTIC ||
this->Identifier == DPKT_MSGID_UNSOLICITED_CHARACTERISTIC_NOTIFICATION ||
(this->Identifier == DPKT_MSGID_READ_CHARACTERISTIC && !this->isRequest) ||
@@ -206,9 +230,15 @@ QByteArray DirconPacket::encode(int last_seq_number) {
this->Length = 16 + this->additional_data.size();
byteout.append((char)(this->Length >> 8)).append((char)(this->Length));
u = this->uuid;
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
if(u >= 1 && u <= 4) {
this->uuid_bytes_zwift_play[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes_zwift_play[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes_zwift_play, 16);
} else {
this->uuid_bytes[DPKT_POS_SH8] = (quint8)(u >> 8);
this->uuid_bytes[DPKT_POS_SH0] = (quint8)(u);
byteout.append((char *)this->uuid_bytes, 16);
}
byteout.append(this->additional_data);
}
return byteout;

View File

@@ -4,10 +4,20 @@
#include <QList>
#include <QObject>
#define ZWIFT_PLAY_ENUM_VALUE 0x0001
#define ZWIFT_PLAY_UUID_STRING "00000001-19CA-4651-86E5-FA29DCDD09D1"
#define ZWIFT_PLAY_CHAR1_ENUM_VALUE 0x0002
#define ZWIFT_PLAY_CHAR2_ENUM_VALUE 0x0003
#define ZWIFT_PLAY_CHAR3_ENUM_VALUE 0x0004
#define ZWIFT_PLAY_CHAR1_UUID_STRING "00000002-19CA-4651-86E5-FA29DCDD09D1"
#define ZWIFT_PLAY_CHAR2_UUID_STRING "00000003-19CA-4651-86E5-FA29DCDD09D1"
#define ZWIFT_PLAY_CHAR3_UUID_STRING "00000004-19CA-4651-86E5-FA29DCDD09D1"
#define DPKT_MESSAGE_HEADER_LENGTH 6
#define DPKT_CHAR_PROP_FLAG_READ 0x01
#define DPKT_CHAR_PROP_FLAG_WRITE 0x02
#define DPKT_CHAR_PROP_FLAG_NOTIFY 0x04
#define DPKT_CHAR_PROP_FLAG_INDICATE 0x08
#define DPKT_MSGID_ERROR 0xFF
#define DPKT_MSGID_DISCOVER_SERVICES 0x01
#define DPKT_MSGID_DISCOVER_CHARACTERISTICS 0x02
@@ -51,6 +61,8 @@ class DirconPacket {
private:
quint8 uuid_bytes[16] = {0x00, 0x00, 0x18, 0x26, 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB};
quint8 uuid_bytes_zwift_play[16] = {0x00, 0x00, 0x00, 0x04, 0x19, 0xCA, 0x46, 0x51,
0x86, 0xE5, 0xFA, 0x29, 0xDC, 0xDD, 0x09, 0xD1};
bool checkIsRequest(int last_seq_number);
};

View File

@@ -14,6 +14,16 @@ DirconProcessor::DirconProcessor(const QList<DirconProcessorService *> &my_servi
DirconProcessor::~DirconProcessor() {}
QString DirconProcessor::convertUUIDFromUINT16ToString (quint16 uuid) {
if(uuid == ZWIFT_PLAY_CHAR1_ENUM_VALUE)
return ZWIFT_PLAY_CHAR1_UUID_STRING;
if(uuid == ZWIFT_PLAY_CHAR2_ENUM_VALUE)
return ZWIFT_PLAY_CHAR2_UUID_STRING;
if(uuid == ZWIFT_PLAY_CHAR3_ENUM_VALUE)
return ZWIFT_PLAY_CHAR3_UUID_STRING;
return "";
}
bool DirconProcessor::initServer() {
qDebug() << "Initializing dircon tcp server for" << serverName;
if (!server) {
@@ -55,10 +65,16 @@ void DirconProcessor::initAdvertising() {
mdnsService.addAttribute(QByteArrayLiteral("serial-number"), serialN.toUtf8());
QString ble_uuids;
int i = 0;
foreach (DirconProcessorService *service, services)
ble_uuids += QString(QStringLiteral(DP_BASE_UUID))
.replace("u", QString(QStringLiteral("%1")).arg(service->uuid, 4, 16, QLatin1Char('0'))) +
((i++ < services.size() - 1) ? QStringLiteral(",") : QStringLiteral(""));
foreach (DirconProcessorService *service, services) {
if(service->uuid == ZWIFT_PLAY_ENUM_VALUE) {
ble_uuids += ZWIFT_PLAY_UUID_STRING +
((i++ < services.size() - 1) ? QStringLiteral(",") : QStringLiteral(""));
} else {
ble_uuids += QString(QStringLiteral(DP_BASE_UUID))
.replace("u", QString(QStringLiteral("%1")).arg(service->uuid, 4, 16, QLatin1Char('0'))) +
((i++ < services.size() - 1) ? QStringLiteral(",") : QStringLiteral(""));
}
}
mdnsService.addAttribute(QByteArrayLiteral("ble-service-uuids"), ble_uuids.toUtf8());
mdnsService.setPort(serverPort);
mdnsProvider->update(mdnsService);
@@ -209,7 +225,7 @@ bool DirconProcessor::sendCharacteristicNotification(quint16 uuid, const QByteAr
pkt.uuid = uuid;
for (QHash<QTcpSocket *, DirconProcessorClient *>::iterator i = clientsMap.begin(); i != clientsMap.end(); ++i) {
client = i.value();
if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool()) {
/*if (client->char_notify.indexOf(uuid) >= 0 || !settings.value(QZSettings::wahoo_rgt_dircon, QZSettings::default_wahoo_rgt_dircon).toBool())*/ {
socket = i.key();
rvs = socket->write(pkt.encode(0)) < 0;
if (rvs)

View File

@@ -84,6 +84,7 @@ class DirconProcessor : public QObject {
bool initServer();
void initAdvertising();
DirconPacket processPacket(DirconProcessorClient *client, const DirconPacket &pkt);
QString convertUUIDFromUINT16ToString (quint16 uuid);
public:
~DirconProcessor();

View File

@@ -48,10 +48,13 @@ void fakebike::update() {
if (requestPower != -1) {
// bepo70: don't know if this conversion is really needed, i would do it anyway.
m_watt = (double)requestPower;
Cadence = requestPower;
m_watt = (double)requestPower * (1.0 + (((double)rand() / RAND_MAX) * 0.4 - 0.2));
if(requestPower)
Cadence = 50 + (static_cast<double>(rand()) / RAND_MAX) * 50;
else
Cadence = 0;
emit debug(QStringLiteral("writing power ") + QString::number(requestPower));
requestPower = -1;
//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.

View File

@@ -271,6 +271,9 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
}
void ftmsbike::update() {
QSettings settings;
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
@@ -280,6 +283,11 @@ void ftmsbike::update() {
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())
init();
initRequest = false;
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
@@ -303,8 +311,7 @@ void ftmsbike::update() {
forceResistance(currentResistance().value());
}
auto virtualBike = this->VirtualBike();
QSettings settings;
auto virtualBike = this->VirtualBike();
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if (requestResistance != -1 || lastGearValue != gears()) {
@@ -387,7 +394,8 @@ void ftmsbike::update() {
lastGearValue = gears();
if (requestPower != -1) {
// if a classic request of power from zwift or any other platform is coming, will be transfereed on the ftmsCharacteristicChanged applying the gear mod too
if (requestPower != -1 && (!virtualBike || !virtualBike->ftmsDeviceConnected() || (zwiftPlayService != nullptr && gears_zwift_ratio))) {
qDebug() << QStringLiteral("writing power") << requestPower;
init();
forcePower(requestPower);

View File

@@ -36,14 +36,14 @@ import Foundation
return fullData
}
public static func ridingData(power: UInt32, cadence: UInt32, speed: Double, HR: UInt32) throws -> Data {
public static func ridingData(power: UInt32, cadence: UInt32, speed: Double, HR: UInt32, unkown1: UInt32, unkown2: UInt32) throws -> Data {
var physical = BLEReceiver_Zwift_HubRidingData()
physical.cadence = cadence
physical.power = power
physical.speedX100 = UInt32(speed * 100.0)
physical.hr = HR
physical.unknown1 = 2864
physical.unknown2 = 25714
physical.unknown1 = unkown1
physical.unknown2 = unkown2
let data = try physical.serializedData()
var fullData = Data([0x03])

View File

@@ -379,7 +379,27 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
func calculateUnknown1(power: UInt16) -> UInt32 {
var lastUnknown1: UInt32 = 836
let baseValue: UInt32 = 19000
var increment = 400 + (UInt32(power) * 2)
increment = min(increment, 800)
if power > 0 {
lastUnknown1 += increment
} else {
lastUnknown1 += 600
}
if lastUnknown1 > 24000 {
lastUnknown1 = baseValue
}
return lastUnknown1
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
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 + " " + ((requests.first!.characteristic == self.FitnessMachineControlPointCharacteristic) ? "FTMS" : "NOFTMS") + " " + String(describing: self.FitnessMachineControlPointCharacteristic) + " " + self.FitnessMachineControlPointCharacteristic.uuid.uuidString + " " + requests.first!.characteristic.uuid.uuidString
@@ -493,7 +513,7 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
// 04 22 02 10 1a TODO
var r = receivedBytes
r.remove(at: 0)
var slopefloat = decodeSInt(r)
let slopefloat = decodeSInt(r)
print("slopefloat \(slopefloat)")
var slope: [UInt8] = [ 0x00, 0x00 ]
self.CurrentSlope = Double(slopefloat)
@@ -501,10 +521,13 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
slope[1] = UInt8((Int16(self.CurrentSlope) >> 8) & 0x00FF)
LastFTMSMessageReceived = Data([0x11, 0x69, 0x01, slope[0], slope[1], 0x32, 0x28])
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))
do {
let response = try ZwiftHubBike.ridingData(power: UInt32(self.CurrentWatt), cadence: UInt32(self.CurrentCadence / 2), speed: 0, HR: UInt32(self.heartRate), unkown1: self.calculateUnknown1(power: self.CurrentWatt), unkown2: 0)
updateQueue.append((ZwiftPlayIndicateCharacteristic, response))
} catch {
}
}
let receivedBytes6 = [UInt8](receivedData.prefix(expectedHexArray6.count))
@@ -561,14 +584,13 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
self.PowerRequested = (Double)(power);
LastFTMSMessageReceived = Data([0x05, UInt8(UInt16(power) & 0xff), UInt8(((UInt16(power) & 0xff00) >> 8) & 0x00ff)])
var response: [UInt8] = [ 0x03, 0x08, 0x82, 0x01, 0x10, 0x22, 0x18, 0x10, 0x20, 0x00, 0x28, 0x98, 0x52, 0x30, 0x86, 0xed, 0x01 ]
response[2] = receivedData[2]
if(receivedData.count == 4) {
response[3] = receivedData[3]
}
let responseData = Data(bytes: &response, count: 17)
updateQueue.append((ZwiftPlayReadCharacteristic, responseData))
do {
let response = try ZwiftHubBike.ridingData(power: UInt32(power), cadence: UInt32(self.CurrentCadence / 2), speed: Double(self.NormalizeSpeed) / 100.0, HR: UInt32(self.heartRate), unkown1: self.calculateUnknown1(power: self.CurrentWatt), unkown2: 0)
updateQueue.append((ZwiftPlayIndicateCharacteristic, response))
} catch {
}
}
}
@@ -719,8 +741,12 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
func startSendingDataToSubscribers() {
if self.notificationTimer == nil {
self.notificationTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.updateSubscribers), userInfo: nil, repeats: true)
var t : Double = 0.2
if(zwift_play_emulator) {
t = 0.01
}
self.notificationTimer = Timer.scheduledTimer(timeInterval: t, target: self, selector: #selector(self.updateSubscribers), userInfo: nil, repeats: true)
}
}
func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) {
@@ -855,22 +881,17 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
}
} else if(zwift_play_emulator) {
if(!sendUpdates()) {
/*
do {
let ZwiftPlayData = try ZwiftHubBike.ridingData(power: UInt32(self.CurrentWatt), cadence: UInt32(self.CurrentCadence), speed: Double(self.NormalizeSpeed), HR: UInt32(self.heartRate))
let ok = self.peripheralManager.updateValue(ZwiftPlayData, for: self.ZwiftPlayReadCharacteristic, onSubscribedCentrals: nil)
let response = try ZwiftHubBike.ridingData(power: UInt32(self.CurrentWatt), cadence: UInt32(self.CurrentCadence / 2), speed: 0, HR: UInt32(self.heartRate), unkown1: self.calculateUnknown1(power: self.CurrentWatt), unkown2: 0)
let ok = self.peripheralManager.updateValue(response, for: self.ZwiftPlayReadCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = self.serviceToggle + 1
}
} catch {
self.serviceToggle = self.serviceToggle + 1
}*/
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
}

View File

@@ -667,7 +667,7 @@ void peloton::login_onfinish(QNetworkReply *reply) {
int status = json[QStringLiteral("status")].toInt();
if (log_request) {
qDebug() << QStringLiteral("login_onfinish") << document;
qDebug() << QStringLiteral("login_onfinish") << document << payload;
} else {
qDebug() << QStringLiteral("login_onfinish");
}

View File

@@ -78,6 +78,9 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX
# include(../qtzeroconf/qtzeroconf.pri)
SOURCES += \
$$PWD/characteristics/characteristicnotifier0002.cpp \
$$PWD/characteristics/characteristicnotifier0004.cpp \
$$PWD/characteristics/characteristicwriteprocessor0003.cpp \
$$PWD/devices/antbike/antbike.cpp \
$$PWD/devices/crossrope/crossrope.cpp \
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.cpp \
@@ -335,6 +338,9 @@ INCLUDEPATH += fit-sdk/ devices/
HEADERS += \
$$PWD/EventHandler.h \
$$PWD/characteristics/characteristicnotifier0002.h \
$$PWD/characteristics/characteristicnotifier0004.h \
$$PWD/characteristics/characteristicwriteprocessor0003.h \
$$PWD/OAuth2.h \
$$PWD/devices/antbike/antbike.h \
$$PWD/devices/crossrope/crossrope.h \

View File

@@ -5135,7 +5135,7 @@ import Qt.labs.platform 1.1
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
/*
IndicatorOnlySwitch {
text: qsTr("Show Gears to Zwift Only")
spacing: 0
@@ -5162,7 +5162,7 @@ import Qt.labs.platform 1.1
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
*/
RowLayout {
spacing: 10
@@ -10933,6 +10933,7 @@ import Qt.labs.platform 1.1
settings: settings
accordionContent: ColumnLayout {
spacing: 0
/*
IndicatorOnlySwitch {
id: wahooRGTDirconDelegate
text: qsTr("MyWhoosh Compatibility")
@@ -10946,7 +10947,7 @@ import Qt.labs.platform 1.1
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.wahoo_rgt_dircon = checked; window.settings_restart_to_apply = true; }
}
}*/
Label {
text: qsTr("Enables the compatibility of the Wahoo KICKR protocol to Wahoo RGT app. Leave the RGT compatibility disabled in order to use Zwift.")

View File

@@ -509,8 +509,8 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
//! [Provide Heartbeat]
QObject::connect(&bikeTimer, &QTimer::timeout, this, &virtualbike::bikeProvider);
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool())
bikeTimer.start(100ms);
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool() || zwift_play_emulator)
bikeTimer.start(50ms);
else
bikeTimer.start(1s);
@@ -991,7 +991,10 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (targetCharacteristic.isValid()) {
characteristicChanged(targetCharacteristic, QByteArray::fromHex("116901") + slope + QByteArray::fromHex("3228"));
QByteArray response = QByteArray::fromHex("3c0888041206 0a0440c0bb01");
QByteArray response = CharacteristicWriteProcessor0003::encodeHubRidingData(Bike->wattsMetric().value(), Bike->currentCadence().value(), 0,
Bike->wattsMetric().value(),
CharacteristicWriteProcessor0003::calculateUnknown1(Bike->wattsMetric().value()),
0);
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
} else {
qDebug() << "ERROR! Zwift Play Ask 5 without answer!";
@@ -1043,9 +1046,11 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (targetCharacteristic.isValid()) {
characteristicChanged(targetCharacteristic, QByteArray::fromHex("05") + power);
QByteArray response = QByteArray::fromHex("030882011022181020002898523086ed01");
response[2] = (uint8_t)Bike->wattsMetric().value();
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayRead, response);
QByteArray response = CharacteristicWriteProcessor0003::encodeHubRidingData(Bike->wattsMetric().value(), Bike->currentCadence().value(), 0,
Bike->wattsMetric().value(),
CharacteristicWriteProcessor0003::calculateUnknown1(Bike->wattsMetric().value()),
0);
writeCharacteristic(serviceZwiftPlayBike, zwiftPlayIndicate, response);
} else {
qDebug() << "ERROR! Zwift Play Ask 8 without answer!";
}
@@ -1470,9 +1475,11 @@ void virtualbike::bikeProvider() {
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);
QByteArray response = CharacteristicWriteProcessor0003::encodeHubRidingData(Bike->wattsMetric().value(), Bike->currentCadence().value(), 0,
Bike->wattsMetric().value(),
CharacteristicWriteProcessor0003::calculateUnknown1(Bike->wattsMetric().value()),
0);
writeCharacteristic(serviceZwiftPlayBike, characteristic1, response);
} else if (watt_bike_emulator) {
QLowEnergyCharacteristic characteristic1 =
serviceWattAtomBike->characteristic(QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")));