diff --git a/src/android/src/ZwiftHub.java b/src/android/src/ZwiftHub.java new file mode 100644 index 000000000..7fc9aca40 --- /dev/null +++ b/src/android/src/ZwiftHub.java @@ -0,0 +1,74 @@ +package org.cagnulen.qdomyoszwift; + +import android.app.ActivityManager; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; +import android.os.Looper; +import android.os.Handler; +import android.util.Log; +import com.garmin.android.connectiq.ConnectIQ; +import com.garmin.android.connectiq.ConnectIQAdbStrategy; +import com.garmin.android.connectiq.IQApp; +import com.garmin.android.connectiq.IQDevice; +import com.garmin.android.connectiq.exception.InvalidStateException; +import com.garmin.android.connectiq.exception.ServiceUnavailableException; +import android.content.BroadcastReceiver; +import android.content.ContextWrapper; +import android.content.IntentFilter; +import android.widget.Toast; + +import org.jetbrains.annotations.Nullable; + +import com.google.protobuf.InvalidProtocolBufferException; + +import java.util.HashMap; +import java.util.List; + +public class ZwiftHub { + + private static Context context; + + private static final String TAG = "ZwiftHub: "; + + public static byte[] inclinationCommand(double inclination) throws InvalidProtocolBufferException { + SimulationParam.Builder simulation = SimulationParam.newBuilder(); + simulation.setInclineX100((int)(inclination * 100.0)); + + HubCommand.Builder command = HubCommand.newBuilder(); + command.setSimulation(simulation.build()); + + byte[] data = command.build().toByteArray(); + byte[] fullData = new byte[data.length + 1]; + fullData[0] = 0x04; + System.arraycopy(data, 0, fullData, 1, data.length); + + return fullData; + } + + public static byte[] setGearCommand(int gears) throws InvalidProtocolBufferException { + PhysicalParam.Builder physical = PhysicalParam.newBuilder(); + physical.setGearRatioX10000(gears); + + HubCommand.Builder command = HubCommand.newBuilder(); + command.setPhysical(physical.build()); + + byte[] data = command.build().toByteArray(); + byte[] fullData = new byte[data.length + 1]; + fullData[0] = 0x04; + System.arraycopy(data, 0, fullData, 1, data.length); + + return fullData; + } +} diff --git a/src/devices/horizontreadmill/horizontreadmill.cpp b/src/devices/horizontreadmill/horizontreadmill.cpp index 0ec6d5fe8..390a560bb 100644 --- a/src/devices/horizontreadmill/horizontreadmill.cpp +++ b/src/devices/horizontreadmill/horizontreadmill.cpp @@ -2311,6 +2311,82 @@ void horizontreadmill::characteristicRead(const QLowEnergyCharacteristic &charac } } +#ifdef Q_OS_WIN +bool horizontreadmill::discoverServicesWin11(const QBluetoothAddress& address) { + BLUETOOTH_DEVICE_INFO deviceInfo = { sizeof(BLUETOOTH_DEVICE_INFO) }; + deviceInfo.Address.ullLong = address.toUInt64(); + + DWORD error = BluetoothGetDeviceInfo(nullptr, &deviceInfo); + if (error != ERROR_SUCCESS) { + qDebug() << "Failed to get device info. Error code:" << error; + DWORD lastError = GetLastError(); + qDebug() << "Last error:" << lastError; + return false; + } + + + DWORD serviceCount = 0; + GUID* serviceGuids = nullptr; + + // Prima chiamata per ottenere il numero di servizi + DWORD result = BluetoothEnumerateInstalledServices( + nullptr, + &deviceInfo, + &serviceCount, + nullptr); + + if (result != ERROR_SUCCESS) { + qDebug() << "Failed to enumerate services count"; + return false; + } + + if (serviceCount == 0) { + qDebug() << "No services found"; + return false; + } + + // Allocare memoria per i GUID dei servizi + serviceGuids = new GUID[serviceCount]; + + // Seconda chiamata per ottenere i GUID effettivi + result = BluetoothEnumerateInstalledServices( + nullptr, + &deviceInfo, + &serviceCount, + serviceGuids); + + if (result != ERROR_SUCCESS) { + delete[] serviceGuids; + qDebug() << "Failed to enumerate services"; + return false; + } + + // Processare ogni servizio trovato + for (DWORD i = 0; i < serviceCount; i++) { + handleWin11Service(serviceGuids[i]); + } + + delete[] serviceGuids; + return true; +} + +void horizontreadmill::handleWin11Service(const GUID& serviceGuid) { + wchar_t guidString[39]; + StringFromGUID2(serviceGuid, guidString, sizeof(guidString)/sizeof(wchar_t)); + + QString qtGuidString = QString::fromWCharArray(guidString); + QBluetoothUuid qtUuid(qtGuidString); + + QLowEnergyService* service = m_control->createServiceObject(qtUuid); + if (service) { + gattCommunicationChannelService.append(service); + connect(service, &QLowEnergyService::stateChanged, this, &horizontreadmill::stateChanged); + service->discoverDetails(); + qDebug() << "Discovered service:" << qtUuid; + } +} +#endif + void horizontreadmill::serviceScanDone(void) { emit debug(QStringLiteral("serviceScanDone")); @@ -2318,12 +2394,18 @@ void horizontreadmill::serviceScanDone(void) { firstStateChanged = 0; auto services_list = m_control->services(); +#ifdef Q_OS_WIN + if (discoverServicesWin11(m_control->remoteAddress())) { + return; + } +#endif + for (const QBluetoothUuid &s : qAsConst(services_list)) { - qDebug() << s << "discovering..."; - gattCommunicationChannelService.append(m_control->createServiceObject(s)); - connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, - &horizontreadmill::stateChanged); - gattCommunicationChannelService.constLast()->discoverDetails(); + qDebug() << s << "discovering..."; + gattCommunicationChannelService.append(m_control->createServiceObject(s)); + connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this, + &horizontreadmill::stateChanged); + gattCommunicationChannelService.constLast()->discoverDetails(); } } diff --git a/src/devices/horizontreadmill/horizontreadmill.h b/src/devices/horizontreadmill/horizontreadmill.h index 66d8685fe..469d82373 100644 --- a/src/devices/horizontreadmill/horizontreadmill.h +++ b/src/devices/horizontreadmill/horizontreadmill.h @@ -34,6 +34,13 @@ #include "ios/lockscreen.h" #endif +#ifdef Q_OS_WIN +#include +#include +#include +#include +#endif + class horizontreadmill : public treadmill { Q_OBJECT public: @@ -48,6 +55,11 @@ class horizontreadmill : public treadmill { bool autoStartWhenSpeedIsGreaterThenZero() override; private: +#ifdef Q_OS_WIN + bool discoverServicesWin11(const QBluetoothAddress& address); + void handleWin11Service(const GUID& serviceGuid); +#endif + void writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic characteristic, uint8_t *data, uint8_t data_len, QString info, bool disable_log = false, bool wait_for_response = false); void waitForAPacket(); diff --git a/src/homeform.cpp b/src/homeform.cpp index 842f12f72..b642fd258 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -990,14 +990,15 @@ void homeform::backup() { static uint8_t index = 0; qDebug() << QStringLiteral("saving fit file backup..."); - QString path = getWritableAppDir(); - bluetoothdevice *dev = bluetoothManager->device(); - if (dev) { - - QString filename = path + QString::number(index) + backupFitFileName; - QFile::remove(filename); - qfit::save(filename, Session, dev->deviceType(), + qDebug() << QStringLiteral("writable dir") << path; + bluetoothdevice *dev = bluetoothManager->device(); + if (dev) { + QString filename = path + QString::number(index) + backupFitFileName; + qDebug() << QStringLiteral("filename") << filename; + QFile::remove(filename); + qDebug() << QStringLiteral("filename removed") << filename; + qfit::save(filename, Session, dev->deviceType(), qobject_cast(dev) ? QFIT_PROCESS_DISTANCENOISE : QFIT_PROCESS_NONE, stravaPelotonWorkoutType, dev->bluetoothDevice.name()); diff --git a/src/main.cpp b/src/main.cpp index a81b7ba2f..728874582 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -331,6 +331,50 @@ int main(int argc, char *argv[]) { app->setOrganizationDomain(QStringLiteral("robertoviola.cloud")); app->setApplicationName(QStringLiteral("qDomyos-Zwift")); + /* TEST ZWIFT HUB */ +#ifdef Q_OS_ANDROID +{ + QAndroidJniObject rrr = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "inclinationCommand", + "(D)[B", + 8.0); + + if(!rrr.isValid()) { + qDebug() << "inclinationCommand returned invalid value"; + } + + jbyteArray array = rrr.object(); + QAndroidJniEnvironment env; + jbyte* bytes = env->GetByteArrayElements(array, nullptr); + jsize length = env->GetArrayLength(array); + + QByteArray message((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + qDebug() << "inclination command" << message.toHex(' '); + + QAndroidJniObject rr = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ZwiftHub", + "setGearCommand", + "(I)[B", + 32608); + + if (!rr.isValid()) { + qDebug() << "setGearCommand returned invalid value"; + } + + array = rr.object(); + bytes = env->GetByteArrayElements(array, nullptr); + length = env->GetArrayLength(array); + + QByteArray proto((char*)bytes, length); + + env->ReleaseByteArrayElements(array, bytes, JNI_ABORT); + qDebug() << "gear command" << proto.toHex(' '); +} +#endif + QSettings settings; #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index 06b154d72..617b56b5c 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -31,7 +31,14 @@ CONFIG += qmltypes #unix:!android: CONFIG += webengine win32:DEFINES += _ITERATOR_DEBUG_LEVEL=0 -win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -L$$PWD +win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -lBthprops -lVersion -lRpcrt4 -lole32 -L$$PWD + +win32 { + LIBS += -lBthprops + LIBS += -lVersion + LIBS += -lRpcrt4 # Per StringFromGUID2 + LIBS += -lole32 # Altra possibile dipendenza per GUID +} QML_IMPORT_NAME = org.cagnulein.qdomyoszwift QML_IMPORT_MAJOR_VERSION = 1