Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/devices/bluetoothdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ class bluetoothdevice : public QObject {
*/
double wattsMetricforUI() {
QSettings settings;
if (paused && settings.value(QZSettings::instant_power_on_pause, QZSettings::default_instant_power_on_pause).toBool()) {
return wattsMetric().value();
}
bool power3s = settings.value(QZSettings::power_avg_3s, QZSettings::default_power_avg_3s).toBool();
bool power5s = settings.value(QZSettings::power_avg_5s, QZSettings::default_power_avg_5s).toBool();
if (power3s)
Expand Down
42 changes: 42 additions & 0 deletions src/devices/strydrunpowersensor/strydrunpowersensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,26 @@ void strydrunpowersensor::update() {
sec1Update = 0;
// updateDisplay(elapsed);
}

if (strydIndoorModeRequested && !strydIndoorModeConfirmed && strydIndoorModeRetries < 3 &&
strydIndoorModeRequestedAt.msecsTo(QDateTime::currentDateTime()) > 1000) {
qDebug() << QStringLiteral("Stryd5: no ack for Indoor mode switch, retrying");
requestStrydIndoorMode();
}
}
}

void strydrunpowersensor::requestStrydIndoorMode() {
if (!strydIndoorModeService || !strydIndoorModeChar.isValid())
return;

qDebug() << QStringLiteral("Stryd5: switching power mode to Indoor");
strydIndoorModeService->writeCharacteristic(strydIndoorModeChar, QByteArray::fromHex("1500"));
strydIndoorModeRequested = true;
strydIndoorModeRequestedAt = QDateTime::currentDateTime();
strydIndoorModeRetries++;
}

void strydrunpowersensor::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
Expand Down Expand Up @@ -506,6 +523,18 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
//emit verticalOscillationChanged(VerticalOscillationMM.value());
qDebug() << QStringLiteral("Current GroundContactMS:") << GroundContactMS.value();
qDebug() << QStringLiteral("Current VerticalOscillationMM:") << VerticalOscillationMM.value();
} else if (strydIndoorModeRequested && newValue.length() >= 3 && (uint8_t)newValue.at(0) == 0x15 &&
(uint8_t)newValue.at(1) == 0x00) {
// ack of the 15 00/15 01 mode switch written to 7e78aa20: byte 2 echoes back the confirmed
// mode (0x00 = Indoor, 0x01 = Outdoor)
uint8_t confirmedMode = (uint8_t)newValue.at(2);
if (confirmedMode == 0x00) {
strydIndoorModeConfirmed = true;
qDebug() << QStringLiteral("Stryd5: Indoor mode confirmed by the pod");
} else {
qDebug() << QStringLiteral("Stryd5: pod acked mode ") << confirmedMode
<< QStringLiteral(" instead of Indoor, will retry");
}
}
}

Expand Down Expand Up @@ -585,6 +614,19 @@ void strydrunpowersensor::stateChanged(QLowEnergyService::ServiceState state) {
if (c.uuid() == QBluetoothUuid::CSCMeasurement) {
cadenceChar = c;
}

// Stryd 5 broadcasts a power value that already reacts to the incline it detects on its own
// (confirmed via BLE sniffing, see #4480), which conflicts with QZ's own inclination gain
// formula. Switching the pod to Indoor mode makes it stop compensating for incline on its
// own, leaving QZ's formula as the only source of the incline adjustment.
if (bluetoothDevice.name().startsWith(QStringLiteral("Stryd5"), Qt::CaseInsensitive) &&
c.uuid() == QBluetoothUuid(QStringLiteral("7e78aa20-72cd-d3b8-a81f-5b7e589bea0f")) &&
(c.properties() & QLowEnergyCharacteristic::Write)) {
strydIndoorModeService = s;
strydIndoorModeChar = c;
requestStrydIndoorMode();
}

qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
auto descriptors_list = c.descriptors();
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
Expand Down
8 changes: 8 additions & 0 deletions src/devices/strydrunpowersensor/strydrunpowersensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ class strydrunpowersensor : public treadmill {

bool FORERUNNER = false;

QLowEnergyService *strydIndoorModeService = nullptr;
QLowEnergyCharacteristic strydIndoorModeChar;
bool strydIndoorModeRequested = false;
bool strydIndoorModeConfirmed = false;
uint8_t strydIndoorModeRetries = 0;
QDateTime strydIndoorModeRequestedAt;
void requestStrydIndoorMode();

#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Expand Down
23 changes: 20 additions & 3 deletions src/devices/treadmill.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,19 @@ void treadmill::changeSpeed(double speed) {

if(stryd_speed_instead_treadmill && Speed.value() > 0) {
double delta = (Speed.value() - rawSpeed.value());
double maxAllowedDelta = speed * 0.20; // 20% of the speed request
double correctionThreshold =
settings.value(QZSettings::power_sensor_speed_correction_threshold,
QZSettings::default_power_sensor_speed_correction_threshold)
.toDouble() /
100.0;
double maxAllowedDelta = speed * correctionThreshold;

if (std::abs(delta) <= maxAllowedDelta) {
qDebug() << "stryd_speed_instead_treadmill so override speed by " << delta;
speed -= delta;
} else {
qDebug() << "Delta" << delta << "exceeds 20% threshold of" << maxAllowedDelta << "- not applying correction";
qDebug() << "Delta" << delta << "exceeds" << (correctionThreshold * 100.0) << "% threshold of"
<< maxAllowedDelta << "- not applying correction";
}
}
qDebug() << "changeSpeed" << speed << autoResistanceEnable << m_difficult << m_difficult_offset << m_lastRawSpeedRequested;
Expand Down Expand Up @@ -165,6 +171,12 @@ void treadmill::update_metrics(bool watt_calc, const double watts, const bool fr
m_watt = 0;
WattKg = 0;
}
} else if (paused && settings.value(QZSettings::instant_power_on_pause, QZSettings::default_instant_power_on_pause)
.toBool()) {
if (watt_calc) {
m_watt = watts;
}
WattKg = m_watt.value() / settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
} else if (m_watt.value() > 0) {
m_watt = 0;
WattKg = 0;
Expand Down Expand Up @@ -287,7 +299,12 @@ double treadmill::requestedSpeed() { return requestSpeed; }
double treadmill::requestedInclination() { return requestInclination; }
double treadmill::currentTargetSpeed() { return targetSpeed; }

void treadmill::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
void treadmill::cadenceSensor(uint8_t cadence) {
if (Cadence.value() > 0 || cadence > 0) {
evaluateStepCount();
}
Cadence.setValue(cadence);
}
void treadmill::powerSensor(uint16_t power) {
double vwatts = 0;
if(power > 0) {
Expand Down
4 changes: 3 additions & 1 deletion src/qzsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ const QString QZSettings::power_sensor_name = QStringLiteral("power_sensor_name"
const QString QZSettings::default_power_sensor_name = QStringLiteral("Disabled");
const QString QZSettings::power_sensor_as_bike = QStringLiteral("power_sensor_as_bike");
const QString QZSettings::power_sensor_as_treadmill = QStringLiteral("power_sensor_as_treadmill");
const QString QZSettings::power_sensor_speed_correction_threshold = QStringLiteral("power_sensor_speed_correction_threshold");
const QString QZSettings::power_sensor_speed_inclination_coeff_a = QStringLiteral("power_sensor_speed_inclination_coeff_a");
const QString QZSettings::power_sensor_speed_inclination_coeff_b = QStringLiteral("power_sensor_speed_inclination_coeff_b");
const QString QZSettings::powr_sensor_running_cadence_double = QStringLiteral("powr_sensor_running_cadence_double");
Expand Down Expand Up @@ -1279,7 +1280,7 @@ const QString QZSettings::default_shortcut_start_stop = QStringLiteral("");
const QString QZSettings::shortcut_stop = QStringLiteral("shortcut_stop");
const QString QZSettings::default_shortcut_stop = QStringLiteral("");

const uint32_t allSettingsCount = 1001;
const uint32_t allSettingsCount = 1002;

QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
Expand Down Expand Up @@ -1569,6 +1570,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::power_sensor_name, QZSettings::default_power_sensor_name},
{QZSettings::power_sensor_as_bike, QZSettings::default_power_sensor_as_bike},
{QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill},
{QZSettings::power_sensor_speed_correction_threshold, QZSettings::default_power_sensor_speed_correction_threshold},
{QZSettings::power_sensor_speed_inclination_coeff_a, QZSettings::default_power_sensor_speed_inclination_coeff_a},
{QZSettings::power_sensor_speed_inclination_coeff_b, QZSettings::default_power_sensor_speed_inclination_coeff_b},
{QZSettings::powr_sensor_running_cadence_double, QZSettings::default_powr_sensor_running_cadence_double},
Expand Down
3 changes: 3 additions & 0 deletions src/qzsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,9 @@ class QZSettings {
static const QString power_sensor_as_treadmill;
static constexpr bool default_power_sensor_as_treadmill = false;

static const QString power_sensor_speed_correction_threshold;
static constexpr double default_power_sensor_speed_correction_threshold = 20.0;

static const QString power_sensor_speed_inclination_coeff_a;
static constexpr double default_power_sensor_speed_inclination_coeff_a = 0.0;

Expand Down
15 changes: 14 additions & 1 deletion src/settings-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://qdomyos-zwift.local/settings-catalog.schema.json",
"schemaVersion": 1,
"format": "qdomyos-zwift-settings-catalog",
"settingCount": 965,
"settingCount": 966,
"pages": [
{
"key": "page_custom_gear_table",
Expand Down Expand Up @@ -3394,6 +3394,19 @@
"defaultExpression": "false",
"options": null
},
{
"key": "power_sensor_speed_correction_threshold",
"name": "Power sensor speed correction threshold (%)",
"description": "Maximum allowed difference between power sensor speed and treadmill speed before QZ stops applying the relative correction. Default: 20%.",
"parent": "Power Sensor Options",
"type": "number",
"qmlType": "real",
"control": "text",
"visible": true,
"defaultValue": 20.0,
"defaultExpression": "20.0",
"options": null
},
{
"key": "powr_sensor_running_cadence_double",
"name": "Doubling Cadence for Run",
Expand Down
35 changes: 35 additions & 0 deletions src/settings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,7 @@ import AndroidStatusBar 1.0
property bool proform_treadmill_105_cst: false
property real trainprogram_pid_hr_pushy_zone_limit: 0.8
property real trainprogram_pid_hr_recovery_zone_limit: 60.0
property real power_sensor_speed_correction_threshold: 20.0
property bool rpe_feel_popup_enabled: false
property int zwiftplay_gear_ls1: 2
property int zwiftplay_gear_ls2: 2
Expand Down Expand Up @@ -13683,6 +13684,40 @@ import AndroidStatusBar 1.0
color: Material.color(Material.Lime)
}

Label {
text: qsTr("Power sensor speed correction threshold (%):")
Layout.fillWidth: true
}
RowLayout {
spacing: 10
TextField {
id: powerSensorSpeedCorrectionThresholdTextField
text: settings.power_sensor_speed_correction_threshold
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.power_sensor_speed_correction_threshold = powerSensorSpeedCorrectionThresholdTextField.text; toast.show("Setting saved!"); }
}
}

Label {
text: qsTr("Maximum allowed difference between power sensor speed and treadmill speed before QZ stops applying the relative correction. Default: 20%.")
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("Use inclination from the power sensor")
spacing: 0
Expand Down
Loading