diff --git a/src/devices/bike.cpp b/src/devices/bike.cpp index 033eb3b45..58b18bd08 100644 --- a/src/devices/bike.cpp +++ b/src/devices/bike.cpp @@ -418,3 +418,20 @@ double bike::gearsZwiftRatio() { } return 1; } + + +void bike::chainRingUp() { + setGears(gearTable.chainRingUp(m_gears)); +} + +void bike::chainRingDown() { + setGears(gearTable.chainRingDown(m_gears)); +} + +void bike::cassetteUp() { + setGears(gearTable.cassetteUp(m_gears)); +} + +void bike::cassetteDown() { + setGears(gearTable.cassetteDown(m_gears)); +} diff --git a/src/devices/bike.h b/src/devices/bike.h index 4adab4b95..8d0a33e50 100644 --- a/src/devices/bike.h +++ b/src/devices/bike.h @@ -3,6 +3,7 @@ #include "devices/bluetoothdevice.h" #include "virtualdevices/virtualbike.h" +#include "wheelcircumference.h" #include class bike : public bluetoothdevice { @@ -43,6 +44,7 @@ class bike : public bluetoothdevice { void setSpeedLimit(double speed) { m_speedLimit = speed; } double speedLimit() { return m_speedLimit; } virtual bool ifitCompatible() {return false;} + wheelCircumference::GearTable gearTable; /** * @brief currentSteeringAngle Gets a metric object to get or set the current steering angle @@ -75,6 +77,10 @@ class bike : public bluetoothdevice { setGears(gears() - (gears_zwift_ratio ? 1 : settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble())); } + void chainRingUp(); + void chainRingDown(); + void cassetteUp(); + void cassetteDown(); Q_SIGNALS: void bikeStarted(); diff --git a/src/devices/bluetooth.cpp b/src/devices/bluetooth.cpp index be4f5ad3d..144a114f1 100644 --- a/src/devices/bluetooth.cpp +++ b/src/devices/bluetooth.cpp @@ -2875,6 +2875,10 @@ void bluetooth::connectedAndDiscovered() { connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug); connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp); connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown); + connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::chainRingUp, (bike*)this->device(), &bike::chainRingUp); + connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::chainRingDown, (bike*)this->device(), &bike::chainRingDown); + connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::cassetteUp, (bike*)this->device(), &bike::cassetteUp); + connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::cassetteDown, (bike*)this->device(), &bike::cassetteDown); if((zwiftPlayDevice.last()->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) || (zwiftPlayDevice.last()->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) { connect((bike*)this->device(), &bike::gearOkUp, this, &bluetooth::gearUp); diff --git a/src/devices/ftmsbike/ftmsbike.cpp b/src/devices/ftmsbike/ftmsbike.cpp index 1730e3237..eb2caea41 100644 --- a/src/devices/ftmsbike/ftmsbike.cpp +++ b/src/devices/ftmsbike/ftmsbike.cpp @@ -15,7 +15,6 @@ #include "keepawakehelper.h" #endif #include -#include "wheelcircumference.h" #ifdef Q_OS_IOS extern quint8 QZ_EnableDiscoveryCharsAndDescripttors; @@ -36,8 +35,7 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis initDone = false; connect(refresh, &QTimer::timeout, this, &ftmsbike::update); refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt()); - wheelCircumference::GearTable g; - g.printTable(); + gearTable.printTable(); } void ftmsbike::writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log, @@ -327,8 +325,7 @@ void ftmsbike::update() { if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) { QSettings settings; - wheelCircumference::GearTable table; - wheelCircumference::GearTable::GearInfo g = table.getGear((int)gears()); + wheelCircumference::GearTable::GearInfo g = gearTable.getGear((int)gears()); double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) / ((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble()); @@ -1329,7 +1326,7 @@ double ftmsbike::maxGears() { if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio) { wheelCircumference::GearTable g; - return g.maxGears; + return gearTable.maxGears; } else if(WATTBIKE) { return 22; } else { diff --git a/src/devices/ftmsbike/ftmsbike.h b/src/devices/ftmsbike/ftmsbike.h index 2f2bee3bc..5fe8e5784 100644 --- a/src/devices/ftmsbike/ftmsbike.h +++ b/src/devices/ftmsbike/ftmsbike.h @@ -26,7 +26,6 @@ #include #include -#include "wheelcircumference.h" #include "devices/bike.h" #ifdef Q_OS_IOS diff --git a/src/devices/technogymbike/technogymbike.cpp b/src/devices/technogymbike/technogymbike.cpp index d862aa71b..fa4fde900 100644 --- a/src/devices/technogymbike/technogymbike.cpp +++ b/src/devices/technogymbike/technogymbike.cpp @@ -36,8 +36,6 @@ technogymbike::technogymbike(bool noWriteResistance, bool noHeartService, int8_t initDone = false; connect(refresh, &QTimer::timeout, this, &technogymbike::update); refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt()); - wheelCircumference::GearTable g; - g.printTable(); } bool technogymbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log, diff --git a/src/devices/technogymbike/technogymbike.h b/src/devices/technogymbike/technogymbike.h index e9b5003dd..56e4275c3 100644 --- a/src/devices/technogymbike/technogymbike.h +++ b/src/devices/technogymbike/technogymbike.h @@ -26,7 +26,6 @@ #include #include -#include "wheelcircumference.h" #include "devices/bike.h" #ifdef Q_OS_IOS diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp index 4883a55d1..a04d87b5a 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp @@ -32,8 +32,7 @@ wahookickrsnapbike::wahookickrsnapbike(bool noWriteResistance, bool noHeartServi connect(refresh, &QTimer::timeout, this, &wahookickrsnapbike::update); QSettings settings; refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt()); - wheelCircumference::GearTable g; - g.printTable(); + gearTable.printTable(); } bool wahookickrsnapbike::writeCharacteristic(uint8_t *data, uint8_t data_len, QString info, bool disable_log, @@ -880,8 +879,7 @@ bool wahookickrsnapbike::inclinationAvailableByHardware() { } double wahookickrsnapbike::maxGears() { - wheelCircumference::GearTable g; - return g.maxGears; + return gearTable.maxGears; } double wahookickrsnapbike::minGears() { diff --git a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h index 7f8eaa75a..487be456e 100644 --- a/src/devices/wahookickrsnapbike/wahookickrsnapbike.h +++ b/src/devices/wahookickrsnapbike/wahookickrsnapbike.h @@ -26,7 +26,6 @@ #include #include -#include "wheelcircumference.h" #include "devices/bike.h" #include "virtualdevices/virtualbike.h" diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index afaf5dd99..93bfb9a8a 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -809,8 +809,9 @@ const QString QZSettings::iconsole_rower = QStringLiteral("iconsole_rower"); const QString QZSettings::proform_treadmill_1500_pro = QStringLiteral("proform_treadmill_1500_pro"); const QString QZSettings::proform_505_cst_80_44 = QStringLiteral("proform_505_cst_80_44"); const QString QZSettings::proform_trainer_8_0 = QStringLiteral("proform_trainer_8_0"); +const QString QZSettings::shift_style = QStringLiteral("shift_style"); -const uint32_t allSettingsCount = 683; +const uint32_t allSettingsCount = 684; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1500,6 +1501,7 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::proform_treadmill_1500_pro, QZSettings::default_proform_treadmill_1500_pro}, {QZSettings::proform_505_cst_80_44, QZSettings::default_proform_505_cst_80_44}, {QZSettings::proform_trainer_8_0, QZSettings::default_proform_trainer_8_0}, + {QZSettings::shift_style, QZSettings::default_shift_style}, }; void QZSettings::qDebugAllSettings(bool showDefaults) { diff --git a/src/qzsettings.h b/src/qzsettings.h index 2ef4e3d39..86814b329 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -2249,6 +2249,14 @@ class QZSettings { static const QString proform_trainer_8_0; static constexpr bool default_proform_trainer_8_0 = false; + static const QString shift_style; + static constexpr int default_shift_style = 0; + + static const int SHIFT_STYLE_SEQUENTIAL = 0; + static const int SHIFT_STYLE_SHIMANO_A = 1; + static const int SHIFT_STYLE_SHIMANO_B = 2; + static const int SHIFT_STYLE_SRAM = 3; + /** * @brief Write the QSettings values using the constants from this namespace. * @param showDefaults Optionally indicates if the default should be shown with the key. diff --git a/src/settings.qml b/src/settings.qml index 07ae2fe96..e40282953 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -1043,6 +1043,7 @@ import QtQuick.Dialogs 1.0 // from version 2.18.16 property bool proform_trainer_8_0: false + property int shift_style: 0 } function paddingZeros(text, limit) { @@ -10098,6 +10099,54 @@ import QtQuick.Dialogs 1.0 onClicked: { settings.zwift_play = checked; window.settings_restart_to_apply = true; } } + RowLayout { + spacing: 10 + Label { + text: qsTr("Shift Style:") + Layout.fillWidth: true + } + ComboBox { + id: shiftStyleTextField + model: [ "Sequential", "Shimano A", "Shimano B", "SRAM" ] + displayText: { + switch(settings.shift_style) { + case 0: return "Sequential" + case 1: return "Shimano A" + case 2: return "Shimano B" + case 3: return "SRAM" + default: return "Sequential" + } + } + Layout.fillHeight: false + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + onActivated: { + displayText = shiftStyleTextField.currentValue + } + } + Button { + text: "OK" + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + onClicked: { + settings.shift_style = shiftStyleTextField.currentIndex + toast.show("Setting saved!") + window.settings_restart_to_apply = true + } + } + } + + Label { + text: qsTr("Choose how the gears should shift: Sequential (standard), Shimano (both hands) or SRAM style. All trademarks, service marks, trade names, and logos referenced herein are the property of their respective owners.") + 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) + } + Label { text: qsTr("Use it to change the gears on QZ!") font.bold: true diff --git a/src/wheelcircumference.h b/src/wheelcircumference.h index ffa0a7a13..0fc94fb28 100644 --- a/src/wheelcircumference.h +++ b/src/wheelcircumference.h @@ -117,6 +117,88 @@ class wheelCircumference : public QObject { loadGearSettings(); } + int cassetteUp(int currentGear) { + GearTable table; + GearTable::GearInfo currentGearInfo = table.getGear(currentGear); + if (currentGearInfo.gear == 0) return currentGear; + + int nextGear = currentGear; + int smallestValidCog = INT_MAX; + + for (int i = 1; i <= maxGears; i++) { + GearTable::GearInfo gear = table.getGear(i); + if (gear.gear != 0 && + gear.crankset == currentGearInfo.crankset && + gear.rearCog > currentGearInfo.rearCog && + gear.rearCog < smallestValidCog) { + smallestValidCog = gear.rearCog; + nextGear = gear.gear; + } + } + return nextGear; + } + + int cassetteDown(int currentGear) { + GearTable table; + GearTable::GearInfo currentGearInfo = table.getGear(currentGear); + if (currentGearInfo.gear == 0) return currentGear; + + int nextGear = currentGear; + int largestValidCog = 0; + + for (int i = 1; i <= maxGears; i++) { + GearTable::GearInfo gear = table.getGear(i); + if (gear.gear != 0 && + gear.crankset == currentGearInfo.crankset && + gear.rearCog < currentGearInfo.rearCog && + gear.rearCog > largestValidCog) { + largestValidCog = gear.rearCog; + nextGear = gear.gear; + } + } + return nextGear; + } + + int chainRingUp(int currentGear) { + GearTable table; + GearTable::GearInfo currentGearInfo = table.getGear(currentGear); + if (currentGearInfo.gear == 0) return currentGear; + + int nextGear = currentGear; + int smallestValidCrankset = INT_MAX; + + for (int i = 1; i <= maxGears; i++) { + GearTable::GearInfo gear = table.getGear(i); + if (gear.gear != 0 && + gear.crankset > currentGearInfo.crankset && + gear.crankset < smallestValidCrankset) { + smallestValidCrankset = gear.crankset; + nextGear = gear.gear; + } + } + return nextGear; + } + + int chainRingDown(int currentGear) { + GearTable table; + GearTable::GearInfo currentGearInfo = table.getGear(currentGear); + if (currentGearInfo.gear == 0) return currentGear; + + int nextGear = currentGear; + int largestValidCrankset = 0; + + for (int i = 1; i <= maxGears; i++) { + GearTable::GearInfo gear = table.getGear(i); + if (gear.gear != 0 && + gear.crankset < currentGearInfo.crankset && + gear.crankset > largestValidCrankset) { + largestValidCrankset = gear.crankset; + nextGear = gear.gear; + } + } + return nextGear; + } + private: std::vector gears; }; diff --git a/src/zwift_play/abstractZapDevice.h b/src/zwift_play/abstractZapDevice.h index edba74081..6b093464d 100755 --- a/src/zwift_play/abstractZapDevice.h +++ b/src/zwift_play/abstractZapDevice.h @@ -34,7 +34,7 @@ class AbstractZapDevice: public QObject { REQUEST_START = QByteArray::fromRawData("\x00\x09", 2); // {0, 9} RESPONSE_START = QByteArray::fromRawData("\x01\x03", 2); // {1, 3} - // Setup auto-repeat + // Setup auto-repeat autoRepeatTimer = new QTimer(); autoRepeatTimer->setInterval(500); connect(autoRepeatTimer, &QTimer::timeout, this, &AbstractZapDevice::handleAutoRepeat); @@ -52,6 +52,7 @@ class AbstractZapDevice: public QObject { QSettings settings; bool gears_volume_debouncing = settings.value(QZSettings::gears_volume_debouncing, QZSettings::default_gears_volume_debouncing).toBool(); bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool(); + int shift_style = settings.value(QZSettings::shift_style, QZSettings::default_shift_style).toInt(); qDebug() << zapType << characteristicName << bytes.toHex() << zwiftplay_swap << gears_volume_debouncing << risingEdge << lastFrame; @@ -84,13 +85,12 @@ class AbstractZapDevice: public QObject { if(!zwiftplay_swap) { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } else { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } else if(bytes[4] == 0) { if(DEBOUNCE) { @@ -98,13 +98,12 @@ class AbstractZapDevice: public QObject { if(!zwiftplay_swap) { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } else { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } else { risingEdge--; @@ -115,25 +114,25 @@ class AbstractZapDevice: public QObject { } } break; + case 0x07: // zwift play lastFrame = QDateTime::currentDateTime(); if(bytes.length() > 5 && bytes[bytes.length() - 5] == 0x40 && ( - (((uint8_t)bytes[bytes.length() - 4]) == 0xc7 && zapType == RIGHT) || - (((uint8_t)bytes[bytes.length() - 4]) == 0xc8 && zapType == LEFT) - ) && bytes[bytes.length() - 3] == 0x01) { + (((uint8_t)bytes[bytes.length() - 4]) == 0xc7 && zapType == RIGHT) || + (((uint8_t)bytes[bytes.length() - 4]) == 0xc8 && zapType == LEFT) + ) && bytes[bytes.length() - 3] == 0x01) { if(zapType == LEFT) { if(DEBOUNCE) { risingEdge = 2; if(!zwiftplay_swap) { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } else { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } else { if(DEBOUNCE) { @@ -141,13 +140,12 @@ class AbstractZapDevice: public QObject { if(!zwiftplay_swap) { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } else { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } } else if(bytes.length() > 14 && bytes[11] == 0x30 && bytes[12] == 0x00) { @@ -157,13 +155,12 @@ class AbstractZapDevice: public QObject { if(!zwiftplay_swap) { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } else { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } else { if(DEBOUNCE) { @@ -171,13 +168,12 @@ class AbstractZapDevice: public QObject { if(!zwiftplay_swap) { emit minus(); lastButtonPlus = false; - autoRepeatTimer->start(); } else { emit plus(); lastButtonPlus = true; - autoRepeatTimer->start(); } + autoRepeatTimer->start(); } } } else { @@ -240,53 +236,60 @@ class AbstractZapDevice: public QObject { autoRepeatTimer->start(); } } - } else if(bytes.length() > 3 && - ((((uint8_t)bytes[3]) == 0xdf) || // right top button - (((uint8_t)bytes[3]) == 0xbf))) { // right bottom button - if(DEBOUNCE) { - risingEdge = 2; - if(!zwiftplay_swap) { - emit plus(); - lastButtonPlus = true; - autoRepeatTimer->start(); - } - else { - emit minus(); - lastButtonPlus = false; - autoRepeatTimer->start(); - } - } - } else if(bytes.length() > 3 && - ((((uint8_t)bytes[3]) == 0xfd) || // left top button - (((uint8_t)bytes[3]) == 0xfb))) { // left bottom button - if(DEBOUNCE) { - risingEdge = 2; - if(!zwiftplay_swap) { - emit minus(); - lastButtonPlus = false; - autoRepeatTimer->start(); - } - else { - emit plus(); - lastButtonPlus = true; - autoRepeatTimer->start(); - } - } - } else if(bytes.length() > 5 && - ((((uint8_t)bytes[4]) == 0xfd) || // left top button - (((uint8_t)bytes[4]) == 0xfb))) { // left bottom button - if(DEBOUNCE) { + } else if(bytes.length() > 3) { + bool isRightTop = ((uint8_t)bytes[3]) == 0xdf; + bool isRightBottom = ((uint8_t)bytes[3]) == 0xbf; + bool isLeftTop = ((uint8_t)bytes[3]) == 0xfd; + bool isLeftBottom = ((uint8_t)bytes[3]) == 0xfb; + + if(DEBOUNCE && (isRightTop || isRightBottom || isLeftTop || isLeftBottom)) { risingEdge = 2; - if(!zwiftplay_swap) { - emit minus(); - lastButtonPlus = false; - autoRepeatTimer->start(); - } - else { - emit plus(); - lastButtonPlus = true; - autoRepeatTimer->start(); + switch(shift_style) { + case QZSettings::SHIFT_STYLE_SEQUENTIAL: + if(isRightTop || isRightBottom) { + if(!zwiftplay_swap) { + emit plus(); + lastButtonPlus = true; + } else { + emit minus(); + lastButtonPlus = false; + } + } else { + if(!zwiftplay_swap) { + emit minus(); + lastButtonPlus = false; + } else { + emit plus(); + lastButtonPlus = true; + } + } + break; + + case QZSettings::SHIFT_STYLE_SHIMANO_A: + if(isLeftTop || isLeftBottom) + emit isLeftTop ? chainRingUp() : chainRingDown(); + else + emit isRightTop ? cassetteUp() : cassetteDown(); + lastButtonPlus = isRightTop || isLeftTop; + break; + + case QZSettings::SHIFT_STYLE_SHIMANO_B: + if(isLeftTop || isLeftBottom) + emit isLeftBottom ? chainRingUp() : chainRingDown(); + else + emit isRightBottom ? cassetteUp() : cassetteDown(); + lastButtonPlus = isRightBottom || isLeftBottom; + break; + + case QZSettings::SHIFT_STYLE_SRAM: + if(isRightTop || isRightBottom) + emit cassetteUp(); + else + emit cassetteDown(); + lastButtonPlus = isRightTop || isRightBottom; + break; } + autoRepeatTimer->start(); } } else { risingEdge--; @@ -329,8 +332,8 @@ class AbstractZapDevice: public QObject { private: QByteArray devicePublicKeyBytes; static volatile int8_t risingEdge; - static QTimer* autoRepeatTimer; // Static timer for auto-repeat - static bool lastButtonPlus; // Static track of which button was last pressed + static QTimer* autoRepeatTimer; + static bool lastButtonPlus; static QDateTime lastFrame; private slots: @@ -342,15 +345,32 @@ class AbstractZapDevice: public QObject { autoRepeatTimer->stop(); return; } - if(lastButtonPlus) - emit plus(); - else - emit minus(); + + QSettings settings; + int shift_style = settings.value(QZSettings::shift_style, QZSettings::default_shift_style).toInt(); + + switch(shift_style) { + case QZSettings::SHIFT_STYLE_SEQUENTIAL: + if(lastButtonPlus) emit plus(); + else emit minus(); + break; + + case QZSettings::SHIFT_STYLE_SHIMANO_A: + case QZSettings::SHIFT_STYLE_SHIMANO_B: + case QZSettings::SHIFT_STYLE_SRAM: + if(lastButtonPlus) emit cassetteUp(); + else emit cassetteDown(); + break; + } } signals: void plus(); void minus(); + void chainRingUp(); + void chainRingDown(); + void cassetteUp(); + void cassetteDown(); }; #endif // ABSTRACTZAPDEVICE_H