Skip to content
Merged
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
70 changes: 66 additions & 4 deletions src/devices/cscbike/cscbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,24 @@ cscbike::cscbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevi
}

void cscbike::enableManualResistancePowerAdjustment(resistance_t resistance) {
if (!jorotoBike) {
if (!jorotoBike && !useCustomResistancePowerTable()) {
return;
}

resistance_t clampedResistance = qBound<resistance_t>(1, resistance, 15);
resistance_t clampedResistance =
jorotoBike ? qBound<resistance_t>(1, resistance, 15) : clampedCustomResistance(resistance);
manualResistanceTarget = clampedResistance;
manualResistancePowerAdjustmentActive = true;
Resistance = clampedResistance;
emit resistanceRead(Resistance.value());

if (!manualResistancePowerAdjustmentToastShown && homeform::singleton()) {
homeform::singleton()->setToastRequested(
QStringLiteral("Manual resistance power adjustment enabled: power now scales with the Resistance tile value."));
jorotoBike
? QStringLiteral(
"Manual resistance power adjustment enabled: power now scales with the Resistance tile value.")
: QStringLiteral(
"Custom CSC power table enabled: power now follows the configured resistance/watt points."));
manualResistancePowerAdjustmentToastShown = true;
}
}
Expand All @@ -60,10 +65,65 @@ uint16_t cscbike::manualResistanceAdjustedWatts() {
return qRound(cadenceOnlyWatts * manualResistancePowerMultiplier());
}

uint16_t cscbike::customResistanceAdjustedWatts() {
if (currentCadence().value() == 0) {
return 0;
}

QSettings settings;
const double resistanceLevel1 =
settings.value(QZSettings::cscbike_custom_resistance_level_1,
QZSettings::default_cscbike_custom_resistance_level_1)
.toDouble();
const double watt1 =
settings.value(QZSettings::cscbike_custom_watt_1, QZSettings::default_cscbike_custom_watt_1).toDouble();
const double resistanceLevel2 =
settings.value(QZSettings::cscbike_custom_resistance_level_2,
QZSettings::default_cscbike_custom_resistance_level_2)
.toDouble();
const double watt2 =
settings.value(QZSettings::cscbike_custom_watt_2, QZSettings::default_cscbike_custom_watt_2).toDouble();
const double resistance = clampedCustomResistance(manualResistanceTarget);

if (resistanceLevel1 == resistanceLevel2) {
return qMax(0, qRound((watt1 + watt2) / 2.0));
}

const double slope = (watt2 - watt1) / (resistanceLevel2 - resistanceLevel1);
const double watts = watt1 + ((resistance - resistanceLevel1) * slope);
return qMax(0, qRound(watts));
}

double cscbike::manualResistancePowerMultiplier() {
const double normalizedResistance = (qBound(1, static_cast<int>(manualResistanceTarget), 15) - 1) / 14.0;
return 1.0 + (normalizedResistance * normalizedResistance * 2.0);
}

bool cscbike::useCustomResistancePowerTable() const {
QSettings settings;
return settings
.value(QZSettings::cscbike_custom_resistance_power_table,
QZSettings::default_cscbike_custom_resistance_power_table)
.toBool();
}

resistance_t cscbike::clampedCustomResistance(resistance_t resistance) const {
QSettings settings;
int resistanceMin =
qRound(settings.value(QZSettings::zwift_erg_resistance_down,
QZSettings::default_zwift_erg_resistance_down)
.toDouble());
int resistanceMax =
qRound(settings.value(QZSettings::zwift_erg_resistance_up,
QZSettings::default_zwift_erg_resistance_up)
.toDouble());

if (resistanceMin > resistanceMax) {
qSwap(resistanceMin, resistanceMax);
}

return qBound(static_cast<resistance_t>(resistanceMin), resistance, static_cast<resistance_t>(resistanceMax));
}
/*
void cscbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool
wait_for_response)
Expand Down Expand Up @@ -111,8 +171,10 @@ void cscbike::update() {

bool rogue_echo_bike = settings.value(QZSettings::rogue_echo_bike, QZSettings::default_rogue_echo_bike).toBool();

if (manualResistancePowerAdjustmentActive) {
if (manualResistancePowerAdjustmentActive && jorotoBike) {
m_watt = manualResistanceAdjustedWatts();
} else if (manualResistancePowerAdjustmentActive && useCustomResistancePowerTable()) {
m_watt = customResistanceAdjustedWatts();
} else if (rogue_echo_bike) {
double rpm = currentCadence().value();
m_watt = 0.000602337 * pow(rpm, 3.11762) + 32.6404;
Expand Down
3 changes: 3 additions & 0 deletions src/devices/cscbike/cscbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class cscbike : public bike {
// bool wait_for_response = false);
void startDiscover();
uint16_t watts() override;
bool useCustomResistancePowerTable() const;
resistance_t clampedCustomResistance(resistance_t resistance) const;

QTimer *refresh;

Expand Down Expand Up @@ -82,6 +84,7 @@ class cscbike : public bike {
#endif

uint16_t manualResistanceAdjustedWatts();
uint16_t customResistanceAdjustedWatts();
double manualResistancePowerMultiplier();

signals:
Expand Down
12 changes: 11 additions & 1 deletion src/qzsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,11 @@ const QString QZSettings::default_cadence_sensor_name = QStringLiteral("Disabled
const QString QZSettings::cadence_sensor_as_bike = QStringLiteral("cadence_sensor_as_bike");
const QString QZSettings::cadence_sensor_as_treadmill = QStringLiteral("cadence_sensor_as_treadmill");
const QString QZSettings::cadence_sensor_speed_ratio = QStringLiteral("cadence_sensor_speed_ratio");
const QString QZSettings::cscbike_custom_resistance_power_table = QStringLiteral("cscbike_custom_resistance_power_table");
const QString QZSettings::cscbike_custom_resistance_level_1 = QStringLiteral("cscbike_custom_resistance_level_1");
const QString QZSettings::cscbike_custom_watt_1 = QStringLiteral("cscbike_custom_watt_1");
const QString QZSettings::cscbike_custom_resistance_level_2 = QStringLiteral("cscbike_custom_resistance_level_2");
const QString QZSettings::cscbike_custom_watt_2 = QStringLiteral("cscbike_custom_watt_2");
const QString QZSettings::power_hr_pwr1 = QStringLiteral("power_hr_pwr1");
const QString QZSettings::power_hr_hr1 = QStringLiteral("power_hr_hr1");
const QString QZSettings::power_hr_pwr2 = QStringLiteral("power_hr_pwr2");
Expand Down Expand Up @@ -1083,7 +1088,7 @@ const QString QZSettings::proform_carbon_tlx_v84_314_treadmill = QStringLiteral(
const QString QZSettings::proform_carbon_tl_PFTL59723_6 = QStringLiteral("proform_carbon_tl_PFTL59723_6");


const uint32_t allSettingsCount = 882;
const uint32_t allSettingsCount = 887;

QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
Expand Down Expand Up @@ -1352,6 +1357,11 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cadence_sensor_as_bike, QZSettings::default_cadence_sensor_as_bike},
{QZSettings::cadence_sensor_as_treadmill, QZSettings::default_cadence_sensor_as_treadmill},
{QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio},
{QZSettings::cscbike_custom_resistance_power_table, QZSettings::default_cscbike_custom_resistance_power_table},
{QZSettings::cscbike_custom_resistance_level_1, QZSettings::default_cscbike_custom_resistance_level_1},
{QZSettings::cscbike_custom_watt_1, QZSettings::default_cscbike_custom_watt_1},
{QZSettings::cscbike_custom_resistance_level_2, QZSettings::default_cscbike_custom_resistance_level_2},
{QZSettings::cscbike_custom_watt_2, QZSettings::default_cscbike_custom_watt_2},
{QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1},
{QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1},
{QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2},
Expand Down
15 changes: 15 additions & 0 deletions src/qzsettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,21 @@ class QZSettings {
static const QString cadence_sensor_speed_ratio;
static constexpr float default_cadence_sensor_speed_ratio = 0.33;

static const QString cscbike_custom_resistance_power_table;
static constexpr bool default_cscbike_custom_resistance_power_table = false;

static const QString cscbike_custom_resistance_level_1;
static constexpr float default_cscbike_custom_resistance_level_1 = 1;

static const QString cscbike_custom_watt_1;
static constexpr float default_cscbike_custom_watt_1 = 100;

static const QString cscbike_custom_resistance_level_2;
static constexpr float default_cscbike_custom_resistance_level_2 = 15;

static const QString cscbike_custom_watt_2;
static constexpr float default_cscbike_custom_watt_2 = 300;

static const QString power_hr_pwr1;
static constexpr float default_power_hr_pwr1 = 200;

Expand Down
137 changes: 137 additions & 0 deletions src/settings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,11 @@ import Qt.labs.platform 1.1
property double power_sensor_speed_inclination_coeff_a: 0.0
property double power_sensor_speed_inclination_coeff_b: 0.0
property bool proform_carbon_tlx_v84_314_treadmill: false
property bool cscbike_custom_resistance_power_table: false
property real cscbike_custom_resistance_level_1: 1
property real cscbike_custom_watt_1: 100
property real cscbike_custom_resistance_level_2: 15
property real cscbike_custom_watt_2: 300
}


Expand Down Expand Up @@ -12029,6 +12034,138 @@ import Qt.labs.platform 1.1
Layout.fillWidth: true
color: Material.color(Material.Lime)
}

IndicatorOnlySwitch {
text: qsTr("Custom CSC Resistance/Watt Table")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.cscbike_custom_resistance_power_table
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.cscbike_custom_resistance_power_table = checked; window.settings_restart_to_apply = true; }
}

Label {
text: qsTr("Enable a custom linear resistance/watt table for CSC bikes. Joroto bikes keep using their dedicated resistance power profile. Resistance is clamped using the existing Min. Resistance and Max. Resistance settings.")
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)
}

RowLayout {
spacing: 10
Label {
text: qsTr("Resistance Level 1:")
Layout.fillWidth: true
}
TextField {
id: cscBikeCustomResistanceLevel1TextField
text: settings.cscbike_custom_resistance_level_1
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: settings.cscbike_custom_resistance_level_1 = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.cscbike_custom_resistance_level_1 = cscBikeCustomResistanceLevel1TextField.text; toast.show("Setting saved!"); }
}
}

RowLayout {
spacing: 10
Label {
text: qsTr("Watt 1:")
Layout.fillWidth: true
}
TextField {
id: cscBikeCustomWatt1TextField
text: settings.cscbike_custom_watt_1
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: settings.cscbike_custom_watt_1 = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.cscbike_custom_watt_1 = cscBikeCustomWatt1TextField.text; toast.show("Setting saved!"); }
}
}

RowLayout {
spacing: 10
Label {
text: qsTr("Resistance Level 2:")
Layout.fillWidth: true
}
TextField {
id: cscBikeCustomResistanceLevel2TextField
text: settings.cscbike_custom_resistance_level_2
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: settings.cscbike_custom_resistance_level_2 = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.cscbike_custom_resistance_level_2 = cscBikeCustomResistanceLevel2TextField.text; toast.show("Setting saved!"); }
}
}

RowLayout {
spacing: 10
Label {
text: qsTr("Watt 2:")
Layout.fillWidth: true
}
TextField {
id: cscBikeCustomWatt2TextField
text: settings.cscbike_custom_watt_2
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: settings.cscbike_custom_watt_2 = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.cscbike_custom_watt_2 = cscBikeCustomWatt2TextField.text; toast.show("Setting saved!"); }
}
}

Label {
text: qsTr("QZ will build a linear equation from the two resistance/watt points and clamp the effective resistance using the existing Min. Resistance and Max. Resistance settings.")
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)
}
}
}

Expand Down
Loading