From c75b6ae5f0295b7a24676a58cf9daee98cc86b0f Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 30 Dec 2024 16:29:31 +0100 Subject: [PATCH 01/11] starting --- src/devices/dircon/dirconmanager.cpp | 17 ++++++++++++++--- src/devices/dircon/dirconpacket.h | 10 ++++++++++ src/devices/dircon/dirconprocessor.cpp | 24 ++++++++++++++++++++---- src/devices/dircon/dirconprocessor.h | 1 + 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index 85270c5e2..ab7980bd3 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -14,10 +14,11 @@ 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) \ + OP(WAHOO_KICKR, "Victory $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \ OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) \ OP(WAHOO_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) @@ -71,7 +72,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_NULL, 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, diff --git a/src/devices/dircon/dirconpacket.h b/src/devices/dircon/dirconpacket.h index 5cbff6dac..8aa2ea705 100644 --- a/src/devices/dircon/dirconpacket.h +++ b/src/devices/dircon/dirconpacket.h @@ -4,10 +4,20 @@ #include #include +#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 diff --git a/src/devices/dircon/dirconprocessor.cpp b/src/devices/dircon/dirconprocessor.cpp index 1805b204e..67df7f006 100644 --- a/src/devices/dircon/dirconprocessor.cpp +++ b/src/devices/dircon/dirconprocessor.cpp @@ -14,6 +14,16 @@ DirconProcessor::DirconProcessor(const QList &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); diff --git a/src/devices/dircon/dirconprocessor.h b/src/devices/dircon/dirconprocessor.h index 24e3051cb..97c9acd05 100644 --- a/src/devices/dircon/dirconprocessor.h +++ b/src/devices/dircon/dirconprocessor.h @@ -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(); From 4f7c7fa7c9575a06e80f6a16407545198ca83466 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 30 Dec 2024 16:39:45 +0100 Subject: [PATCH 02/11] it's working for asking the UUID! --- src/devices/dircon/dirconmanager.cpp | 2 +- src/devices/dircon/dirconpacket.cpp | 56 +++++++++++++++++++++------- src/devices/dircon/dirconpacket.h | 2 + 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index ab7980bd3..26c19e142 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -18,7 +18,7 @@ using namespace std::chrono_literals; OP(ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3) #define DM_MACHINE_OP(OP, P1, P2, P3) \ - OP(WAHOO_KICKR, "Victory $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \ + OP(WAHOO_KICKR, "Wahoo KICKR $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \ OP(WAHOO_BLUEHR, "Wahoo HRM", DM_MACHINE_TYPE_BIKE | DM_MACHINE_TYPE_TREADMILL, P1, P2, P3) \ OP(WAHOO_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) diff --git a/src/devices/dircon/dirconpacket.cpp b/src/devices/dircon/dirconpacket.cpp index 93960d908..acf71d9df 100644 --- a/src/devices/dircon/dirconpacket.cpp +++ b/src/devices/dircon/dirconpacket.cpp @@ -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; diff --git a/src/devices/dircon/dirconpacket.h b/src/devices/dircon/dirconpacket.h index 8aa2ea705..28657c955 100644 --- a/src/devices/dircon/dirconpacket.h +++ b/src/devices/dircon/dirconpacket.h @@ -61,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); }; From c8355184f29d7145ea4e93df55c7975570a7e4b1 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 30 Dec 2024 18:12:10 +0100 Subject: [PATCH 03/11] i'm getting the 0003 but i need to notify the 0002 it doesn't enter into the sendCharacteristicNotification loop --- .../qdomyoszwift.xcodeproj/project.pbxproj | 20 +++ .../characteristicnotifier0002.cpp | 16 ++ .../characteristicnotifier0002.h | 17 ++ .../characteristicwriteprocessor0003.cpp | 162 ++++++++++++++++++ .../characteristicwriteprocessor0003.h | 32 ++++ src/devices/dircon/dirconmanager.cpp | 4 +- src/devices/dircon/dirconmanager.h | 4 +- src/qdomyos-zwift.pri | 4 + 8 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 src/characteristics/characteristicnotifier0002.cpp create mode 100644 src/characteristics/characteristicnotifier0002.h create mode 100644 src/characteristics/characteristicwriteprocessor0003.cpp create mode 100644 src/characteristics/characteristicwriteprocessor0003.h diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj index 451d04e32..420001c93 100644 --- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj +++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj @@ -518,6 +518,10 @@ 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 */; }; 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 +1532,12 @@ 87D91F982800B9970026D43C /* proformwifibike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifibike.h; path = ../src/devices/proformwifibike/proformwifibike.h; sourceTree = ""; }; 87D91F992800B9970026D43C /* proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifibike.cpp; path = ../src/devices/proformwifibike/proformwifibike.cpp; sourceTree = ""; }; 87D91F9B2800B9B90026D43C /* moc_proformwifibike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformwifibike.cpp; sourceTree = ""; }; + 87DA62A22D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicnotifier0002.cpp; sourceTree = ""; }; + 87DA62A32D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_characteristicwriteprocessor0003.cpp; sourceTree = ""; }; + 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; }; 87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5TextToSpeech.a; path = ../../Qt/5.15.2/ios/lib/libQt5TextToSpeech.a; sourceTree = ""; }; 87DA8463284933D200B550E9 /* fakeelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fakeelliptical.h; path = ../src/devices/fakeelliptical/fakeelliptical.h; sourceTree = ""; }; 87DA8464284933D200B550E9 /* fakeelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fakeelliptical.cpp; path = ../src/devices/fakeelliptical/fakeelliptical.cpp; sourceTree = ""; }; @@ -2158,6 +2168,12 @@ 2EB56BE3C2D93CDAB0C52E67 /* Sources */ = { isa = PBXGroup; children = ( + 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 +3442,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 +3785,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 */, diff --git a/src/characteristics/characteristicnotifier0002.cpp b/src/characteristics/characteristicnotifier0002.cpp new file mode 100644 index 000000000..74cdfb262 --- /dev/null +++ b/src/characteristics/characteristicnotifier0002.cpp @@ -0,0 +1,16 @@ +#include "characteristicnotifier0002.h" +#include "bike.h" +#include + +CharacteristicNotifier0002::CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent) + : CharacteristicNotifier(0x0002, parent) { + Bike = bike; +} + +int CharacteristicNotifier0002::notify(QByteArray &value) { + if(answer.length()) { + value.append(answer); + return CN_OK; + } + return CN_INVALID; +} diff --git a/src/characteristics/characteristicnotifier0002.h b/src/characteristics/characteristicnotifier0002.h new file mode 100644 index 000000000..dde47ec68 --- /dev/null +++ b/src/characteristics/characteristicnotifier0002.h @@ -0,0 +1,17 @@ +#ifndef CHARACTERISTICNOTIFIER0002_H +#define CHARACTERISTICNOTIFIER0002_H + +#include "bluetoothdevice.h" +#include "characteristicnotifier.h" + +class CharacteristicNotifier0002 : public CharacteristicNotifier { + Q_OBJECT + bluetoothdevice* Bike = nullptr; + +public: + explicit CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent = nullptr); + int notify(QByteArray &value) override; + QByteArray answer; +}; + +#endif // CHARACTERISTICNOTIFIER0002_H diff --git a/src/characteristics/characteristicwriteprocessor0003.cpp b/src/characteristics/characteristicwriteprocessor0003.cpp new file mode 100644 index 000000000..06672550d --- /dev/null +++ b/src/characteristics/characteristicwriteprocessor0003.cpp @@ -0,0 +1,162 @@ +#include "bike.h" +#include "characteristicwriteprocessor0003.h" +#include + +CharacteristicWriteProcessor0003::CharacteristicWriteProcessor0003(double bikeResistanceGain, + int8_t bikeResistanceOffset, + bluetoothdevice *bike, + CharacteristicNotifier0002 *notifier, + QObject *parent) + : CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier(notifier) { +} + +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(bytes.at(i)); + result |= static_cast(byte & 0x7F) << shift; + bytesRead++; + + if ((byte & 0x80) == 0) { + break; + } + shift += 7; + } + + return {result, bytesRead}; +} + +qint32 CharacteristicWriteProcessor0003::decodeSInt(const QByteArray& bytes) { + if (static_cast(bytes.at(0)) != 0x22) { + qFatal("Invalid field header"); + } + + int length = static_cast(bytes.at(1)); + + if (static_cast(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; +} + +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"); + + QByteArray receivedData = data; + + if (receivedData.startsWith(expectedHexArray)) { + qDebug() << "Zwift Play Processor: Initial connection request"; + reply = QByteArray::fromHex("2a08031211220f4154582030342c2053545820303400"); + } + else if (receivedData.startsWith(expectedHexArray2)) { + qDebug() << "Zwift Play Processor: Device info request"; + reply = QByteArray::fromHex("3c080012320a3008800412040500050" + "11a0b4b49434b5220434f524500320f" + "3430323431383030393834000000003a01314204080110140"); + } + else if (receivedData.startsWith(expectedHexArray3)) { + qDebug() << "Zwift Play Processor: Status request"; + reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01"); + } + 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")); + + reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01"); + } + else if (receivedData.startsWith(expectedHexArray6) || + receivedData.startsWith(expectedHexArray7)) { + qDebug() << "Zwift Play Processor: Gear change request"; + handleZwiftGear(receivedData.mid(4)); + reply = QByteArray::fromHex("03080010001827e7 2000 28 00 3093ed01"); + } + 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 = QByteArray::fromHex("030882011022181020002898523086ed01"); + reply[2] = ((bike*)Bike)->wattsMetric().value(); + } + else { + qDebug() << "Zwift Play Processor: Unhandled request:" << receivedData.toHex(); + return -1; + } + + if (notifier) { + notifier->answer = reply; + } + + return 0; +} diff --git a/src/characteristics/characteristicwriteprocessor0003.h b/src/characteristics/characteristicwriteprocessor0003.h new file mode 100644 index 000000000..74815384b --- /dev/null +++ b/src/characteristics/characteristicwriteprocessor0003.h @@ -0,0 +1,32 @@ +#ifndef CHARACTERISTICWRITEPROCESSOR0003_H +#define CHARACTERISTICWRITEPROCESSOR0003_H + +#include "characteristicnotifier0002.h" +#include "characteristicwriteprocessor.h" + +class CharacteristicWriteProcessor0003 : public CharacteristicWriteProcessor { + Q_OBJECT + CharacteristicNotifier0002 *notifier = nullptr; + +public: + explicit CharacteristicWriteProcessor0003(double bikeResistanceGain, int8_t bikeResistanceOffset, + bluetoothdevice *bike, CharacteristicNotifier0002 *notifier, + 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; + +signals: + void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue); +}; + +#endif // CHARACTERISTICWRITEPROCESSOR0003_H diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index 26c19e142..5ae1363e7 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -23,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 @@ -75,7 +76,7 @@ using namespace std::chrono_literals; 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_NULL, P1, P2, P3) \ + 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) \ @@ -171,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, 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, diff --git a/src/devices/dircon/dirconmanager.h b/src/devices/dircon/dirconmanager.h index a00296a4e..5380a5606 100644 --- a/src/devices/dircon/dirconmanager.h +++ b/src/devices/dircon/dirconmanager.h @@ -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 @@ -20,7 +21,7 @@ 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) #define DM_CHAR_NOTIF_DEFINE_OP(UUID, P1, P2, P3) CharacteristicNotifier##UUID *notif##UUID = 0; @@ -28,6 +29,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 processors; diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index 43a1ba941..23aaa50c9 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -75,6 +75,8 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX # include(../qtzeroconf/qtzeroconf.pri) SOURCES += \ + $$PWD/characteristics/characteristicnotifier0002.cpp \ + $$PWD/characteristics/characteristicwriteprocessor0003.cpp \ $$PWD/devices/antbike/antbike.cpp \ $$PWD/devices/crossrope/crossrope.cpp \ $$PWD/devices/deeruntreadmill/deerruntreadmill.cpp \ @@ -329,6 +331,8 @@ INCLUDEPATH += fit-sdk/ devices/ HEADERS += \ $$PWD/EventHandler.h \ + $$PWD/characteristics/characteristicnotifier0002.h \ + $$PWD/characteristics/characteristicwriteprocessor0003.h \ $$PWD/devices/antbike/antbike.h \ $$PWD/devices/crossrope/crossrope.h \ $$PWD/devices/deeruntreadmill/deerruntreadmill.h \ From 345b0d2f7492c0c46794ade22fcc2df4ecc53ccc Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 31 Dec 2024 14:20:34 +0100 Subject: [PATCH 04/11] adding 0004 notifier --- .../qdomyoszwift.xcodeproj/project.pbxproj | 10 ++++++++ .../characteristicnotifier0002.cpp | 13 ++++++++--- .../characteristicnotifier0002.h | 6 +++-- .../characteristicnotifier0004.cpp | 23 +++++++++++++++++++ .../characteristicnotifier0004.h | 19 +++++++++++++++ .../characteristicwriteprocessor0003.cpp | 14 ++++++----- .../characteristicwriteprocessor0003.h | 7 ++++-- src/devices/dircon/dirconmanager.cpp | 2 +- src/devices/dircon/dirconmanager.h | 3 ++- src/devices/dircon/dirconprocessor.cpp | 2 +- src/qdomyos-zwift.pri | 2 ++ 11 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 src/characteristics/characteristicnotifier0004.cpp create mode 100644 src/characteristics/characteristicnotifier0004.h diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj index 420001c93..c0ba22a8d 100644 --- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj +++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj @@ -522,6 +522,8 @@ 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 */; }; @@ -1538,6 +1540,9 @@ 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 = ""; }; 87DA764F2848F8F200A71B64 /* libQt5TextToSpeech.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libQt5TextToSpeech.a; path = ../../Qt/5.15.2/ios/lib/libQt5TextToSpeech.a; sourceTree = ""; }; 87DA8463284933D200B550E9 /* fakeelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fakeelliptical.h; path = ../src/devices/fakeelliptical/fakeelliptical.h; sourceTree = ""; }; 87DA8464284933D200B550E9 /* fakeelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = fakeelliptical.cpp; path = ../src/devices/fakeelliptical/fakeelliptical.cpp; sourceTree = ""; }; @@ -2168,6 +2173,9 @@ 2EB56BE3C2D93CDAB0C52E67 /* Sources */ = { isa = PBXGroup; children = ( + 87DA62AC2D2426F2008ADA0F /* characteristicnotifier0004.h */, + 87DA62AD2D2426F2008ADA0F /* characteristicnotifier0004.cpp */, + 87DA62AE2D2426F2008ADA0F /* moc_characteristicnotifier0004.cpp */, 87DA62A62D2305F5008ADA0F /* characteristicnotifier0002.h */, 87DA62A72D2305F5008ADA0F /* characteristicnotifier0002.cpp */, 87DA62A82D2305F5008ADA0F /* characteristicwriteprocessor0003.h */, @@ -3827,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 */, diff --git a/src/characteristics/characteristicnotifier0002.cpp b/src/characteristics/characteristicnotifier0002.cpp index 74cdfb262..0e589da25 100644 --- a/src/characteristics/characteristicnotifier0002.cpp +++ b/src/characteristics/characteristicnotifier0002.cpp @@ -1,16 +1,23 @@ #include "characteristicnotifier0002.h" #include "bike.h" #include +#include CharacteristicNotifier0002::CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent) : CharacteristicNotifier(0x0002, parent) { Bike = bike; + answerList = QList(); // Initialize empty list +} + +void CharacteristicNotifier0002::addAnswer(const QByteArray &newAnswer) { + answerList.append(newAnswer); } int CharacteristicNotifier0002::notify(QByteArray &value) { - if(answer.length()) { - value.append(answer); + if(!answerList.isEmpty()) { + value.append(answerList.first()); // Get first item + answerList.removeFirst(); // Remove it from list return CN_OK; } return CN_INVALID; -} +} \ No newline at end of file diff --git a/src/characteristics/characteristicnotifier0002.h b/src/characteristics/characteristicnotifier0002.h index dde47ec68..60ed8862f 100644 --- a/src/characteristics/characteristicnotifier0002.h +++ b/src/characteristics/characteristicnotifier0002.h @@ -3,15 +3,17 @@ #include "bluetoothdevice.h" #include "characteristicnotifier.h" +#include class CharacteristicNotifier0002 : public CharacteristicNotifier { Q_OBJECT bluetoothdevice* Bike = nullptr; + QList answerList; public: explicit CharacteristicNotifier0002(bluetoothdevice *bike, QObject *parent = nullptr); int notify(QByteArray &value) override; - QByteArray answer; + void addAnswer(const QByteArray &newAnswer); }; -#endif // CHARACTERISTICNOTIFIER0002_H +#endif // CHARACTERISTICNOTIFIER0002_H \ No newline at end of file diff --git a/src/characteristics/characteristicnotifier0004.cpp b/src/characteristics/characteristicnotifier0004.cpp new file mode 100644 index 000000000..500589fe3 --- /dev/null +++ b/src/characteristics/characteristicnotifier0004.cpp @@ -0,0 +1,23 @@ +#include "characteristicnotifier0004.h" +#include "bike.h" +#include +#include + +CharacteristicNotifier0004::CharacteristicNotifier0004(bluetoothdevice *bike, QObject *parent) + : CharacteristicNotifier(0x0004, parent) { + Bike = bike; + answerList = QList(); +} + +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; +} \ No newline at end of file diff --git a/src/characteristics/characteristicnotifier0004.h b/src/characteristics/characteristicnotifier0004.h new file mode 100644 index 000000000..43fbcc802 --- /dev/null +++ b/src/characteristics/characteristicnotifier0004.h @@ -0,0 +1,19 @@ +#ifndef CHARACTERISTICNOTIFIER0004_H +#define CHARACTERISTICNOTIFIER0004_H + +#include "bluetoothdevice.h" +#include "characteristicnotifier.h" +#include + +class CharacteristicNotifier0004 : public CharacteristicNotifier { + Q_OBJECT + bluetoothdevice* Bike = nullptr; + QList answerList; + +public: + explicit CharacteristicNotifier0004(bluetoothdevice *bike, QObject *parent = nullptr); + int notify(QByteArray &value) override; + void addAnswer(const QByteArray &newAnswer); +}; + +#endif // CHARACTERISTICNOTIFIER0004_H \ No newline at end of file diff --git a/src/characteristics/characteristicwriteprocessor0003.cpp b/src/characteristics/characteristicwriteprocessor0003.cpp index 06672550d..7222b1c78 100644 --- a/src/characteristics/characteristicwriteprocessor0003.cpp +++ b/src/characteristics/characteristicwriteprocessor0003.cpp @@ -5,9 +5,10 @@ CharacteristicWriteProcessor0003::CharacteristicWriteProcessor0003(double bikeResistanceGain, int8_t bikeResistanceOffset, bluetoothdevice *bike, - CharacteristicNotifier0002 *notifier, + CharacteristicNotifier0002 *notifier0002, + CharacteristicNotifier0004 *notifier0004, QObject *parent) - : CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier(notifier) { + : CharacteristicWriteProcessor(bikeResistanceGain, bikeResistanceOffset, bike, parent), notifier0002(notifier0002), notifier0004(notifier0004) { } CharacteristicWriteProcessor0003::VarintResult CharacteristicWriteProcessor0003::decodeVarint(const QByteArray& bytes, int startIndex) { @@ -107,6 +108,11 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra 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"; @@ -154,9 +160,5 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra return -1; } - if (notifier) { - notifier->answer = reply; - } - return 0; } diff --git a/src/characteristics/characteristicwriteprocessor0003.h b/src/characteristics/characteristicwriteprocessor0003.h index 74815384b..adf3cbecf 100644 --- a/src/characteristics/characteristicwriteprocessor0003.h +++ b/src/characteristics/characteristicwriteprocessor0003.h @@ -2,15 +2,18 @@ #define CHARACTERISTICWRITEPROCESSOR0003_H #include "characteristicnotifier0002.h" +#include "characteristicnotifier0004.h" #include "characteristicwriteprocessor.h" class CharacteristicWriteProcessor0003 : public CharacteristicWriteProcessor { Q_OBJECT - CharacteristicNotifier0002 *notifier = nullptr; + CharacteristicNotifier0002 *notifier0002 = nullptr; + CharacteristicNotifier0004 *notifier0004 = nullptr; public: explicit CharacteristicWriteProcessor0003(double bikeResistanceGain, int8_t bikeResistanceOffset, - bluetoothdevice *bike, CharacteristicNotifier0002 *notifier, + bluetoothdevice *bike, CharacteristicNotifier0002 *notifier0002, + CharacteristicNotifier0004 *notifier0004, QObject *parent = nullptr); int writeProcess(quint16 uuid, const QByteArray &data, QByteArray &out) override; diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index 5ae1363e7..6f6cfb9a8 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -172,7 +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, 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, diff --git a/src/devices/dircon/dirconmanager.h b/src/devices/dircon/dirconmanager.h index 5380a5606..9713516ce 100644 --- a/src/devices/dircon/dirconmanager.h +++ b/src/devices/dircon/dirconmanager.h @@ -21,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(0002, 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; diff --git a/src/devices/dircon/dirconprocessor.cpp b/src/devices/dircon/dirconprocessor.cpp index 67df7f006..7c821fba6 100644 --- a/src/devices/dircon/dirconprocessor.cpp +++ b/src/devices/dircon/dirconprocessor.cpp @@ -225,7 +225,7 @@ bool DirconProcessor::sendCharacteristicNotification(quint16 uuid, const QByteAr pkt.uuid = uuid; for (QHash::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) diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index 23aaa50c9..8a644e97e 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -76,6 +76,7 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX SOURCES += \ $$PWD/characteristics/characteristicnotifier0002.cpp \ + $$PWD/characteristics/characteristicnotifier0004.cpp \ $$PWD/characteristics/characteristicwriteprocessor0003.cpp \ $$PWD/devices/antbike/antbike.cpp \ $$PWD/devices/crossrope/crossrope.cpp \ @@ -332,6 +333,7 @@ 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 \ From 288cb3974bda2e53dc0ce941e7e68c862f5c105f Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 31 Dec 2024 15:00:51 +0100 Subject: [PATCH 05/11] kind of works (no unhandled frames) --- .../characteristicwriteprocessor0003.cpp | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/characteristics/characteristicwriteprocessor0003.cpp b/src/characteristics/characteristicwriteprocessor0003.cpp index 7222b1c78..a42008758 100644 --- a/src/characteristics/characteristicwriteprocessor0003.cpp +++ b/src/characteristics/characteristicwriteprocessor0003.cpp @@ -102,6 +102,8 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra 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; @@ -119,10 +121,24 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra 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"; @@ -135,12 +151,32 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra QByteArray::fromHex("116901") + slope + QByteArray::fromHex("3228")); reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01"); + notifier0004->addAnswer(reply); } - else if (receivedData.startsWith(expectedHexArray6) || - receivedData.startsWith(expectedHexArray7)) { - qDebug() << "Zwift Play Processor: Gear change request"; + 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"; @@ -154,6 +190,19 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra reply = QByteArray::fromHex("030882011022181020002898523086ed01"); reply[2] = ((bike*)Bike)->wattsMetric().value(); + notifier0002->addAnswer(reply); + } + 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(); From 1946b4666575861d3d1a8aaf5403e02a0224af50 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 1 Jan 2025 14:45:36 +0100 Subject: [PATCH 06/11] it works! --- helpers/dircon-parser.py | 188 ++++++++++++++++++ .../characteristicwriteprocessor0003.cpp | 74 ++++++- .../characteristicwriteprocessor0003.h | 7 + 3 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 helpers/dircon-parser.py diff --git a/helpers/dircon-parser.py b/helpers/dircon-parser.py new file mode 100644 index 000000000..bd06ff00d --- /dev/null +++ b/helpers/dircon-parser.py @@ -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 ") + sys.exit(1) + + parse_file(sys.argv[1]) diff --git a/src/characteristics/characteristicwriteprocessor0003.cpp b/src/characteristics/characteristicwriteprocessor0003.cpp index a42008758..5d27ae1b6 100644 --- a/src/characteristics/characteristicwriteprocessor0003.cpp +++ b/src/characteristics/characteristicwriteprocessor0003.cpp @@ -93,6 +93,69 @@ void CharacteristicWriteProcessor0003::handleZwiftGear(const QByteArray &array) 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"); @@ -150,8 +213,15 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), QByteArray::fromHex("116901") + slope + QByteArray::fromHex("3228")); - reply = QByteArray::fromHex("3c0888041206 0a0440c0bb01"); - notifier0004->addAnswer(reply); + 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"; diff --git a/src/characteristics/characteristicwriteprocessor0003.h b/src/characteristics/characteristicwriteprocessor0003.h index adf3cbecf..b0ea3cade 100644 --- a/src/characteristics/characteristicwriteprocessor0003.h +++ b/src/characteristics/characteristicwriteprocessor0003.h @@ -27,6 +27,13 @@ class CharacteristicWriteProcessor0003 : public CharacteristicWriteProcessor { 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); From 2bdbeed5f40473368e4e0bf546b59f2f14f9cb24 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 1 Jan 2025 14:51:39 +0100 Subject: [PATCH 07/11] wahoo rgt setting is not useful anymore --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++++++------ src/settings.qml | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj index c0ba22a8d..0be4cc7d0 100644 --- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj +++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj @@ -4243,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 = 990; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4437,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 = 990; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4667,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 = 990; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4763,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 = 990; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4855,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 = 990; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4969,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 = 990; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/settings.qml b/src/settings.qml index 1071e9dd9..3663df026 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -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.") From 730c16f7b49f9f207d7d2e46d1c217246cbcecbf Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 1 Jan 2025 18:41:53 +0100 Subject: [PATCH 08/11] dircon works perfectly on ios! --- .../characteristicwriteprocessor0003.cpp | 16 +++++++++++++--- src/devices/dircon/dirconmanager.cpp | 2 ++ src/devices/zwifthubbike/zwifthubbike.swift | 6 +++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/characteristics/characteristicwriteprocessor0003.cpp b/src/characteristics/characteristicwriteprocessor0003.cpp index 5d27ae1b6..e977f3fa5 100644 --- a/src/characteristics/characteristicwriteprocessor0003.cpp +++ b/src/characteristics/characteristicwriteprocessor0003.cpp @@ -212,7 +212,9 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), QByteArray::fromHex("116901") + slope + QByteArray::fromHex("3228")); - + + changeSlope(slopefloat, 0 /* TODO */, 0 /* TODO */); + reply = encodeHubRidingData( Bike->wattsMetric().value(), Bike->currentCadence().value(), @@ -258,9 +260,17 @@ int CharacteristicWriteProcessor0003::writeProcess(quint16 uuid, const QByteArra emit ftmsCharacteristicChanged(QLowEnergyCharacteristic(), QByteArray::fromHex("05") + power); - reply = QByteArray::fromHex("030882011022181020002898523086ed01"); - reply[2] = ((bike*)Bike)->wattsMetric().value(); + 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"; diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index 6f6cfb9a8..2eba16f29 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -180,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) diff --git a/src/devices/zwifthubbike/zwifthubbike.swift b/src/devices/zwifthubbike/zwifthubbike.swift index c92dfc5fa..b0d3b253b 100644 --- a/src/devices/zwifthubbike/zwifthubbike.swift +++ b/src/devices/zwifthubbike/zwifthubbike.swift @@ -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]) From 860489616cc1ee06c5d9e8d680321620f1cb490b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 1 Jan 2025 18:56:11 +0100 Subject: [PATCH 09/11] improving wattage also for all bluetooth, but it's not perfect yet --- .../qdomyoszwift.xcodeproj/project.pbxproj | 12 ++-- src/ios/virtualbike_zwift.swift | 65 ++++++++++++------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj index 0be4cc7d0..522053e38 100644 --- a/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj +++ b/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qdomyoszwift.xcodeproj/project.pbxproj @@ -4243,7 +4243,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1"; @@ -4437,7 +4437,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4667,7 +4667,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4763,7 +4763,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4855,7 +4855,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4969,7 +4969,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 990; + CURRENT_PROJECT_VERSION = 991; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/ios/virtualbike_zwift.swift b/src/ios/virtualbike_zwift.swift index 44f6a2198..c0ebe2ebd 100644 --- a/src/ios/virtualbike_zwift.swift +++ b/src/ios/virtualbike_zwift.swift @@ -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 } From 71cb562b057370193c57a728d3021f4ca29ddb8d Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 7 Jan 2025 17:31:40 +0100 Subject: [PATCH 10/11] Update characteristicwriteprocessor0003.h --- src/characteristics/characteristicwriteprocessor0003.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/characteristics/characteristicwriteprocessor0003.h b/src/characteristics/characteristicwriteprocessor0003.h index b0ea3cade..c644b43eb 100644 --- a/src/characteristics/characteristicwriteprocessor0003.h +++ b/src/characteristics/characteristicwriteprocessor0003.h @@ -26,7 +26,7 @@ class CharacteristicWriteProcessor0003 : public CharacteristicWriteProcessor { VarintResult decodeVarint(const QByteArray& bytes, int startIndex); qint32 decodeSInt(const QByteArray& bytes); void handleZwiftGear(const QByteArray &array); - int currentZwiftGear = 0; + int currentZwiftGear = 8; QByteArray encodeHubRidingData(uint32_t power, uint32_t cadence, uint32_t speedX100, From 3ceb256272f55f7c1a73426e6469da02772ec686 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 20 Jan 2025 15:26:35 +0100 Subject: [PATCH 11/11] Update dirconmanager.cpp --- src/devices/dircon/dirconmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/devices/dircon/dirconmanager.cpp b/src/devices/dircon/dirconmanager.cpp index 2eba16f29..ad22ce926 100644 --- a/src/devices/dircon/dirconmanager.cpp +++ b/src/devices/dircon/dirconmanager.cpp @@ -180,6 +180,7 @@ 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);