Skip to content

Commit

Permalink
Zwift Ride HID device (Issue #2886)
Browse files Browse the repository at this point in the history
  • Loading branch information
cagnulein committed Dec 9, 2024
1 parent 98cd3f2 commit 0eab69f
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/virtualdevices/virtualbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
}
}

services << QBluetoothUuid::HumanInterfaceDevice;

advertisingData.setServices(services);
//! [Advertising Data]

Expand Down Expand Up @@ -431,6 +433,38 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
serviceDataChanged.addCharacteristic(charData);
}

QLowEnergyCharacteristicData reportMapChar;
reportMapChar.setUuid(QBluetoothUuid::ReportMap);
reportMapChar.setValue(getHIDReportDescriptor());
reportMapChar.setProperties(QLowEnergyCharacteristic::Read);

// HID Information Characteristic
QLowEnergyCharacteristicData hidInfoChar;
hidInfoChar.setUuid(QBluetoothUuid::HidInformation);
QByteArray hidInfo;
hidInfo.append((char)0x11); // HID version 1.1
hidInfo.append((char)0x01); // Country code
hidInfo.append((char)0x00); // Flags
hidInfoChar.setValue(hidInfo);
hidInfoChar.setProperties(QLowEnergyCharacteristic::Read);

// Report Characteristic
QLowEnergyCharacteristicData reportChar;
reportChar.setUuid(QBluetoothUuid::Report);
reportChar.setProperties(QLowEnergyCharacteristic::Notify | QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::ClientCharacteristicConfiguration, descriptor);
reportChar.addDescriptor(clientConfig);

// Configure service
serviceDataHID.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceDataHID.setUuid(QBluetoothUuid::HumanInterfaceDevice);
serviceDataHID.addCharacteristic(reportMapChar);
serviceDataHID.addCharacteristic(hidInfoChar);
serviceDataHID.addCharacteristic(reportChar);

//! [Start Advertising]
leController = QLowEnergyController::createPeripheral();
Q_ASSERT(leController);
Expand Down Expand Up @@ -473,6 +507,9 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
serviceHR = leController->addService(serviceDataHR);
}

QThread::msleep(100); // give time to Android to add the service async.ly
serviceHID = leController->addService(serviceDataHID);

if (!echelon && !ifit) {
if (!heart_only) {
if (!cadence && !power) {
Expand All @@ -482,6 +519,8 @@ virtualbike::virtualbike(bluetoothdevice *t, bool noWriteResistance, bool noHear
&virtualbike::characteristicChanged);
QObject::connect(serviceZwiftPlayBike, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
QObject::connect(serviceHID, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
} else {
QObject::connect(service, &QLowEnergyService::characteristicChanged, this,
&virtualbike::characteristicChanged);
Expand Down Expand Up @@ -1345,6 +1384,9 @@ void virtualbike::reconnect() {

if (!this->noHeartService || heart_only)
serviceHR = leController->addService(serviceDataHR);

QThread::msleep(100); // give time to Android to add the service async.ly
serviceHID = leController->addService(serviceDataHID);
#endif

QLowEnergyAdvertisingParameters pars;
Expand Down Expand Up @@ -1373,6 +1415,8 @@ void virtualbike::bikeProvider() {

uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);

sendMouseReport(10, 0);

#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
if (h) {
Expand Down
61 changes: 61 additions & 0 deletions src/virtualdevices/virtualbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ class virtualbike : public virtualdevice {
return lastDirconFTMSFrameReceived;
}

// Function to send mouse input reports
void sendMouseReport(int8_t x, int8_t y, uint8_t buttons = 0, int8_t wheel = 0) {
if (!serviceHID || !hidEnabled || leController->state() != QLowEnergyController::ConnectedState) {
qDebug() << "HID service not available or not connected";
return;
}

QByteArray report;
report.append(buttons); // Button state
report.append(x); // X movement
report.append(y); // Y movement
report.append(wheel); // Wheel movement

// Find the report characteristic
QLowEnergyCharacteristic reportChar = serviceHID->characteristic(QBluetoothUuid(quint16(0x2A4D)));
if (!reportChar.isValid()) {
qDebug() << "HID report characteristic not found";
return;
}

writeCharacteristic(serviceHID, reportChar, report);
}

private:
QLowEnergyController *leController = nullptr;
QLowEnergyService *serviceHR = nullptr;
Expand Down Expand Up @@ -106,6 +129,44 @@ class virtualbike : public virtualdevice {
};
VarintResult decodeVarint(const QByteArray& bytes, int startIndex);
qint32 decodeSInt(const QByteArray& bytes);

QLowEnergyServiceData serviceDataHID;
QLowEnergyService* serviceHID = nullptr;
bool hidEnabled = false;

// HID report descriptor for mouse
QByteArray getHIDReportDescriptor() {
static const char reportDesc[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
static_cast<char>(0xA1), 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
static_cast<char>(0xA1), 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x03, // Usage Maximum (3)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
static_cast<char>(0x95), 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
static_cast<char>(0x81), 0x02, // Input (Data, Variable, Absolute)
static_cast<char>(0x95), 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
static_cast<char>(0x81), 0x01, // Input (Constant)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
0x15, static_cast<char>(0x81), // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
static_cast<char>(0x95), 0x03, // Report Count (3)
static_cast<char>(0x81), 0x06, // Input (Data, Variable, Relative)
static_cast<char>(0xC0), // End Collection
static_cast<char>(0xC0) // End Collection
};
return QByteArray(reportDesc, sizeof(reportDesc));
}

signals:
void changeInclination(double grade, double percentage);
Expand Down

0 comments on commit 0eab69f

Please sign in to comment.