Compare commits

...

15 Commits

Author SHA1 Message Date
Roberto Viola
7d33d87f04 Update project.pbxproj 2025-11-24 13:14:37 +01:00
Roberto Viola
b15055e914 Add THINK_X check to characteristicChanged logic
Updated the conditional in characteristicChanged to include a THINK_X flag, ensuring that crank revolutions are only incremented when THINK_X is false. This accommodates devices where THINK_X sends crank revs in the power characteristic.
2025-11-24 12:43:33 +01:00
Roberto Viola
5ddb5f08cd Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-11-24 12:34:53 +01:00
Roberto Viola
13161cd894 Fix resistance value formatting in update method
Ensures the resistance value is formatted as a floating-point number with zero decimal places when updating the UI.
2025-11-24 12:34:47 +01:00
Roberto Viola
3089dc8a1c Revert "Format resistance values as strings with no decimals"
This reverts commit fdbc6e94e1.
2025-11-24 12:34:12 +01:00
Roberto Viola
f38652f7b2 Add support for NordicTrack Elliptical SE7i (#3900)
* Add support for NordicTrack Elliptical SE7i

Introduces device-specific initialization, polling, and control logic for the NordicTrack Elliptical SE7i in proformelliptical. Adds a new settings flag and UI switch for enabling SE7i support. Updates QZSettings and settings.qml to include the new option and default value.

* Update proformelliptical.h

* Update proformelliptical.cpp

* moved to right module, and init fixed

* build fix

* Update nordictrackelliptical.cpp

* Update settings.qml

* Update settings.qml
2025-11-24 12:02:53 +01:00
Roberto Viola
fdbc6e94e1 Format resistance values as strings with no decimals
Changed resistance and peloton_resistance assignments to use QString::number with zero decimal places, ensuring consistent string formatting for display.
2025-11-24 12:00:21 +01:00
Roberto Viola
025815fe99 Treadmill NYMAN PLUS min step inclination to 1 2025-11-24 08:40:00 +01:00
Roberto Viola
e2a93cde72 QZ app does not record cadence or resistance after connecting with SmartSpin2k (Issue #3887) 2025-11-21 14:08:14 +01:00
Roberto Viola
a44002c924 Change Kettler USB baudrate from 9600 to 57600 (#3899)
* Change Kettler USB baudrate from 9600 to 57600

Update baudrate configuration for Kettler USB bike support across all platforms:
- Android: Update JNI call parameter to 57600
- Linux/Mac: Change cfsetspeed to B57600
- Windows: Change BaudRate to CBR_57600

* Add baudrate selection setting for Kettler USB

- Add kettler_usb_baud_57600 setting (default: true for 57600 baud)
- Add baudrate parameter to KettlerUSB constructor
- Update openPort() to use configured baudrate for all platforms (Android, Linux/Mac, Windows)
- Add UI ComboBox in Kettler USB Bike Options to choose between 9600 and 57600 baud
- Update settings infrastructure (qzsettings.h/cpp, settings.qml)
- Allow users to switch between 9600 and 57600 baudrate via UI

* Change Kettler USB baudrate setting from bool to int

- Replace kettler_usb_baud_57600 (bool) with kettler_usb_baudrate (int)
- Change default from 57600 to 9600 as requested
- Update KettlerUSB constructor to accept int baudrate parameter
- Add switch statements to convert int to platform-specific constants:
  * Linux/Mac: Convert to speed_t (B9600, B57600)
  * Windows: Convert to CBR constants (CBR_9600, CBR_57600)
  * Android: Use int value directly
- Update settings.qml to use int property and parseInt()
- Future-proof: Can easily add more baudrate options without new settings

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 14:00:21 +01:00
Roberto Viola
3bcd4d0ee4 Improve KettlerUSB status parsing and serial buffer handling
Added validation to ignore non-status and malformed responses in parseStatusResponse. Introduced flushSerialBuffer to clear serial buffers after initialization. Reduced polling interval for faster response and improved handling of power command responses.
2025-11-21 11:40:27 +01:00
Roberto Viola
e15e8ebf9e Concept2>QZ: Rower Distance Discrepancy #3872 2025-11-20 15:38:54 +01:00
Roberto Viola
fba48cb7da Update project.pbxproj 2025-11-20 14:39:36 +01:00
Roberto Viola
daacf806bf Track valid cadence from 0x2AD2 characteristic
email "QZ compatibility smart Trainer" from Niklas H. on 20/11/2025

Introduces a static flag to ensure cadence from the 0x2A5B characteristic is only processed until a valid cadence is received from 0x2AD2. This prevents duplicate or conflicting cadence data.
2025-11-20 13:39:50 +01:00
Roberto Viola
4c21b01903 Set ergModeSupported to false for JFBK5.0 devices
mail from Darren K. on 20/11/2025

When discovering devices with names starting with JFBK5.0 or JFBK7.0, explicitly set ergModeSupported to false to reflect their capabilities.
2025-11-20 13:32:15 +01:00
17 changed files with 371 additions and 45 deletions

View File

@@ -4569,7 +4569,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4770,7 +4770,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -5007,7 +5007,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -5103,7 +5103,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5195,7 +5195,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5311,7 +5311,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5421,7 +5421,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -5512,7 +5512,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1212;
CURRENT_PROJECT_VERSION = 1214;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;

View File

@@ -1677,6 +1677,7 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if(bluetoothDevice.name().toUpper().startsWith("JFBK5.0") || bluetoothDevice.name().toUpper().startsWith("JFBK7.0")) {
qDebug() << QStringLiteral("JFBK5.0 found");
resistance_lvl_mode = true;
ergModeSupported = false;
JFBK5_0 = true;
} else if((bluetoothDevice.name().toUpper().startsWith("BIKE-"))) {
qDebug() << QStringLiteral("BIKE- found");

View File

@@ -2574,6 +2574,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("HORIZON_7.8AT workaround ON!");
} else if(device.name().toUpper().startsWith("T01_")) {
QSettings settings;
T01 = true;
iconcept_ftms_treadmill_inclination_table = settings.value(QZSettings::iconcept_ftms_treadmill_inclination_table, QZSettings::default_iconcept_ftms_treadmill_inclination_table).toBool();
if(iconcept_ftms_treadmill_inclination_table) {
ICONCEPT_FTMS_treadmill = true;
@@ -3333,7 +3334,7 @@ void horizontreadmill::testProfileCRC() {
double horizontreadmill::minStepInclination() {
QSettings settings;
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT || T3G_PRO || T3G_ELITE)
if (kettler_treadmill || T01 || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT || T3G_PRO || T3G_ELITE)
return 1.0;
else
return 0.5;

View File

@@ -113,6 +113,7 @@ class horizontreadmill : public treadmill {
bool T3G_PRO = false;
bool T3G_ELITE = false;
bool TP1 = false;
bool T01 = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -23,13 +23,14 @@
/* ----------------------------------------------------------------------
* CONSTRUCTOR/DESTRUCTOR
* ---------------------------------------------------------------------- */
KettlerUSB::KettlerUSB(QObject *parent, QString devname) : QThread(parent) {
KettlerUSB::KettlerUSB(QObject *parent, QString devname, int baudrate) : QThread(parent) {
devicePower = deviceHeartRate = deviceCadence = deviceSpeed = deviceDistance = 0.00;
targetPower = DEFAULT_POWER;
writePower = false;
setDevice(devname);
deviceStatus = 0;
this->parent = parent;
this->baudrate = baudrate;
}
KettlerUSB::~KettlerUSB() {}
@@ -85,21 +86,46 @@ void KettlerUSB::parseStatusResponse(const QString &response) {
// HR RPM Speed Distance Power Energy Time CurrentPower
// Example: 101\t047\t074\t002\t025\t0312\t01:12\t025
// Ignore non-status responses (ACK, ERROR, etc.)
if (response == "ACK" || response == "ERROR" || response.length() < 10) {
qDebug() << " Skipping non-status response:" << response;
return;
}
QStringList fields = response.split('\t');
if (fields.size() >= 8) {
pvars.lock();
deviceHeartRate = fields[0].toDouble();
deviceCadence = fields[1].toDouble();
deviceSpeed = fields[2].toDouble() * 0.1; // Speed is in 0.1 km/h
deviceDistance = fields[3].toDouble() * 100; // Distance is in 100m units
devicePower = fields[7].toDouble(); // CurrentPower (last field)
pvars.unlock();
// Additional validation: check if fields contain numeric data
bool validData = true;
for (int i = 0; i < 8; i++) {
if (i != 6) { // Skip time field (format HH:MM)
bool ok;
fields[i].toDouble(&ok);
if (!ok && i != 3 && i != 5) { // Distance and Energy can be non-numeric sometimes
validData = false;
break;
}
}
}
qDebug() << "Kettler Status: HR=" << deviceHeartRate
<< " RPM=" << deviceCadence
<< " Speed=" << deviceSpeed
<< " Dist=" << deviceDistance
<< " Power=" << devicePower;
if (validData) {
pvars.lock();
deviceHeartRate = fields[0].toDouble();
deviceCadence = fields[1].toDouble();
deviceSpeed = fields[2].toDouble() * 0.1; // Speed is in 0.1 km/h
deviceDistance = fields[3].toDouble() * 100; // Distance is in 100m units
devicePower = fields[7].toDouble(); // CurrentPower (last field)
pvars.unlock();
qDebug() << "Kettler Status: HR=" << deviceHeartRate
<< " RPM=" << deviceCadence
<< " Speed=" << deviceSpeed
<< " Dist=" << deviceDistance
<< " Power=" << devicePower;
} else {
qDebug() << " Invalid status data (non-numeric fields):" << response;
}
} else {
qDebug() << " Invalid status format (expected 8 fields, got" << fields.size() << "):" << response;
}
}
@@ -188,6 +214,10 @@ void KettlerUSB::run() {
// Send initialization sequence
initSequence();
// Flush buffer after init to clear any leftover responses
KettlerSleeper::msleep(500);
flushSerialBuffer();
// Wait 3 seconds before first poll (as per kettlerUSB2BLE)
KettlerSleeper::msleep(3000);
@@ -200,13 +230,27 @@ void KettlerUSB::run() {
pvars.unlock();
if (curWritePower) {
// Send power command instead of status request
// Send power command
QString powerCmd = QString("PW%1").arg((int)curPower);
sendCommand(powerCmd);
pvars.lock();
this->writePower = false;
pvars.unlock();
// Read and discard response (usually ACK or a number)
KettlerSleeper::msleep(150);
QString pwResponse = readResponse();
if (!pwResponse.isEmpty()) {
qDebug() << " PW response (discarded):" << pwResponse;
}
// Immediately send ST to get fresh status
sendCommand("ST");
QString response = readResponse();
if (!response.isEmpty()) {
parseStatusResponse(response);
}
} else {
// Normal status poll
sendCommand("ST");
@@ -216,8 +260,8 @@ void KettlerUSB::run() {
}
}
// Wait 2 seconds between polls (as per kettlerUSB2BLE)
KettlerSleeper::msleep(2000);
// Reduced polling interval from 2000ms to 500ms for faster response
KettlerSleeper::msleep(500);
}
// Check status
@@ -276,11 +320,11 @@ int KettlerUSB::closePort() {
int KettlerUSB::openPort() {
#ifdef Q_OS_ANDROID
// Call Java Usbserial with 9600 baud for Kettler
// Call Java Usbserial with configured baud rate for Kettler
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/Usbserial", "open",
"(Landroid/content/Context;I)V",
QtAndroid::androidContext().object(),
9600);
baudrate);
#elif !defined(WIN32)
// LINUX AND MAC USES TERMIO / IOCTL / STDIO
@@ -301,7 +345,14 @@ int KettlerUSB::openPort() {
tcgetattr(devicePort, &deviceSettings);
cfmakeraw(&deviceSettings);
cfsetspeed(&deviceSettings, B9600); // Kettler uses 9600 baud
// Convert int baudrate to speed_t constant
speed_t speed;
switch (baudrate) {
case 57600: speed = B57600; break;
case 9600:
default: speed = B9600; break;
}
cfsetspeed(&deviceSettings, speed);
deviceSettings.c_iflag &=
~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ICANON | ISTRIP | IXON | IXOFF | IXANY);
@@ -347,7 +398,12 @@ int KettlerUSB::openPort() {
if (GetCommState(devicePort, &deviceSettings) == false)
return -1;
deviceSettings.BaudRate = CBR_9600; // Kettler uses 9600 baud
// Convert int baudrate to Windows CBR constant
switch (baudrate) {
case 57600: deviceSettings.BaudRate = CBR_57600; break;
case 9600:
default: deviceSettings.BaudRate = CBR_9600; break;
}
deviceSettings.fParity = NOPARITY;
deviceSettings.ByteSize = 8;
deviceSettings.StopBits = ONESTOPBIT;
@@ -453,6 +509,20 @@ QString KettlerUSB::rawRead() {
return result;
}
void KettlerUSB::flushSerialBuffer() {
qDebug() << "Flushing serial buffer...";
#ifdef Q_OS_ANDROID
// Clear Android buffer
rxBuffer.clear();
// Read and discard any pending data
QAndroidJniObject::callStaticObjectMethod("org/cagnulen/qdomyoszwift/Usbserial", "read", "()[B");
#elif defined(WIN32)
PurgeComm(devicePort, PURGE_RXCLEAR | PURGE_TXCLEAR);
#else
tcflush(devicePort, TCIOFLUSH);
#endif
}
bool KettlerUSB::discover(QString filename) {
if (filename.isEmpty())
return false;

View File

@@ -64,10 +64,11 @@
class KettlerUSB : public QThread {
public:
KettlerUSB(QObject *parent = 0, QString deviceFilename = 0);
KettlerUSB(QObject *parent = 0, QString deviceFilename = 0, int baudrate = 9600);
~KettlerUSB();
QObject *parent;
int baudrate = 9600;
// HIGH-LEVEL FUNCTIONS
int start(); // Calls QThread to start
@@ -126,6 +127,7 @@ class KettlerUSB : public QThread {
// raw device utils
int rawWrite(const char *bytes, int size);
QString rawRead();
void flushSerialBuffer(); // Flush any pending data in serial buffer
#ifdef Q_OS_ANDROID
QByteArray rxBuffer;

View File

@@ -48,8 +48,10 @@ kettlerusbbike::kettlerusbbike(bool noWriteResistance, bool noHeartService, int8
QString kettlerSerialPort =
settings.value(QZSettings::kettler_usb_serialport, QZSettings::default_kettler_usb_serialport).toString();
int kettlerBaudrate =
settings.value(QZSettings::kettler_usb_baudrate, QZSettings::default_kettler_usb_baudrate).toInt();
myKettler = new KettlerUSB(this, kettlerSerialPort);
myKettler = new KettlerUSB(this, kettlerSerialPort, kettlerBaudrate);
myKettler->start();
ergModeSupported = true; // ERG mode supported

View File

@@ -26,6 +26,10 @@ nordictrackelliptical::nordictrackelliptical(bool noWriteResistance, bool noHear
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
QSettings settings;
nordictrack_se7i = settings.value(QZSettings::nordictrack_se7i, QZSettings::default_nordictrack_se7i).toBool();
initDone = false;
connect(refresh, &QTimer::timeout, this, &nordictrackelliptical::update);
refresh->start(200ms);
@@ -277,6 +281,13 @@ void nordictrackelliptical::forceIncline(double requestIncline) {
writeCharacteristic((uint8_t *)inc200, sizeof(inc200), QStringLiteral("inc200"), false, true);
break;
}
} else if (nordictrack_se7i) {
// SE7i uses ff 0d packet with byte[10]=0x02 for incline
// Incline encoding: value = incline% * 100
uint16_t incValue = (uint16_t)(requestIncline * 100);
uint8_t incCmd[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x06, 0x09, 0x02, 0x01,
0x02, (uint8_t)(incValue & 0xFF), (uint8_t)((incValue >> 8) & 0xFF), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(incCmd, sizeof(incCmd), QStringLiteral("incline_se7i"), false, true);
}
}
@@ -405,6 +416,13 @@ void nordictrackelliptical::forceResistance(resistance_t requestResistance) {
writeCharacteristic((uint8_t *)res22, sizeof(res22), QStringLiteral("resistance22"), false, true);
break;
}
} else if (nordictrack_se7i) {
// SE7i uses ff 0d packet with byte[10]=0x04 for resistance
// Resistance encoding: value = resistance * 454 - 1
uint16_t resValue = (requestResistance * 454) - 1;
uint8_t resCmd[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x06, 0x09, 0x02, 0x01,
0x04, (uint8_t)(resValue & 0xFF), (uint8_t)((resValue >> 8) & 0xFF), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(resCmd, sizeof(resCmd), QStringLiteral("resistance_se7i"), false, true);
} else if (proform_hybrid_trainer_xt) {
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x06, 0x09, 0x02, 0x01,
0x04, 0x32, 0x02, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -669,6 +687,63 @@ void nordictrackelliptical::update() {
if (counterPoll > 4) {
counterPoll = 0;
}
} else if (nordictrack_se7i) {
// NordicTrack Elliptical SE7i - 6 packet sendPoll cycle
uint8_t se7i_noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t se7i_noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x06, 0x13, 0x02, 0x00,
0x0d, 0x3e, 0x96, 0x33, 0x00, 0x10, 0x40, 0x50, 0x00, 0x80};
uint8_t se7i_noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x05, 0x54, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t se7i_noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t se7i_noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x06, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t se7i_noOpData6[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x90, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
writeCharacteristic(se7i_noOpData1, sizeof(se7i_noOpData1), QStringLiteral("noOp"));
break;
case 1:
writeCharacteristic(se7i_noOpData2, sizeof(se7i_noOpData2), QStringLiteral("noOp"));
break;
case 2:
writeCharacteristic(se7i_noOpData3, sizeof(se7i_noOpData3), QStringLiteral("noOp"));
if (requestResistance != -1) {
if (requestResistance < 0)
requestResistance = 0;
if (requestResistance != currentResistance().value() && requestResistance >= 0 &&
requestResistance <= max_resistance) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
forceResistance(requestResistance);
}
requestResistance = -1;
}
if (requestInclination != -1) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= max_inclination) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -1;
}
break;
case 3:
writeCharacteristic(se7i_noOpData4, sizeof(se7i_noOpData4), QStringLiteral("noOp"));
break;
case 4:
writeCharacteristic(se7i_noOpData5, sizeof(se7i_noOpData5), QStringLiteral("noOp"));
break;
case 5:
writeCharacteristic(se7i_noOpData6, sizeof(se7i_noOpData6), QStringLiteral("noOp"));
break;
}
counterPoll++;
if (counterPoll > 5) {
counterPoll = 0;
}
} else {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x06, 0x13, 0x02, 0x00,
@@ -958,6 +1033,28 @@ void nordictrackelliptical::characteristicChanged(const QLowEnergyCharacteristic
lastPacket = newValue;
// SE7i Speed and Cadence parsing (Type 0x01 packets with byte[4]=0x46)
if (nordictrack_se7i && newValue.length() == 20 && newValue.at(0) == 0x01 && newValue.at(1) == 0x12 &&
newValue.at(4) == 0x46 && initDone == true) {
// Parse speed from bytes 12-13 (little endian, divided by 100)
Speed = (double)(((uint16_t)((uint8_t)newValue.at(13)) << 8) + (uint16_t)((uint8_t)newValue.at(12))) / 100.0;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
lastSpeedChanged = QDateTime::currentDateTime();
// Parse cadence from byte 2
uint8_t c = newValue.at(2);
if (c > 0)
Cadence = (c * cadence_gain) + cadence_offset;
else
Cadence = 0;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
return;
}
if (!proform_hybrid_trainer_xt && !nordictrack_elliptical_c7_5 && newValue.length() == 20 &&
newValue.at(0) == 0x01 && newValue.at(1) == 0x12 && newValue.at(19) == 0x2C) {
uint8_t c = newValue.at(2);
@@ -973,7 +1070,7 @@ void nordictrackelliptical::characteristicChanged(const QLowEnergyCharacteristic
return;
}
if (!nordictrack_elliptical_c7_5 && newValue.length() == 20 && newValue.at(0) == 0x01 && newValue.at(1) == 0x12 &&
if (!nordictrack_elliptical_c7_5 && !nordictrack_se7i && newValue.length() == 20 && newValue.at(0) == 0x01 && newValue.at(1) == 0x12 &&
initDone == true) {
Speed = (double)(((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14))) / 100.0;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
@@ -985,7 +1082,7 @@ void nordictrackelliptical::characteristicChanged(const QLowEnergyCharacteristic
emit debug(QStringLiteral("Current Heart from machinery: ") + QString::number(heart));
}
} else if (QDateTime::currentDateTime().secsTo(lastSpeedChanged) > 3) {
} else if (!nordictrack_se7i && QDateTime::currentDateTime().secsTo(lastSpeedChanged) > 3) {
Speed = 0;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
@@ -1021,13 +1118,15 @@ void nordictrackelliptical::characteristicChanged(const QLowEnergyCharacteristic
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
if (newValue.length() != 20 || (newValue.at(0) != 0x00 && newValue.at(0) != 0x01) || newValue.at(1) != 0x12 ||
// Skip the strict packet validation for SE7i (it has different packet structures)
if (!nordictrack_se7i &&
(newValue.length() != 20 || (newValue.at(0) != 0x00 && newValue.at(0) != 0x01) || newValue.at(1) != 0x12 ||
newValue.at(2) != 0x01 || newValue.at(3) != 0x04 || newValue.at(4) != 0x02 ||
(newValue.at(5) != 0x2e && newValue.at(5) != 0x30 && newValue.at(5) != 0x31) ||
(((uint8_t)newValue.at(12)) == 0xFF && ((uint8_t)newValue.at(13)) == 0xFF &&
((uint8_t)newValue.at(14)) == 0xFF && ((uint8_t)newValue.at(15)) == 0xFF &&
((uint8_t)newValue.at(16)) == 0xFF && ((uint8_t)newValue.at(17)) == 0xFF &&
((uint8_t)newValue.at(18)) == 0xFF && ((uint8_t)newValue.at(19)) == 0xFF)) {
((uint8_t)newValue.at(18)) == 0xFF && ((uint8_t)newValue.at(19)) == 0xFF))) {
return;
}
@@ -1046,7 +1145,20 @@ void nordictrackelliptical::characteristicChanged(const QLowEnergyCharacteristic
}
}
if (!nordictrack_elliptical_c7_5) {
if (nordictrack_se7i && newValue.length() == 20 && newValue.at(0) == 0x00 &&
newValue.at(1) == 0x12 && newValue.at(2) == 0x01 && newValue.at(3) == 0x04 && newValue.at(4) == 0x02 &&
(newValue.at(5) == 0x30 || newValue.at(5) == 0x31)) {
// SE7i Resistance and Inclination parsing (Type 0x00 packets)
// Inclination from bytes 10-11 (little endian, divided by 100)
uint16_t incValue = ((uint16_t)((uint8_t)newValue.at(10))) + ((uint16_t)((uint8_t)newValue.at(11)) << 8);
Inclination = ((double)incValue) / 100.0;
emit debug(QStringLiteral("Current Inclination from packet: ") + QString::number(Inclination.value()));
// Resistance from bytes 12-13 (little endian): resistance = (value + 1) / 454
uint16_t resValue = ((uint16_t)((uint8_t)newValue.at(12))) + ((uint16_t)((uint8_t)newValue.at(13)) << 8);
Resistance = ((double)(resValue + 1)) / 454.0;
emit debug(QStringLiteral("Current Resistance from packet: ") + QString::number(Resistance.value()));
} else if (!nordictrack_elliptical_c7_5 && !nordictrack_se7i) {
Resistance = GetResistanceFromPacket(newValue);
} else if (nordictrack_elliptical_c7_5 && newValue.length() == 20 && newValue.at(0) == 0x00 &&
newValue.at(1) == 0x12 && newValue.at(2) == 0x01 && newValue.at(3) == 0x04 && newValue.at(4) == 0x02 &&
@@ -1203,7 +1315,68 @@ void nordictrackelliptical::btinit() {
QThread::msleep(400);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(400);
if (nordictrack_elliptical_c7_5) {
if (nordictrack_se7i) {
// NordicTrack Elliptical SE7i initialization (19 packets: pkt944 to pkt1020)
max_resistance = 22;
max_inclination = 20;
uint8_t se7i_initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t se7i_initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t se7i_initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x06, 0x04, 0x80, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t se7i_initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x06, 0x04, 0x88, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t se7i_initData5[] = {0xfe, 0x02, 0x0b, 0x02}; // pkt972
uint8_t se7i_initData6[] = {0xff, 0x0b, 0x02, 0x04, 0x02, 0x07, 0x02, 0x07, 0x82, 0x00, 0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt975
uint8_t se7i_initData7[] = {0xfe, 0x02, 0x0a, 0x02}; // pkt982
uint8_t se7i_initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt985
uint8_t se7i_initData9[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt994
uint8_t se7i_initData10[] = {0xfe, 0x02, 0x2c, 0x04}; // pkt1000
uint8_t se7i_initData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x06, 0x28, 0x90, 0x04, 0x00, 0x0d, 0x68, 0xc9, 0x28, 0x95, 0xf0, 0x69, 0xc0, 0x3d}; // pkt1003
uint8_t se7i_initData12[] = {0x01, 0x12, 0xa8, 0x19, 0x88, 0xf5, 0x60, 0xf9, 0x70, 0xcd, 0x48, 0xc9, 0x48, 0xf5, 0x70, 0xe9, 0x60, 0x1d, 0x88, 0x39}; // pkt1006
uint8_t se7i_initData13[] = {0xff, 0x08, 0xa8, 0x55, 0xc0, 0x80, 0x02, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1009
uint8_t se7i_initData14[] = {0xfe, 0x02, 0x19, 0x03}; // pkt1014
uint8_t se7i_initData15[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x06, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1017
uint8_t se7i_initData16[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1020
int sleepms = 400;
writeCharacteristic(se7i_initData1, sizeof(se7i_initData1), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData2, sizeof(se7i_initData2), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData1, sizeof(se7i_initData1), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData3, sizeof(se7i_initData3), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData1, sizeof(se7i_initData1), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData4, sizeof(se7i_initData4), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData5, sizeof(se7i_initData5), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData6, sizeof(se7i_initData6), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData7, sizeof(se7i_initData7), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData8, sizeof(se7i_initData8), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData1, sizeof(se7i_initData1), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData9, sizeof(se7i_initData9), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData10, sizeof(se7i_initData10), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData11, sizeof(se7i_initData11), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData12, sizeof(se7i_initData12), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData13, sizeof(se7i_initData13), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData14, sizeof(se7i_initData14), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData15, sizeof(se7i_initData15), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
writeCharacteristic(se7i_initData16, sizeof(se7i_initData16), QStringLiteral("init"), false, true);
QThread::msleep(sleepms);
} else if (nordictrack_elliptical_c7_5) {
max_resistance = 22;
max_inclination = 20;

View File

@@ -78,6 +78,7 @@ class nordictrackelliptical : public elliptical {
bool noWriteResistance = false;
bool noHeartService = false;
bool nordictrack_se7i = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -10,6 +10,7 @@
#include <QThread>
#include <math.h>
#include <limits>
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
@@ -349,6 +350,7 @@ void schwinnic4bike::characteristicChanged(const QLowEnergyCharacteristic &chara
emit resistanceRead(Resistance.value());
} else {
Resistance = ResistanceFromFTMSAccessory.value();
m_pelotonResistance = bikeResistanceToPeloton(ResistanceFromFTMSAccessory.value());
}
lastRefreshCharacteristicChanged = now;
@@ -628,6 +630,21 @@ void schwinnic4bike::resistanceFromFTMSAccessory(resistance_t res) {
qDebug() << QStringLiteral("resistanceFromFTMSAccessory") << res;
}
double schwinnic4bike::bikeResistanceToPeloton(double bikeResistance) {
// brute-force inverse of pelotonToBikeResistance with current settings
double bestPeloton = 0;
double bestDiff = std::numeric_limits<double>::max();
for (int peloton = 0; peloton <= 100; peloton++) {
resistance_t converted = pelotonToBikeResistance(peloton);
double diff = qFabs((double)converted - bikeResistance);
if (diff < bestDiff) {
bestDiff = diff;
bestPeloton = peloton;
}
}
return bestPeloton;
}
/*
uint8_t schwinnic4bike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value() << power;

View File

@@ -83,6 +83,7 @@ class schwinnic4bike : public bike {
void resistanceFromFTMSAccessory(resistance_t res) override;
private slots:
double bikeResistanceToPeloton(double bikeResistance);
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);

View File

@@ -218,8 +218,9 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
uint8_t heart = 0;
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
static bool validCadenceFrom2ad2 = false;
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A5B)) {
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A5B) && !validCadenceFrom2ad2) {
lastPacket = newValue;
uint8_t index = 1;
@@ -599,6 +600,8 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
Cadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0;
if(Cadence.value() > 0)
validCadenceFrom2ad2 = true;
}
index += 2;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
@@ -750,7 +753,8 @@ void tacxneo2::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
if (characteristic.uuid() != QBluetoothUuid((quint16)0xFFF4) && characteristic.uuid() != QBluetoothUuid(QStringLiteral("6e40fec2-b5a3-f393-e0a9-e50e24dcca9e"))) {
if (characteristic.uuid() != QBluetoothUuid((quint16)0xFFF4) && characteristic.uuid() != QBluetoothUuid(QStringLiteral("6e40fec2-b5a3-f393-e0a9-e50e24dcca9e")) &&
THINK_X == false) { // THINK_X sends the crank revs in the power characteristic
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));

View File

@@ -6150,7 +6150,7 @@ void homeform::update() {
QString::number(((elliptical *)bluetoothManager->device())->lastRequestedResistance().value(), 'f', 0));
this->target_peloton_resistance->setValue(QString::number(
((elliptical *)bluetoothManager->device())->lastRequestedPelotonResistance().value(), 'f', 0));
this->resistance->setValue(QString::number(resistance));
this->resistance->setValue(QString::number(resistance, 'f', 0));
this->peloton_resistance->setSecondLine(
QStringLiteral("AVG: ") +
QString::number(((elliptical *)bluetoothManager->device())->pelotonResistance().average(), 'f', 0) +

View File

@@ -241,9 +241,11 @@ void qfit::save(const QString &filename, QList<SessionLine> session, BLUETOOTH_T
fit::SessionMesg sessionMesg;
sessionMesg.SetTimestamp(session.at(firstRealIndex).time.toSecsSinceEpoch() - 631065600L);
sessionMesg.SetStartTime(session.at(firstRealIndex).time.toSecsSinceEpoch() - 631065600L);
sessionMesg.SetTotalElapsedTime(session.last().elapsedTime);
sessionMesg.SetTotalTimerTime(session.last().time.toSecsSinceEpoch() -
session.at(firstRealIndex).time.toSecsSinceEpoch());
// Fixed: According to FIT spec, total_elapsed_time includes pauses (real-time duration)
// and total_timer_time excludes pauses (active time only)
sessionMesg.SetTotalElapsedTime(session.last().time.toSecsSinceEpoch() -
session.at(firstRealIndex).time.toSecsSinceEpoch());
sessionMesg.SetTotalTimerTime(session.last().elapsedTime);
sessionMesg.SetTotalDistance((session.last().distance - startingDistanceOffset) * 1000.0); // meters
sessionMesg.SetTotalCalories(session.last().calories);
sessionMesg.SetTotalMovingTime(session.last().elapsedTime);

View File

@@ -225,6 +225,7 @@ const QString QZSettings::nordictrack_treadmill_ultra_le = QStringLiteral("nordi
const QString QZSettings::proform_treadmill_carbon_tls = QStringLiteral("proform_treadmill_carbon_tls");
const QString QZSettings::proform_treadmill_995i = QStringLiteral("proform_treadmill_995i");
const QString QZSettings::nordictrack_series_7 = QStringLiteral("nordictrack_series_7");
const QString QZSettings::nordictrack_se7i = QStringLiteral("nordictrack_se7i");
const QString QZSettings::toorx_3_0 = QStringLiteral("toorx_3_0");
const QString QZSettings::toorx_65s_evo = QStringLiteral("toorx_65s_evo");
const QString QZSettings::jtx_fitness_sprint_treadmill = QStringLiteral("jtx_fitness_sprint_treadmill");
@@ -573,6 +574,7 @@ const QString QZSettings::computrainer_serialport = QStringLiteral("computrainer
const QString QZSettings::default_computrainer_serialport = QStringLiteral("");
const QString QZSettings::kettler_usb_serialport = QStringLiteral("kettler_usb_serialport");
const QString QZSettings::default_kettler_usb_serialport = QStringLiteral("");
const QString QZSettings::kettler_usb_baudrate = QStringLiteral("kettler_usb_baudrate");
const QString QZSettings::strava_virtual_activity = QStringLiteral("strava_virtual_activity");
const QString QZSettings::powr_sensor_running_cadence_half_on_strava =
QStringLiteral("powr_sensor_running_cadence_half_on_strava");
@@ -994,7 +996,7 @@ const QString QZSettings::proform_csx210 = QStringLiteral("proform_csx210");
const QString QZSettings::skandika_wiri_x2000_protocol = QStringLiteral("skandika_wiri_x2000_protocol");
const uint32_t allSettingsCount = 814;
const uint32_t allSettingsCount = 815;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1467,6 +1469,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton},
{QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport},
{QZSettings::kettler_usb_serialport, QZSettings::default_kettler_usb_serialport},
{QZSettings::kettler_usb_baudrate, QZSettings::default_kettler_usb_baudrate},
{QZSettings::strava_virtual_activity, QZSettings::default_strava_virtual_activity},
{QZSettings::powr_sensor_running_cadence_half_on_strava,
QZSettings::default_powr_sensor_running_cadence_half_on_strava},

View File

@@ -692,6 +692,8 @@ class QZSettings {
static constexpr bool default_proform_treadmill_995i = false;
static const QString nordictrack_series_7;
static constexpr bool default_nordictrack_series_7 = false;
static const QString nordictrack_se7i;
static constexpr bool default_nordictrack_se7i = false;
static const QString toorx_3_0;
static constexpr bool default_toorx_3_0 = false;
@@ -1640,6 +1642,8 @@ class QZSettings {
static const QString kettler_usb_serialport;
static const QString default_kettler_usb_serialport;
static const QString kettler_usb_baudrate;
static constexpr int default_kettler_usb_baudrate = 9600;
static const QString strava_virtual_activity;
static constexpr bool default_strava_virtual_activity = true;

View File

@@ -1214,7 +1214,9 @@ import Qt.labs.platform 1.1
property bool iconcept_ftms_treadmill_inclination_table: false
property bool skandika_wiri_x2000_protocol: true
property bool nordictrack_series_7: false
property string kettler_usb_serialport: ""
property string kettler_usb_serialport: ""
property int kettler_usb_baudrate: 9600
property bool nordictrack_se7i: false
}
@@ -4336,6 +4338,35 @@ import Qt.labs.platform 1.1
onClicked: { settings.kettler_usb_serialport = kettlerUsbSerialPortTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
}
}
RowLayout {
spacing: 10
Label {
id: labelKettlerUsbBaudrate
text: qsTr("Baudrate:")
Layout.fillWidth: true
}
ComboBox {
id: kettlerUsbBaudrateComboBox
model: [ "9600", "57600" ]
displayText: settings.kettler_usb_baudrate.toString()
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
console.log("kettler baudrate combobox activated" + kettlerUsbBaudrateComboBox.currentIndex)
displayText = kettlerUsbBaudrateComboBox.currentValue
}
}
Button {
id: okKettlerUsbBaudrateButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {
settings.kettler_usb_baudrate = parseInt(kettlerUsbBaudrateComboBox.displayText);
window.settings_restart_to_apply = true;
toast.show("Setting saved!");
}
}
}
}
@@ -9318,6 +9349,19 @@ import Qt.labs.platform 1.1
Layout.fillWidth: true
onClicked: { settings.nordictrack_elliptical_c7_5 = checked; window.settings_restart_to_apply = true; }
}
IndicatorOnlySwitch {
text: qsTr("NordicTrack Elliptical SE7i")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.nordictrack_se7i
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.nordictrack_se7i = checked; window.settings_restart_to_apply = true; }
}
RowLayout {
spacing: 10
Label {