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 e26aa1c72..e79580048 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 @@ -3754,7 +3754,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; HEADER_SEARCH_PATHS = ( @@ -3924,7 +3924,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = NO; @@ -4130,7 +4130,7 @@ CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -4226,7 +4226,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 6335M7T29D; ENABLE_BITCODE = YES; @@ -4318,7 +4318,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; ENABLE_PREVIEWS = YES; @@ -4432,7 +4432,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 701; + CURRENT_PROJECT_VERSION = 703; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\""; ENABLE_BITCODE = YES; diff --git a/src/domyosrower.cpp b/src/domyosrower.cpp index 957a34e9c..924957c1d 100644 --- a/src/domyosrower.cpp +++ b/src/domyosrower.cpp @@ -40,6 +40,11 @@ void domyosrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QSt QEventLoop loop; QTimer timeout; + if(ftmsRower) { + qDebug() << "ftmsRower! so don't send anything!"; + return; + } + if (wait_for_response) { connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit); timeout.singleShot(300ms, &loop, &QEventLoop::quit); @@ -161,7 +166,8 @@ void domyosrower::update() { initRequest = false; // if(bike_type == CHANG_YOW) - btinit_changyow(false); + if(!ftmsRower) + btinit_changyow(false); // else // btinit_telink(false); } else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState && @@ -180,6 +186,22 @@ void domyosrower::update() { bool virtual_device_force_bike = settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike) .toBool(); +#ifdef Q_OS_IOS +#ifndef IO_UNDER_QT + bool cadence = + settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool(); + bool ios_peloton_workaround = + settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool(); + if (ios_peloton_workaround && cadence) { + + qDebug() << "ios_peloton_workaround activated!"; + h = new lockscreen(); + h->virtualbike_ios(); + } else + +#endif +#endif + { if (virtual_device_enabled) { if (virtual_device_rower) { qDebug() << QStringLiteral("creating virtual rower interface..."); @@ -202,9 +224,11 @@ void domyosrower::update() { connect(virtualBike, &virtualbike::changeInclination, this, &domyosrower::changeInclination); this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE); } - firstVirtual = 1; + } } } + firstVirtual = 1; + // ******************************************************************************************************** // updating the treadmill console every second @@ -280,89 +304,298 @@ void domyosrower::characteristicChanged(const QLowEnergyCharacteristic &characte QSettings settings; QString heartRateBeltName = settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString(); + bool disable_hr_frommachinery = + settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool(); emit debug(QStringLiteral(" << ") + newValue.toHex(' ')); lastPacket = newValue; - if (newValue.length() != 26) { - return; - } + if(!ftmsRower) { + if (newValue.length() != 26) { + return; + } - if (newValue.at(22) == 0x06) { - emit debug(QStringLiteral("inclination up button pressed!")); + if (newValue.at(22) == 0x06) { + emit debug(QStringLiteral("inclination up button pressed!")); - // requestStart = 1; - } else if (newValue.at(22) == 0x07) { - emit debug(QStringLiteral("inclination down button pressed!")); // i guess it should be the inclination down + // requestStart = 1; + } else if (newValue.at(22) == 0x07) { + emit debug(QStringLiteral("inclination down button pressed!")); // i guess it should be the inclination down - // requestStop = 1; - } + // requestStop = 1; + } - /*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the - inclination and speed status return;*/ - - double speed = - GetSpeedFromPacket(newValue) * - settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio) - .toDouble(); - double kcal = GetKcalFromPacket(newValue); - double distance = - GetDistanceFromPacket(newValue) * - settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio) - .toDouble(); - - if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name) - .toString() - .startsWith(QStringLiteral("Disabled"))) { - Cadence = ((uint8_t)newValue.at(9)); - } - Resistance = newValue.at(14); - Inclination = newValue.at(21); - if (Resistance.value() < 1) { - emit debug(QStringLiteral("invalid resistance value ") + QString::number(Resistance.value()) + - QStringLiteral(" putting to default")); - Resistance = 1; - } - if (Inclination.value() < 0 || Inclination.value() > 15) { - emit debug(QStringLiteral("invalid inclination value ") + QString::number(Inclination.value()) + - QStringLiteral(" putting to default")); - Inclination.setValue(0); - } + /*if ((uint8_t)newValue.at(1) != 0xbc && newValue.at(2) != 0x04) // intense run, these are the bytes for the + inclination and speed status return;*/ + + double speed = + GetSpeedFromPacket(newValue) * + settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio) + .toDouble(); + double kcal = GetKcalFromPacket(newValue); + double distance = + GetDistanceFromPacket(newValue) * + settings.value(QZSettings::domyos_elliptical_speed_ratio, QZSettings::default_domyos_elliptical_speed_ratio) + .toDouble(); + + if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name) + .toString() + .startsWith(QStringLiteral("Disabled"))) { + Cadence = ((uint8_t)newValue.at(9)); + } + Resistance = newValue.at(14); + Inclination = newValue.at(21); + if (Resistance.value() < 1) { + emit debug(QStringLiteral("invalid resistance value ") + QString::number(Resistance.value()) + + QStringLiteral(" putting to default")); + Resistance = 1; + } + if (Inclination.value() < 0 || Inclination.value() > 15) { + emit debug(QStringLiteral("invalid inclination value ") + QString::number(Inclination.value()) + + QStringLiteral(" putting to default")); + Inclination.setValue(0); + } + + #ifdef Q_OS_ANDROID + if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) + Heart = (uint8_t)KeepAwakeHelper::heart(); + else + #endif + { + if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) { + Heart = ((uint8_t)newValue.at(18)); + } + } + + CrankRevs++; + LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0)); + + emit debug(QStringLiteral("Current speed: ") + QString::number(speed)); + emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value())); + emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value())); + emit debug(QStringLiteral("Current inclination: ") + QString::number(Inclination.value())); + emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value())); + emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal)); + emit debug(QStringLiteral("Current Distance: ") + QString::number(distance)); + emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs)); + emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); + emit debug(QStringLiteral("Current Watt: ") + QString::number(watts())); + + if (m_control->error() != QLowEnergyController::NoError) { + qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString(); + } + + Speed = speed; + KCal = kcal; + Distance += ((Speed.value() / 3600000.0) * + ((double)lastRefreshCharacteristicChanged.msecsTo(now))); + lastRefreshCharacteristicChanged = now; + } else { + union flags { + struct { + + uint16_t moreData : 1; + uint16_t avgStroke : 1; + uint16_t totDistance : 1; + uint16_t instantPace : 1; + uint16_t avgPace : 1; + uint16_t instantPower : 1; + uint16_t avgPower : 1; + uint16_t resistanceLvl : 1; + uint16_t expEnergy : 1; + uint16_t heartRate : 1; + uint16_t metabolic : 1; + uint16_t elapsedTime : 1; + uint16_t remainingTime : 1; + uint16_t spare : 3; + }; + + uint16_t word_flags; + }; + + flags Flags; + int index = 0; + double cadence_divider = 2.0; + Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0); + index += 2; + + if (!Flags.moreData) { + + Cadence = ((uint8_t)newValue.at(index)) / cadence_divider; + + StrokesCount = + (((uint16_t)((uint8_t)newValue.at(index + 2)) << 8) | (uint16_t)((uint8_t)newValue.at(index + 1))); + + if (lastStrokesCount != StrokesCount.value()) { + lastStroke = now; + } + lastStrokesCount = StrokesCount.value(); + + index += 3; + + /* + * the concept 2 sends the pace in 2 frames, so this condition will create a bogus speed + if (!Flags.instantPace) { + // eredited by echelon rower, probably we need to change this + Speed = (0.37497622 * ((double)Cadence.value())) / 2.0; + emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value())); + }*/ + emit debug(QStringLiteral("Strokes Count: ") + QString::number(StrokesCount.value())); + } + + if (Flags.avgStroke) { + + double avgStroke; + avgStroke = ((double)(uint16_t)((uint8_t)newValue.at(index))) / cadence_divider; + index += 1; + emit debug(QStringLiteral("Current Average Stroke: ") + QString::number(avgStroke)); + } + + if (Flags.totDistance) { + Distance = ((double)((((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) | + (uint32_t)((uint8_t)newValue.at(index + 1)) << 8) | + (uint32_t)((uint8_t)newValue.at(index)))) / + 1000.0; + index += 3; + } else { + Distance += ((Speed.value() / 3600000.0) * + ((double)lastRefreshCharacteristicChanged.msecsTo(now))); + } + + emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value())); + + if (Flags.instantPace) { + + double instantPace; + instantPace = + ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + index += 2; + emit debug(QStringLiteral("Current Pace: ") + QString::number(instantPace)); + + Speed = (60.0 / instantPace) * + 30.0; // translating pace (min/500m) to km/h in order to match the pace function in the rower.cpp + emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value())); + } + + if (Flags.avgPace) { + + double avgPace; + avgPace = + ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + index += 2; + emit debug(QStringLiteral("Current Average Pace: ") + QString::number(avgPace)); + } + + if (Flags.instantPower) { + double watt = + ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + index += 2; + m_watt = watt; + emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value())); + } + + if (Flags.avgPower) { + + double avgPower; + avgPower = + ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + index += 2; + emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower)); + } + + if (Flags.resistanceLvl) { + Resistance = + ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + emit resistanceRead(Resistance.value()); + index += 2; + emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value())); + } + + if (Flags.expEnergy && index + 1 < newValue.length()) { + KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))); + index += 2; + + // energy per hour + index += 2; + + // energy per minute + index += 1; + } else { + if (watts()) + KCal += + ((((0.048 * ((double)watts()) + 1.19) * + settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) / + 200.0) / + (60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo( + now)))); //(( (0.048* Output in watts +1.19) * body weight in + // kg * 3.5) / 200 ) / 60 + } + + emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value())); + + #ifdef Q_OS_ANDROID + if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) + Heart = (uint8_t)KeepAwakeHelper::heart(); + else + #endif + { + if (Flags.heartRate && !disable_hr_frommachinery) { + if (index < newValue.length()) { + Heart = ((double)(((uint8_t)newValue.at(index)))); + // index += 1; //NOTE: clang-analyzer-deadcode.DeadStores + emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value())); + } else + emit debug(QStringLiteral("Error on parsing heart")); + } + } + + if (Flags.metabolic) { + + // todo + } + + if (Flags.elapsedTime) { + + // todo + } + + if (Flags.remainingTime) { + + // todo + } + + if (Cadence.value() > 0) { + + CrankRevs++; + LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0)); + } + + lastRefreshCharacteristicChanged = now; -#ifdef Q_OS_ANDROID - if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) - Heart = (uint8_t)KeepAwakeHelper::heart(); - else -#endif - { if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) { - Heart = ((uint8_t)newValue.at(18)); + update_hr_from_external(); } - } - CrankRevs++; - LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0)); - - emit debug(QStringLiteral("Current speed: ") + QString::number(speed)); - emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value())); - emit debug(QStringLiteral("Current resistance: ") + QString::number(Resistance.value())); - emit debug(QStringLiteral("Current inclination: ") + QString::number(Inclination.value())); - emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value())); - emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal)); - emit debug(QStringLiteral("Current Distance: ") + QString::number(distance)); - emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs)); - emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); - emit debug(QStringLiteral("Current Watt: ") + QString::number(watts())); - - if (m_control->error() != QLowEnergyController::NoError) { - qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString(); - } + #ifdef Q_OS_IOS + #ifndef IO_UNDER_QT + bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool(); + bool ios_peloton_workaround = + settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool(); + if (ios_peloton_workaround && cadence && h && firstVirtual) { + + h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime()); + h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate()); + } + #endif + #endif + + emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs)); + emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); - Speed = speed; - KCal = kcal; - Distance += ((Speed.value() / 3600000.0) * - ((double)lastRefreshCharacteristicChanged.msecsTo(now))); - lastRefreshCharacteristicChanged = now; + if (m_control->error() != QLowEnergyController::NoError) { + qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString(); + } + } } double domyosrower::GetSpeedFromPacket(const QByteArray &packet) { @@ -458,40 +691,133 @@ void domyosrower::btinit_telink(bool startTape) { } void domyosrower::stateChanged(QLowEnergyService::ServiceState state) { - QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3")); - QBluetoothUuid _gattNotifyCharacteristicId(QStringLiteral("49535343-1e4d-4bd9-ba61-23c647249616")); - - QMetaEnum metaEnum = QMetaEnum::fromType(); - emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state))); - - if (state == QLowEnergyService::ServiceDiscovered) { - - // qDebug() << gattCommunicationChannelService->characteristics(); - - gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId); - gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId); - Q_ASSERT(gattWriteCharacteristic.isValid()); - Q_ASSERT(gattNotifyCharacteristic.isValid()); - - // establish hook into notifications - connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this, - &domyosrower::characteristicChanged); - connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this, - &domyosrower::characteristicWritten); - connect(gattCommunicationChannelService, - static_cast(&QLowEnergyService::error), - this, &domyosrower::errorService); - connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this, - &domyosrower::descriptorWritten); - - QByteArray descriptor; - descriptor.append((char)0x01); - descriptor.append((char)0x00); - gattCommunicationChannelService->writeDescriptor( - gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor); + if(!ftmsRower) { + QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3")); + QBluetoothUuid _gattNotifyCharacteristicId(QStringLiteral("49535343-1e4d-4bd9-ba61-23c647249616")); + + QMetaEnum metaEnum = QMetaEnum::fromType(); + emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state))); + + if (state == QLowEnergyService::ServiceDiscovered) { + + // qDebug() << gattCommunicationChannelService->characteristics(); + + gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId); + gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId); + Q_ASSERT(gattWriteCharacteristic.isValid()); + Q_ASSERT(gattNotifyCharacteristic.isValid()); + + // establish hook into notifications + connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this, + &domyosrower::characteristicChanged); + connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this, + &domyosrower::characteristicWritten); + connect(gattCommunicationChannelService, + static_cast(&QLowEnergyService::error), + this, &domyosrower::errorService); + connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this, + &domyosrower::descriptorWritten); + + QByteArray descriptor; + descriptor.append((char)0x01); + descriptor.append((char)0x00); + gattCommunicationChannelService->writeDescriptor( + gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor); + } + } else { + QMetaEnum metaEnum = QMetaEnum::fromType(); + emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state))); + + for (QLowEnergyService *s : qAsConst(gattCommunicationChannelServiceArray)) { + qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state(); + if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) { + qDebug() << QStringLiteral("not all services discovered"); + + return; + } + } + + qDebug() << QStringLiteral("all services discovered!"); + + for (QLowEnergyService *s : qAsConst(gattCommunicationChannelServiceArray)) { + if (s->state() == QLowEnergyService::ServiceDiscovered) { + + // establish hook into notifications + connect(s, &QLowEnergyService::characteristicChanged, this, &domyosrower::characteristicChanged); + connect(s, &QLowEnergyService::characteristicWritten, this, &domyosrower::characteristicWritten); + connect(s, &QLowEnergyService::characteristicRead, this, &domyosrower::characteristicRead); + connect( + s, static_cast(&QLowEnergyService::error), + this, &domyosrower::errorService); + connect(s, &QLowEnergyService::descriptorWritten, this, &domyosrower::descriptorWritten); + connect(s, &QLowEnergyService::descriptorRead, this, &domyosrower::descriptorRead); + + qDebug() << s->serviceUuid() << QStringLiteral("connected!"); + + auto characteristics_list = s->characteristics(); + for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) { + qDebug() << "char uuid" << c.uuid() << QStringLiteral("handle") << c.handle(); + auto descriptors_list = c.descriptors(); + for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) { + qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle(); + } + + if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) { + + QByteArray descriptor; + descriptor.append((char)0x01); + descriptor.append((char)0x00); + if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) { + s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor); + } else { + qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid() + << c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid() + << c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle() + << QStringLiteral(" is not valid"); + } + + qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("notification subscribed!"); + } else if ((c.properties() & QLowEnergyCharacteristic::Indicate) == + QLowEnergyCharacteristic::Indicate) { + QByteArray descriptor; + descriptor.append((char)0x02); + descriptor.append((char)0x00); + if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) { + s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor); + } else { + qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid() + << c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid() + << c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle() + << QStringLiteral(" is not valid"); + } + + qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("indication subscribed!"); + } else if ((c.properties() & QLowEnergyCharacteristic::Read) == QLowEnergyCharacteristic::Read) { + // s->readCharacteristic(c); + // qDebug() << s->serviceUuid() << c.uuid() << "reading!"; + } + + QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9); + if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharControlPointId) { + qDebug() << QStringLiteral("FTMS service and Control Point found"); + + gattWriteCharControlPointId = c; + gattFTMSService = s; + } + } + } + } } } +void domyosrower::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) { + qDebug() << QStringLiteral("descriptorRead ") << descriptor.name() << descriptor.uuid() << newValue.toHex(' '); +} + +void domyosrower::characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) { + qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' '); +} + void domyosrower::searchingStop() { searchStopped = true; } void domyosrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) { @@ -512,8 +838,19 @@ void domyosrower::serviceScanDone(void) { QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455")); gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId); - connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosrower::stateChanged); - gattCommunicationChannelService->discoverDetails(); + if(gattCommunicationChannelService) { + connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyosrower::stateChanged); + gattCommunicationChannelService->discoverDetails(); + } else { + ftmsRower = true; + auto services_list = m_control->services(); + for (const QBluetoothUuid &s : qAsConst(services_list)) { + gattCommunicationChannelServiceArray.append(m_control->createServiceObject(s)); + connect(gattCommunicationChannelServiceArray.constLast(), &QLowEnergyService::stateChanged, this, + &domyosrower::stateChanged); + gattCommunicationChannelServiceArray.constLast()->discoverDetails(); + } + } } void domyosrower::errorService(QLowEnergyService::ServiceError err) { diff --git a/src/domyosrower.h b/src/domyosrower.h index 2d44d6608..e8f0e8619 100644 --- a/src/domyosrower.h +++ b/src/domyosrower.h @@ -28,6 +28,10 @@ #include "rower.h" +#ifdef Q_OS_IOS +#include "ios/lockscreen.h" +#endif + class domyosrower : public rower { Q_OBJECT public: @@ -58,11 +62,16 @@ class domyosrower : public rower { QLowEnergyCharacteristic gattWriteCharacteristic; QLowEnergyCharacteristic gattNotifyCharacteristic; + QList gattCommunicationChannelServiceArray; + QLowEnergyCharacteristic gattWriteCharControlPointId; + QLowEnergyService *gattFTMSService = nullptr; + bool initDone = false; bool initRequest = false; bool noWriteResistance = false; bool noHeartService = false; bool testResistance = false; + bool ftmsRower = false; uint8_t bikeResistanceOffset = 4; double bikeResistanceGain = 1.0; bool searchStopped = false; @@ -70,6 +79,13 @@ class domyosrower : public rower { QByteArray lastPacket; QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime(); + QDateTime lastStroke = QDateTime::currentDateTime(); + double lastStrokesCount = 0; + +#ifdef Q_OS_IOS + lockscreen *h = 0; +#endif + enum _BIKE_TYPE { CHANG_YOW, TELINK, @@ -86,6 +102,8 @@ class domyosrower : public rower { private slots: + void characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue); + void descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue); void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue); void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue); void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue); diff --git a/src/horizontreadmill.cpp b/src/horizontreadmill.cpp index 31e09303b..bbffa861c 100644 --- a/src/horizontreadmill.cpp +++ b/src/horizontreadmill.cpp @@ -1139,6 +1139,9 @@ void horizontreadmill::forceIncline(double requestIncline) { bool horizon_paragon_x = settings.value(QZSettings::horizon_paragon_x, QZSettings::default_horizon_paragon_x).toBool(); + if(tunturi_t60_treadmill) + Inclination = requestIncline; + if (gattCustomService) { if (!horizon_paragon_x) { messageID++; @@ -1488,9 +1491,10 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value())); if (Flags.inclination) { - Inclination = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | - (uint16_t)((uint8_t)newValue.at(index)))) / - 10.0; + if(!tunturi_t60_treadmill) + Inclination = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | + (uint16_t)((uint8_t)newValue.at(index)))) / + 10.0; index += 4; // the ramo value is useless emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value())); } @@ -1993,6 +1997,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) { } else if (device.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) { anplus_treadmill = true; qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!"); + } else if (device.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-"))) { + tunturi_t60_treadmill = true; + qDebug() << QStringLiteral("TUNTURI T60 TREADMILL workaround ON!"); } #ifdef Q_OS_IOS diff --git a/src/horizontreadmill.h b/src/horizontreadmill.h index a05c59938..1c8d12a74 100644 --- a/src/horizontreadmill.h +++ b/src/horizontreadmill.h @@ -89,6 +89,7 @@ class horizontreadmill : public treadmill { bool mobvoi_treadmill = false; bool kettler_treadmill = false; bool anplus_treadmill = false; + bool tunturi_t60_treadmill = false; void testProfileCRC(); void updateProfileCRC();