Compare commits

...

7 Commits

Author SHA1 Message Date
Roberto Viola
802d6f58fa Update project.pbxproj 2026-01-30 15:13:49 +01:00
Roberto Viola
35115da097 Merge branch 'claude/add-thinkrider-device-7YXzY' of https://github.com/cagnulein/qdomyos-zwift into claude/add-thinkrider-device-7YXzY 2026-01-30 14:13:06 +00:00
Roberto Viola
c859a3e3af Update project.pbxproj 2026-01-30 15:05:35 +01:00
Roberto Viola
a8f1fc076b Update project.pbxproj 2026-01-30 14:48:16 +01:00
Roberto Viola
99dac32de4 Merge branch 'master' into claude/add-thinkrider-device-7YXzY 2026-01-30 13:13:47 +00:00
Claude
c770ab6b80 Update allSettingsCount to 857 for thinkrider_controller setting
https://claude.ai/code/session_01DK5qQY9wKyHTKfYhAkGECS
2026-01-29 17:19:10 +00:00
Claude
89cd6b93e9 Add Thinkrider VS200 controller support for gear shifting
Implements support for the Thinkrider VS200 remote controller,
enabling gear up/down functionality similar to Zwift Click.
Uses service UUID 0000fea0 and detects button patterns for
shift up (f3050301fc) and shift down (f3050300fb).

https://claude.ai/code/session_01DK5qQY9wKyHTKfYhAkGECS
2026-01-29 16:53:03 +00:00
9 changed files with 372 additions and 9 deletions

View File

@@ -557,6 +557,8 @@
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */; };
87DAE16A26E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */; };
87DAE16B26E9FF5000B0527E /* moc_solef80treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */; };
87DBD5DB2F2CEE1900342F2B /* thinkridercontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DBD5DA2F2CEE1900342F2B /* thinkridercontroller.cpp */; };
87DBD5ED2F2CF22100342F2B /* moc_thinkridercontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DBD5EC2F2CF22100342F2B /* moc_thinkridercontroller.cpp */; };
87DC27EA2D9BDB53007A1B9D /* echelonstairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */; };
87DC27EB2D9BDB53007A1B9D /* stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */; };
87DC27EE2D9BDB8F007A1B9D /* moc_stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */; };
@@ -1660,6 +1662,9 @@
87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_shuaa5treadmill.cpp; sourceTree = "<group>"; };
87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr2treadmill.cpp; sourceTree = "<group>"; };
87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_solef80treadmill.cpp; sourceTree = "<group>"; };
87DBD5D92F2CEE1900342F2B /* thinkridercontroller.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = thinkridercontroller.h; path = ../src/devices/thinkridercontroller/thinkridercontroller.h; sourceTree = SOURCE_ROOT; };
87DBD5DA2F2CEE1900342F2B /* thinkridercontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = thinkridercontroller.cpp; path = ../src/devices/thinkridercontroller/thinkridercontroller.cpp; sourceTree = SOURCE_ROOT; };
87DBD5EC2F2CF22100342F2B /* moc_thinkridercontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_thinkridercontroller.cpp; sourceTree = "<group>"; };
87DC27E62D9BDB53007A1B9D /* echelonstairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = echelonstairclimber.h; path = ../src/devices/echelonstairclimber/echelonstairclimber.h; sourceTree = SOURCE_ROOT; };
87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = echelonstairclimber.cpp; path = ../src/devices/echelonstairclimber/echelonstairclimber.cpp; sourceTree = SOURCE_ROOT; };
87DC27E82D9BDB53007A1B9D /* stairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stairclimber.h; path = ../src/devices/stairclimber.h; sourceTree = SOURCE_ROOT; };
@@ -2335,6 +2340,9 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87DBD5EC2F2CF22100342F2B /* moc_thinkridercontroller.cpp */,
87DBD5D92F2CEE1900342F2B /* thinkridercontroller.h */,
87DBD5DA2F2CEE1900342F2B /* thinkridercontroller.cpp */,
87A892572F0C173600811D95 /* sportsplusrower.cpp */,
87A892552F0C12EB00811D95 /* deerruntreadmill.cpp */,
87CBCF0F2EFAA2F8004F5ECE /* garminconnect.h */,
@@ -3892,6 +3900,7 @@
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */,
8718CBAC263063CE004BF4EE /* moc_tcpclientinfosender.cpp in Compile Sources */,
873824B527E64707004F1B46 /* moc_provider_p.cpp in Compile Sources */,
87DBD5ED2F2CF22100342F2B /* moc_thinkridercontroller.cpp in Compile Sources */,
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */,
333C629F93DB3941862924F7 /* fit_field_base.cpp in Compile Sources */,
87473A9827ECAA0500C203F5 /* moc_proformrower.cpp in Compile Sources */,
@@ -4198,6 +4207,7 @@
874D272029AFA11F0007C079 /* apexbike.cpp in Compile Sources */,
8798C8872733E103003148B3 /* strydrunpowersensor.cpp in Compile Sources */,
87C5F0B626285E5F0067A1B5 /* quotedprintable.cpp in Compile Sources */,
87DBD5DB2F2CEE1900342F2B /* thinkridercontroller.cpp in Compile Sources */,
87310B23266FBB78008BA0D6 /* moc_smartrowrower.cpp in Compile Sources */,
EE29228550794460E7654533 /* moc_trxappgateusbtreadmill.cpp in Compile Sources */,
3DB7B5F0CE1E2390CEFFC1E8 /* moc_virtualbike.cpp in Compile Sources */,
@@ -4573,7 +4583,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4774,7 +4784,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -5011,7 +5021,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -5107,7 +5117,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5199,7 +5209,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5315,7 +5325,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5425,7 +5435,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -5516,7 +5526,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1275;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;

View File

@@ -3115,6 +3115,24 @@ void bluetooth::connectedAndDiscovered() {
}
}
if(settings.value(QZSettings::thinkrider_controller, QZSettings::default_thinkrider_controller).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("VS")) || (b.name().toUpper().startsWith("THINKRIDER"))) && !thinkriderController && this->device() &&
this->device()->deviceType() == BIKE) {
thinkriderController = new thinkridercontroller(this->device());
connect(thinkriderController, &thinkridercontroller::debug, this, &bluetooth::debug);
connect(thinkriderController, &thinkridercontroller::plus, (bike*)this->device(), &bike::gearUp);
connect(thinkriderController, &thinkridercontroller::minus, (bike*)this->device(), &bike::gearDown);
thinkriderController->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Thinkrider Controller Connected!");
break;
}
}
}
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("SQUARE"))) && !eliteSquareController && this->device() &&

View File

@@ -154,6 +154,7 @@
#include "zwift_play/zwiftPlayDevice.h"
#include "zwift_play/zwiftclickremote.h"
#include "devices/thinkridercontroller/thinkridercontroller.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
@@ -306,6 +307,7 @@ class bluetooth : public QObject, public SignalHandler {
QList<eliteariafan *> eliteAriaFan;
QList<zwiftclickremote* > zwiftPlayDevice;
zwiftclickremote* zwiftClickRemote = nullptr;
thinkridercontroller* thinkriderController = nullptr;
sramaxscontroller* sramAXSController = nullptr;
elitesquarecontroller* eliteSquareController = nullptr;
QString filterDevice = QLatin1String("");

View File

@@ -0,0 +1,225 @@
#include "thinkridercontroller.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
using namespace std::chrono_literals;
// Thinkrider VS200 UUIDs
const QBluetoothUuid thinkridercontroller::SERVICE_UUID =
QBluetoothUuid(QStringLiteral("0000fea0-0000-1000-8000-00805f9b34fb"));
const QBluetoothUuid thinkridercontroller::CHARACTERISTIC_UUID =
QBluetoothUuid(QStringLiteral("0000fea1-0000-1000-8000-00805f9b34fb"));
// Button patterns (from swiftcontrol implementation)
const QByteArray thinkridercontroller::SHIFT_UP_PATTERN = QByteArray::fromHex("f3050301fc");
const QByteArray thinkridercontroller::SHIFT_DOWN_PATTERN = QByteArray::fromHex("f3050300fb");
thinkridercontroller::thinkridercontroller(bluetoothdevice *parentDevice) {
this->parentDevice = parentDevice;
}
void thinkridercontroller::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void thinkridercontroller::disconnectBluetooth() {
qDebug() << QStringLiteral("thinkridercontroller::disconnect") << m_control;
if (m_control) {
m_control->disconnectFromDevice();
}
}
void thinkridercontroller::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("thinkridercontroller << ") << newValue.toHex(' ');
// Check for shift up pattern
if (newValue == SHIFT_UP_PATTERN) {
qDebug() << QStringLiteral("Thinkrider: Shift UP detected");
emit plus();
}
// Check for shift down pattern
else if (newValue == SHIFT_DOWN_PATTERN) {
qDebug() << QStringLiteral("Thinkrider: Shift DOWN detected");
emit minus();
}
}
void thinkridercontroller::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
}
}
if (state != QLowEnergyService::ServiceState::ServiceDiscovered) {
qDebug() << QStringLiteral("ignoring this state");
return;
}
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
if (s->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
connect(s, &QLowEnergyService::characteristicChanged, this, &thinkridercontroller::characteristicChanged);
connect(s, &QLowEnergyService::characteristicRead, this, &thinkridercontroller::characteristicChanged);
connect(
s, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &thinkridercontroller::errorService);
connect(s, &QLowEnergyService::descriptorWritten, this, &thinkridercontroller::descriptorWritten);
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("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!");
}
if (c.uuid() == CHARACTERISTIC_UUID) {
qDebug() << QStringLiteral("Thinkrider characteristic found");
gattNotifyCharacteristic = c;
}
}
}
}
initDone = true;
}
void thinkridercontroller::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
}
void thinkridercontroller::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
gattCommunicationChannelService.append(m_control->createServiceObject(s));
if (gattCommunicationChannelService.constLast()) {
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&thinkridercontroller::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
} else {
m_control->disconnectFromDevice();
}
}
}
void thinkridercontroller::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("thinkridercontroller::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void thinkridercontroller::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("thinkridercontroller::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void thinkridercontroller::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &thinkridercontroller::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &thinkridercontroller::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &thinkridercontroller::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &thinkridercontroller::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
emit debug(QStringLiteral("Cannot connect to remote device."));
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Controller connected. Search services..."));
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("LowEnergy controller disconnected"));
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool thinkridercontroller::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void thinkridercontroller::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
if (m_control)
m_control->connectToDevice();
}
}

View File

@@ -0,0 +1,73 @@
#ifndef THINKRIDERCONTROLLER_H
#define THINKRIDERCONTROLLER_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 <QObject>
#include <QTime>
#include "devices/bluetoothdevice.h"
class thinkridercontroller : public bluetoothdevice {
Q_OBJECT
public:
thinkridercontroller(bluetoothdevice *parentDevice);
bool connected() override;
private:
// Thinkrider VS200 UUIDs
static const QBluetoothUuid SERVICE_UUID;
static const QBluetoothUuid CHARACTERISTIC_UUID;
// Button patterns
static const QByteArray SHIFT_UP_PATTERN;
static const QByteArray SHIFT_DOWN_PATTERN;
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattNotifyCharacteristic;
bluetoothdevice *parentDevice = nullptr;
bool initDone = false;
signals:
void disconnected();
void debug(QString string);
void plus();
void minus();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void disconnectBluetooth();
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void controllerStateChanged(QLowEnergyController::ControllerState state);
private slots:
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // THINKRIDERCONTROLLER_H

View File

@@ -103,6 +103,7 @@ SOURCES += \
$$PWD/devices/sportsplusrower/sportsplusrower.cpp \
$$PWD/devices/sportstechelliptical/sportstechelliptical.cpp \
$$PWD/devices/sramAXSController/sramAXSController.cpp \
$$PWD/devices/thinkridercontroller/thinkridercontroller.cpp \
$$PWD/devices/stairclimber.cpp \
$$PWD/devices/echelonstairclimber/echelonstairclimber.cpp \
$$PWD/devices/technogymbike/technogymbike.cpp \
@@ -380,6 +381,7 @@ HEADERS += \
$$PWD/devices/sportsplusrower/sportsplusrower.h \
$$PWD/devices/sportstechelliptical/sportstechelliptical.h \
$$PWD/devices/sramAXSController/sramAXSController.h \
$$PWD/devices/thinkridercontroller/thinkridercontroller.h \
$$PWD/devices/stairclimber.h \
$$PWD/devices/technogymbike/technogymbike.h \
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \

View File

@@ -776,6 +776,7 @@ const QString QZSettings::proform_treadmill_505_cst = QStringLiteral("proform_tr
const QString QZSettings::nordictrack_treadmill_t8_5s = QStringLiteral("nordictrack_treadmill_t8_5s");
const QString QZSettings::proform_treadmill_705_cst = QStringLiteral("proform_treadmill_705_cst");
const QString QZSettings::zwift_click = QStringLiteral("zwift_click");
const QString QZSettings::thinkrider_controller = QStringLiteral("thinkrider_controller");
const QString QZSettings::hop_sport_hs_090h_bike = QStringLiteral("hop_sport_hs_090h_bike");
const QString QZSettings::zwift_play = QStringLiteral("zwift_play");
const QString QZSettings::zwift_play_vibration = QStringLiteral("zwift_play_vibration");
@@ -1050,7 +1051,7 @@ const QString QZSettings::trainprogram_auto_lap_on_segment = QStringLiteral("tra
const QString QZSettings::kingsmith_r2_enable_hw_buttons = QStringLiteral("kingsmith_r2_enable_hw_buttons");
const uint32_t allSettingsCount = 856;
const uint32_t allSettingsCount = 857;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1696,6 +1697,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::nordictrack_treadmill_t8_5s, QZSettings::default_nordictrack_treadmill_t8_5s},
{QZSettings::proform_treadmill_705_cst, QZSettings::default_proform_treadmill_705_cst},
{QZSettings::zwift_click, QZSettings::default_zwift_click},
{QZSettings::thinkrider_controller, QZSettings::default_thinkrider_controller},
{QZSettings::hop_sport_hs_090h_bike, QZSettings::default_hop_sport_hs_090h_bike},
{QZSettings::zwift_play, QZSettings::default_zwift_play},
{QZSettings::zwift_play_vibration, QZSettings::default_zwift_play_vibration},

View File

@@ -2140,6 +2140,9 @@ class QZSettings {
static const QString zwift_click;
static constexpr bool default_zwift_click = false;
static const QString thinkrider_controller;
static constexpr bool default_thinkrider_controller = false;
static const QString proform_treadmill_705_cst;
static constexpr bool default_proform_treadmill_705_cst = false;

View File

@@ -1273,6 +1273,7 @@ import Qt.labs.platform 1.1
property bool kingsmith_r2_enable_hw_buttons: false
property bool treadmill_direct_distance: false
property bool domyos_treadmill_ts100: false
property bool thinkrider_controller: false
}
@@ -12779,6 +12780,33 @@ import Qt.labs.platform 1.1
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Thinkrider Controller")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.thinkrider_controller
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.thinkrider_controller = checked; window.settings_restart_to_apply = true; }
}
Label {
text: qsTr("Thinkrider VS200 remote controller. Use it to change gears on QZ!")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Zwift Play")
spacing: 0