mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860489616c | ||
|
|
730c16f7b4 | ||
|
|
2bdbeed5f4 | ||
|
|
1946b46665 | ||
|
|
288cb3974b | ||
|
|
345b0d2f74 | ||
|
|
c8355184f2 | ||
|
|
4f7c7fa7c9 | ||
|
|
c75b6ae5f0 |
@@ -518,6 +518,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 */; };
|
||||
@@ -1528,6 +1534,15 @@
|
||||
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>"; };
|
||||
87DA62A62D2305F5008ADA0F /* characteristicnotifier0002.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier0002.h; path = ../src/characteristics/characteristicnotifier0002.h; sourceTree = SOURCE_ROOT; };
|
||||
87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier0002.cpp; path = ../src/characteristics/characteristicnotifier0002.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87DA62A82D2305F5008ADA0F /* characteristicwriteprocessor0003.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = characteristicwriteprocessor0003.h; path = ../src/characteristics/characteristicwriteprocessor0003.h; sourceTree = SOURCE_ROOT; };
|
||||
87DA62A92D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessor0003.cpp; path = ../src/characteristics/characteristicwriteprocessor0003.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87DA62AC2D2426F2008ADA0F /* characteristicnotifier0004.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = characteristicnotifier0004.h; path = ../src/characteristics/characteristicnotifier0004.h; 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>"; };
|
||||
@@ -2158,6 +2173,15 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87DA62AC2D2426F2008ADA0F /* characteristicnotifier0004.h */,
|
||||
87DA62AD2D2426F2008ADA0F /* characteristicnotifier0004.cpp */,
|
||||
87DA62AE2D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp */,
|
||||
87DA62A62D2305F5008ADA0F /* characteristicnotifier0002.h */,
|
||||
87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */,
|
||||
87DA62A82D2305F5008ADA0F /* characteristicwriteprocessor0003.h */,
|
||||
87DA62A92D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp */,
|
||||
87DA62A22D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp */,
|
||||
87DA62A32D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp */,
|
||||
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */,
|
||||
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */,
|
||||
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */,
|
||||
@@ -3426,6 +3450,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 */,
|
||||
@@ -3767,6 +3793,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 */,
|
||||
@@ -3807,6 +3835,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 */,
|
||||
@@ -4213,7 +4243,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4407,7 +4437,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4637,7 +4667,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4733,7 +4763,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4825,7 +4855,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4939,7 +4969,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 989;
|
||||
CURRENT_PROJECT_VERSION = 991;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
|
||||
188
helpers/dircon-parser.py
Normal file
188
helpers/dircon-parser.py
Normal 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])
|
||||
23
src/characteristics/characteristicnotifier0002.cpp
Normal file
23
src/characteristics/characteristicnotifier0002.cpp
Normal 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;
|
||||
}
|
||||
19
src/characteristics/characteristicnotifier0002.h
Normal file
19
src/characteristics/characteristicnotifier0002.h
Normal 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
|
||||
23
src/characteristics/characteristicnotifier0004.cpp
Normal file
23
src/characteristics/characteristicnotifier0004.cpp
Normal 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;
|
||||
}
|
||||
19
src/characteristics/characteristicnotifier0004.h
Normal file
19
src/characteristics/characteristicnotifier0004.h
Normal 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
|
||||
293
src/characteristics/characteristicwriteprocessor0003.cpp
Normal file
293
src/characteristics/characteristicwriteprocessor0003.cpp
Normal file
@@ -0,0 +1,293 @@
|
||||
#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; }
|
||||
}
|
||||
|
||||
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 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 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)) {
|
||||
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[9] = receivedData[4];
|
||||
reply[10] = 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;
|
||||
}
|
||||
42
src/characteristics/characteristicwriteprocessor0003.h
Normal file
42
src/characteristics/characteristicwriteprocessor0003.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#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;
|
||||
|
||||
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 = 0;
|
||||
QByteArray encodeHubRidingData(uint32_t power,
|
||||
uint32_t cadence,
|
||||
uint32_t speedX100,
|
||||
uint32_t hr,
|
||||
uint32_t unknown1,
|
||||
uint32_t unknown2);
|
||||
|
||||
|
||||
signals:
|
||||
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
|
||||
};
|
||||
|
||||
#endif // CHARACTERISTICWRITEPROCESSOR0003_H
|
||||
@@ -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,6 +180,8 @@ 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(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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -854,22 +876,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
|
||||
}
|
||||
|
||||
@@ -75,6 +75,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/deeruntreadmill/deerruntreadmill.cpp \
|
||||
@@ -329,6 +332,9 @@ INCLUDEPATH += fit-sdk/ devices/
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/EventHandler.h \
|
||||
$$PWD/characteristics/characteristicnotifier0002.h \
|
||||
$$PWD/characteristics/characteristicnotifier0004.h \
|
||||
$$PWD/characteristics/characteristicwriteprocessor0003.h \
|
||||
$$PWD/devices/antbike/antbike.h \
|
||||
$$PWD/devices/crossrope/crossrope.h \
|
||||
$$PWD/devices/deeruntreadmill/deerruntreadmill.h \
|
||||
|
||||
@@ -10547,6 +10547,7 @@ import QtQuick.Dialogs 1.0
|
||||
settings: settings
|
||||
accordionContent: ColumnLayout {
|
||||
spacing: 0
|
||||
/*
|
||||
IndicatorOnlySwitch {
|
||||
id: wahooRGTDirconDelegate
|
||||
text: qsTr("MyWhoosh Compatibility")
|
||||
@@ -10560,7 +10561,7 @@ import QtQuick.Dialogs 1.0
|
||||
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.")
|
||||
|
||||
Reference in New Issue
Block a user