Compare commits

...

3 Commits

Author SHA1 Message Date
Roberto Viola
6ba867a1b5 Mobi Rower 2026-01-30 21:31:29 +01:00
Roberto Viola
72f57053a7 Update project.pbxproj 2026-01-30 09:37:13 +01:00
Roberto Viola
13ea5313b1 Add D500V2 workaround for FTMS start simulation command
Implements a workaround for D500V2 bikes that require a START_RESUME (0x07) command before accepting simulation parameters (0x11). The code now tracks the command sequence and injects the necessary command if missing, ensuring compatibility with D500V2 models. Also adds detection and flag for D500V2 during device discovery.
2026-01-30 09:23:31 +01:00
8 changed files with 493 additions and 8 deletions

View File

@@ -4573,7 +4573,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4774,7 +4774,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -5011,7 +5011,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -5107,7 +5107,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5199,7 +5199,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5315,7 +5315,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5425,7 +5425,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -5516,7 +5516,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1273;
CURRENT_PROJECT_VERSION = 1274;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;

View File

@@ -2094,6 +2094,15 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(lifespanTreadmill, &lifespantreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
lifespanTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(lifespanTreadmill);
} else if (b.name().startsWith(QStringLiteral("AT-R")) && !mobiRower && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
mobiRower = new mobirower(noWriteResistance, noHeartService);
emit deviceConnected(b);
connect(mobiRower, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
mobiRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(mobiRower);
} else if ((b.name().toUpper().startsWith(QStringLiteral("ECH-ROW")) ||
b.name().toUpper().startsWith(QStringLiteral("ROWSPORT")) ||
b.name().toUpper().startsWith(QStringLiteral("ROW-S"))) &&
@@ -3605,6 +3614,11 @@ void bluetooth::restart() {
delete echelonRower;
echelonRower = nullptr;
}
if (mobiRower) {
delete mobiRower;
mobiRower = nullptr;
}
if (echelonStride) {
delete echelonStride;
@@ -4040,6 +4054,8 @@ bluetoothdevice *bluetooth::device() {
return echelonConnectSport;
} else if (echelonRower) {
return echelonRower;
} else if (mobiRower) {
return mobiRower;
} else if (echelonStride) {
return echelonStride;
} else if (echelonStairclimber) {

View File

@@ -154,6 +154,7 @@
#include "zwift_play/zwiftPlayDevice.h"
#include "zwift_play/zwiftclickremote.h"
#include "devices/mobirower/mobirower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
@@ -269,6 +270,7 @@ class bluetooth : public QObject, public SignalHandler {
echelonrower *echelonRower = nullptr;
ftmsrower *ftmsRower = nullptr;
smartrowrower *smartrowRower = nullptr;
mobirower *mobiRower = nullptr;
echelonstride *echelonStride = nullptr;
echelonstairclimber *echelonStairclimber = nullptr;
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;

View File

@@ -1477,6 +1477,29 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
if (gattWriteCharControlPointId.isValid()) {
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
// D500V2 workaround: track request control (0x00) and start simulation (0x07) commands
// If we receive simulation params (0x11) without start simulation, inject it first
if (D500V2 && b.length() > 0) {
uint8_t commandCode = (uint8_t)b.at(0);
if (commandCode == FTMS_REQUEST_CONTROL) {
// Command 0x00: Request Control - expect start simulation next
awaiting_start_simulation_after_request_control = true;
qDebug() << "D500V2 workaround: received REQUEST_CONTROL (0x00), now awaiting START_RESUME (0x07)";
} else if (commandCode == FTMS_START_RESUME) {
// Command 0x07: Start Resume - no longer awaiting
awaiting_start_simulation_after_request_control = false;
qDebug() << "D500V2 workaround: received START_RESUME (0x07), ready for simulation params";
} else if (commandCode == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && D500V2 && awaiting_start_simulation_after_request_control) {
// Command 0x11: Set Simulation Params - but we're still awaiting start simulation
// For D500V2, inject the start simulation command (0x07) first
qDebug() << "D500V2 workaround: received SET_INDOOR_BIKE_SIMULATION_PARAMS (0x11) without START_RESUME, injecting 0x07 first";
uint8_t startSimulation[] = {FTMS_START_RESUME};
writeCharacteristic(startSimulation, sizeof(startSimulation), "injectWrite [D500V2 workaround: start simulation 0x07]", false, true);
awaiting_start_simulation_after_request_control = false;
}
}
// handling gears
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && (zwiftPlayService == nullptr || !gears_zwift_ratio)) {
double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble();
@@ -1684,6 +1707,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
ergModeSupported = false;
max_resistance = 32;
DOMYOS = true;
} else if (bluetoothDevice.name().toUpper().startsWith("D500V2")) {
qDebug() << QStringLiteral("D500V2 found - enabling workaround for start simulation command");
D500V2 = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("3G Cardio RB"))) {
qDebug() << QStringLiteral("_3G_Cardio_RB found");
_3G_Cardio_RB = true;

View File

@@ -135,9 +135,13 @@ class ftmsbike : public bike {
bool resistance_received = false;
inclinationResistanceTable _inclinationResistanceTable;
// D500V2 workaround: track if we're awaiting start simulation command after request control
bool awaiting_start_simulation_after_request_control = false;
bool DU30_bike = false;
bool ICSE = false;
bool DOMYOS = false;
bool D500V2 = false;
bool _3G_Cardio_RB = false;
bool SCH_190U = false;
bool SCH_290R = false;

View File

@@ -0,0 +1,353 @@
#include "mobirower.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualrower.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
mobirower::mobirower(bool noWriteResistance, bool noHeartService) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT, deviceType());
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, &QTimer::timeout, this, &mobirower::update);
refresh->start(200ms);
}
void mobirower::update() {
if (m_control == nullptr)
return;
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattNotifyCharacteristic.isValid() && initDone) {
update_metrics(true, watts());
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
qDebug() << QStringLiteral("stopping...");
requestStop = -1;
}
}
}
void mobirower::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
void mobirower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
qDebug() << QStringLiteral(" << ") + newValue.toHex(' ');
// Validate packet: 13 bytes, starts with 0xab 0x04
if (newValue.length() < 13 ||
(uint8_t)newValue.at(0) != 0xab ||
(uint8_t)newValue.at(1) != 0x04) {
qDebug() << QStringLiteral("Invalid packet format");
return;
}
// Parse power from bytes 9-10 (big-endian uint16)
uint16_t power = ((uint8_t)newValue.at(9) << 8) | (uint8_t)newValue.at(10);
// Parse stroke count from bytes 11-12 (big-endian uint16)
uint16_t strokeCount = ((uint8_t)newValue.at(11) << 8) | (uint8_t)newValue.at(12);
// Calculate cadence from stroke delta
double timeDelta = lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime());
if (timeDelta > 0 && strokeCount >= lastStrokeCount) {
uint16_t strokeDelta = strokeCount - lastStrokeCount;
// Convert to strokes per minute (SPM)
double cadence = (strokeDelta / (timeDelta / 60000.0));
if (cadence < 200) { // sanity check
Cadence = cadence;
}
}
lastStrokeCount = strokeCount;
m_watt = power;
StrokesCount = strokeCount;
// Calculate speed from strokes (standard rower formula)
// Using a simplified formula: speed in km/h derived from cadence
if (Cadence.value() > 0) {
// Typical rower: ~10m per stroke at normal pace
// Speed = (cadence * meters_per_stroke * 60) / 1000 for km/h
double metersPerStroke = 8.0; // approximate
Speed = (Cadence.value() * metersPerStroke * 60.0) / 1000.0;
} else {
Speed = 0;
}
StrokesLength =
((Speed.value() / 60.0) * 1000.0) /
Cadence.value(); // this is just to fill the tile
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(
QDateTime::currentDateTime()))));
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#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"))) {
update_hr_from_external();
}
}
#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();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
if (ios_peloton_workaround && cadence && !virtual_device_rower && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
qDebug() << QStringLiteral("Current Power: ") + QString::number(m_watt.value());
qDebug() << QStringLiteral("Current Stroke Count: ") + QString::number(StrokesCount.value());
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
void mobirower::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
// Find the notify characteristic (0xffe4)
QBluetoothUuid notifyCharUuid((quint16)0xffe4);
gattNotifyCharacteristic = gattCommunicationChannelService->characteristic(notifyCharUuid);
if (!gattNotifyCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattNotifyCharacteristic not valid, trying to find by properties");
auto characteristics_list = gattCommunicationChannelService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("c -> ") << c.uuid() << c.properties();
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
gattNotifyCharacteristic = c;
break;
}
}
}
if (!gattNotifyCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattNotifyCharacteristic still not valid");
return;
}
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&mobirower::characteristicChanged);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &mobirower::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&mobirower::descriptorWritten);
// ******************************************* virtual bike/rower init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).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 && !virtual_device_rower) {
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 bike interface...");
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
qDebug() << QStringLiteral("creating virtual rower interface...");
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
}
firstStateChanged = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void mobirower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' ');
initDone = true;
emit connectedAndDiscovered();
}
void mobirower::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
// Service UUID 0xffe0
QBluetoothUuid serviceUuid((quint16)0xffe0);
gattCommunicationChannelService = m_control->createServiceObject(serviceUuid);
if (!gattCommunicationChannelService) {
qDebug() << "service 0xffe0 not found, trying to find any service";
auto services = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services)) {
qDebug() << QStringLiteral("service ") << s.toString();
}
if (!services.isEmpty()) {
gattCommunicationChannelService = m_control->createServiceObject(services.first());
}
}
if (!gattCommunicationChannelService) {
qDebug() << "no service found";
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &mobirower::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void mobirower::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("mobirower::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void mobirower::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << "mobirower::error" + QString::fromLocal8Bit(metaEnum.valueToKey(err)) + m_control->errorString();
}
void mobirower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << "Found new device: " + device.name() + " (" + device.address().toString() + ')';
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &mobirower::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &mobirower::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &mobirower::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &mobirower::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
bool mobirower::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
uint16_t mobirower::watts() {
return m_watt.value();
}
void mobirower::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}

View File

@@ -0,0 +1,82 @@
#ifndef MOBIROWER_H
#define MOBIROWER_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "rower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class mobirower : public rower {
Q_OBJECT
public:
mobirower(bool noWriteResistance, bool noHeartService);
bool connected() override;
private:
void startDiscover();
uint16_t watts() override;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattNotifyCharacteristic;
uint8_t firstStateChanged = 0;
uint16_t lastStrokeCount = 0;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
bool initDone = false;
bool noWriteResistance = false;
bool noHeartService = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // MOBIROWER_H

View File

@@ -101,6 +101,7 @@ SOURCES += \
$$PWD/devices/pitpatbike/pitpatbike.cpp \
$$PWD/devices/speraxtreadmill/speraxtreadmill.cpp \
$$PWD/devices/sportsplusrower/sportsplusrower.cpp \
$$PWD/devices/mobirower/mobirower.cpp \
$$PWD/devices/sportstechelliptical/sportstechelliptical.cpp \
$$PWD/devices/sramAXSController/sramAXSController.cpp \
$$PWD/devices/stairclimber.cpp \
@@ -378,6 +379,7 @@ HEADERS += \
$$PWD/devices/pitpatbike/pitpatbike.h \
$$PWD/devices/speraxtreadmill/speraxtreadmill.h \
$$PWD/devices/sportsplusrower/sportsplusrower.h \
$$PWD/devices/mobirower/mobirower.h \
$$PWD/devices/sportstechelliptical/sportstechelliptical.h \
$$PWD/devices/sramAXSController/sramAXSController.h \
$$PWD/devices/stairclimber.h \