From 9065ef0c195436ff2e8cfc6a8c945dfab2a6a5a3 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Wed, 3 Jan 2024 09:16:28 +0100 Subject: [PATCH] Support Nordictrack S25 treadmill #1933 --- src/proformtreadmill.cpp | 147 +++++++++++++++++++++++++++++++++++++-- src/qzsettings.cpp | 5 +- src/qzsettings.h | 2 + src/settings.qml | 17 ++++- 4 files changed, 163 insertions(+), 8 deletions(-) diff --git a/src/proformtreadmill.cpp b/src/proformtreadmill.cpp index 8910856cb..71d645662 100644 --- a/src/proformtreadmill.cpp +++ b/src/proformtreadmill.cpp @@ -72,6 +72,8 @@ void proformtreadmill::forceIncline(double incline) { settings.value(QZSettings::proform_treadmill_se, QZSettings::default_proform_treadmill_se).toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool proform_treadmill_z1300i = settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, @@ -97,7 +99,7 @@ void proformtreadmill::forceIncline(double incline) { if (norditrack_s25i_treadmill) { write[14] = write[11] + write[12] + 0x11; - } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i || proform_treadmill_l6_0s) { + } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill) { write[14] = write[11] + write[12] + 0x12; } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) { for (uint8_t i = 0; i < 7; i++) { @@ -132,6 +134,8 @@ void proformtreadmill::forceSpeed(double speed) { .toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool proform_treadmill_z1300i = settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, @@ -148,7 +152,7 @@ void proformtreadmill::forceSpeed(double speed) { if (norditrack_s25i_treadmill) { write[14] = write[11] + write[12] + 0x11; } else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_treadmill_cadence_lt || - proform_treadmill_z1300i || proform_treadmill_l6_0s) { + proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill) { write[14] = write[11] + write[12] + 0x11; } else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) { for (uint8_t i = 0; i < 7; i++) { @@ -205,6 +209,9 @@ void proformtreadmill::update() { bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill) .toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill) + .toBool(); bool nordictrack_incline_trainer_x7i = settings .value(QZSettings::nordictrack_incline_trainer_x7i, QZSettings::default_nordictrack_incline_trainer_x7i) @@ -1268,6 +1275,68 @@ void proformtreadmill::update() { if (counterPoll > 5) { counterPoll = 0; } + } else if (norditrack_s25_treadmill) { + uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03}; + uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t noOpData3[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x10, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t noOpData4[] = {0xfe, 0x02, 0x14, 0x03}; + uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00, 0x0a, 0x1b, 0x94, 0x30, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80}; + uint8_t noOpData6[] = {0xff, 0x02, 0x18, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + switch (counterPoll) { + case 0: + writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp")); + break; + case 1: + writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp")); + break; + case 2: + writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp")); + break; + case 3: + writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), false, true); + break; + case 4: + writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp")); + break; + case 5: + writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"), false, true); + if (requestInclination != -100) { + if (requestInclination < 0) + requestInclination = 0; + if (requestInclination != currentInclination().value() && requestInclination >= 0 && + requestInclination <= 15) { + emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination)); + forceIncline(requestInclination); + } + requestInclination = -100; + } + if (requestSpeed != -1) { + if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) { + emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed)); + forceSpeed(requestSpeed); + } + requestSpeed = -1; + } + if (requestStart != -1) { + emit debug(QStringLiteral("starting...")); + + // btinit(); + + requestStart = -1; + emit tapeStarted(); + } + if (requestStop != -1) { + emit debug(QStringLiteral("stopping...")); + // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape"); + requestStop = -1; + } + break; + } + counterPoll++; + if (counterPoll > 5) { + counterPoll = 0; + } } else { uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03}; uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00, @@ -1381,7 +1450,6 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha settings.value(QZSettings::proform_treadmill_z1300i, QZSettings::default_proform_treadmill_z1300i).toBool(); bool nordictrack_s20_treadmill = settings.value(QZSettings::nordictrack_s20_treadmill, QZSettings::default_nordictrack_s20_treadmill).toBool(); - bool proform_treadmill_l6_0s = settings.value(QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s).toBool(); double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat(); @@ -1483,6 +1551,8 @@ void proformtreadmill::btinit() { .toBool(); bool norditrack_s25i_treadmill = settings.value(QZSettings::norditrack_s25i_treadmill, QZSettings::default_norditrack_s25i_treadmill).toBool(); + bool norditrack_s25_treadmill = + settings.value(QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill).toBool(); bool nordictrack_t65s_83_treadmill = settings.value(QZSettings::nordictrack_t65s_83_treadmill, QZSettings::default_nordictrack_t65s_83_treadmill) .toBool(); @@ -1878,6 +1948,73 @@ void proformtreadmill::btinit() { QThread::msleep(sleepms); writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false); QThread::msleep(sleepms); + } else if (norditrack_s25_treadmill) { + uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02}; + uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02}; + uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02}; + uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04}; + uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x38, 0xac, 0x12, 0x8e, 0xfc, 0x78, 0xee, 0x6a, 0xd0}; + uint8_t initData15[] = {0x01, 0x12, 0x54, 0xda, 0x56, 0xd4, 0x70, 0xf6, 0x62, 0xe8, 0x9c, 0x02, 0xbe, 0x2c, 0xc8, 0x7e, 0x1a, 0x80, 0x24, 0xca}; + uint8_t initData16[] = {0xff, 0x08, 0x66, 0x04, 0xe0, 0x98, 0x02, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t initData17[] = {0xfe, 0x02, 0x19, 0x03}; + uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0}; + uint8_t initData19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); + writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false); + QThread::msleep(sleepms); } else if (norditrack_s25i_treadmill) { uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, @@ -2921,14 +3058,14 @@ void proformtreadmill::stateChanged(QLowEnergyService::ServiceState state) { .toBool(); if (virtual_device_enabled) { if (!virtual_device_force_bike) { - debug("creating virtual treadmill interface..."); + emit debug("creating virtual treadmill interface..."); auto virtualTreadmill = new virtualtreadmill(this, noHeartService); connect(virtualTreadmill, &virtualtreadmill::debug, this, &proformtreadmill::debug); connect(virtualTreadmill, &virtualtreadmill::changeInclination, this, &proformtreadmill::changeInclinationRequested); this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY); } else { - debug("creating virtual bike interface..."); + emit debug("creating virtual bike interface..."); auto virtualBike = new virtualbike(this); connect(virtualBike, &virtualbike::changeInclination, this, &proformtreadmill::changeInclinationRequested); diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index 2d27ecac5..f84b8ea48 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -695,8 +695,9 @@ const QString QZSettings::default_zwift_username = QStringLiteral(""); const QString QZSettings::zwift_password = QStringLiteral("zwift_password"); const QString QZSettings::default_zwift_password = QStringLiteral(""); const QString QZSettings::garmin_bluetooth_compatibility = QStringLiteral("garmin_bluetooth_compatibility"); +const QString QZSettings::norditrack_s25_treadmill = QStringLiteral("norditrack_s25_treadmill"); -const uint32_t allSettingsCount = 582; +const uint32_t allSettingsCount = 583; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1285,7 +1286,7 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::zwift_username, QZSettings::default_zwift_username}, {QZSettings::zwift_password, QZSettings::default_zwift_password}, {QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility}, - + {QZSettings::norditrack_s25_treadmill, QZSettings::default_norditrack_s25_treadmill}, }; void QZSettings::qDebugAllSettings(bool showDefaults) { diff --git a/src/qzsettings.h b/src/qzsettings.h index b240c9cb2..4dd7af41b 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -1947,6 +1947,8 @@ class QZSettings { static const QString garmin_bluetooth_compatibility; static constexpr bool default_garmin_bluetooth_compatibility = false; + static const QString norditrack_s25_treadmill; + static constexpr int default_norditrack_s25_treadmill = false; /** * @brief Write the QSettings values using the constants from this namespace. diff --git a/src/settings.qml b/src/settings.qml index 5f4f45a49..24d9007a9 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -864,6 +864,7 @@ import QtQuick.Dialogs 1.0 // from version 2.16.31 property bool garmin_bluetooth_compatibility: false + property bool norditrack_s25_treadmill: false } function paddingZeros(text, limit) { @@ -5531,7 +5532,21 @@ import QtQuick.Dialogs 1.0 textColor: Material.color(Material.Yellow) color: Material.backgroundColor accordionContent: ColumnLayout { - spacing: 0 + spacing: 0 + SwitchDelegate { + text: qsTr("Nordictrack S25") + spacing: 0 + bottomPadding: 0 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + clip: false + checked: settings.norditrack_s25_treadmill + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + onClicked: { settings.norditrack_s25_treadmill = checked; window.settings_restart_to_apply = true; } + } + SwitchDelegate { id: nordictrackS25iDelegate text: qsTr("Nordictrack S25i")