Skip to content

Commit 88c97c0

Browse files
authored
custom watt table for cscbike (#4590)
1 parent 784f67a commit 88c97c0

5 files changed

Lines changed: 232 additions & 5 deletions

File tree

src/devices/cscbike/cscbike.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,24 @@ cscbike::cscbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevi
3030
}
3131

3232
void cscbike::enableManualResistancePowerAdjustment(resistance_t resistance) {
33-
if (!jorotoBike) {
33+
if (!jorotoBike && !useCustomResistancePowerTable()) {
3434
return;
3535
}
3636

37-
resistance_t clampedResistance = qBound<resistance_t>(1, resistance, 15);
37+
resistance_t clampedResistance =
38+
jorotoBike ? qBound<resistance_t>(1, resistance, 15) : clampedCustomResistance(resistance);
3839
manualResistanceTarget = clampedResistance;
3940
manualResistancePowerAdjustmentActive = true;
4041
Resistance = clampedResistance;
4142
emit resistanceRead(Resistance.value());
4243

4344
if (!manualResistancePowerAdjustmentToastShown && homeform::singleton()) {
4445
homeform::singleton()->setToastRequested(
45-
QStringLiteral("Manual resistance power adjustment enabled: power now scales with the Resistance tile value."));
46+
jorotoBike
47+
? QStringLiteral(
48+
"Manual resistance power adjustment enabled: power now scales with the Resistance tile value.")
49+
: QStringLiteral(
50+
"Custom CSC power table enabled: power now follows the configured resistance/watt points."));
4651
manualResistancePowerAdjustmentToastShown = true;
4752
}
4853
}
@@ -60,10 +65,65 @@ uint16_t cscbike::manualResistanceAdjustedWatts() {
6065
return qRound(cadenceOnlyWatts * manualResistancePowerMultiplier());
6166
}
6267

68+
uint16_t cscbike::customResistanceAdjustedWatts() {
69+
if (currentCadence().value() == 0) {
70+
return 0;
71+
}
72+
73+
QSettings settings;
74+
const double resistanceLevel1 =
75+
settings.value(QZSettings::cscbike_custom_resistance_level_1,
76+
QZSettings::default_cscbike_custom_resistance_level_1)
77+
.toDouble();
78+
const double watt1 =
79+
settings.value(QZSettings::cscbike_custom_watt_1, QZSettings::default_cscbike_custom_watt_1).toDouble();
80+
const double resistanceLevel2 =
81+
settings.value(QZSettings::cscbike_custom_resistance_level_2,
82+
QZSettings::default_cscbike_custom_resistance_level_2)
83+
.toDouble();
84+
const double watt2 =
85+
settings.value(QZSettings::cscbike_custom_watt_2, QZSettings::default_cscbike_custom_watt_2).toDouble();
86+
const double resistance = clampedCustomResistance(manualResistanceTarget);
87+
88+
if (resistanceLevel1 == resistanceLevel2) {
89+
return qMax(0, qRound((watt1 + watt2) / 2.0));
90+
}
91+
92+
const double slope = (watt2 - watt1) / (resistanceLevel2 - resistanceLevel1);
93+
const double watts = watt1 + ((resistance - resistanceLevel1) * slope);
94+
return qMax(0, qRound(watts));
95+
}
96+
6397
double cscbike::manualResistancePowerMultiplier() {
6498
const double normalizedResistance = (qBound(1, static_cast<int>(manualResistanceTarget), 15) - 1) / 14.0;
6599
return 1.0 + (normalizedResistance * normalizedResistance * 2.0);
66100
}
101+
102+
bool cscbike::useCustomResistancePowerTable() const {
103+
QSettings settings;
104+
return settings
105+
.value(QZSettings::cscbike_custom_resistance_power_table,
106+
QZSettings::default_cscbike_custom_resistance_power_table)
107+
.toBool();
108+
}
109+
110+
resistance_t cscbike::clampedCustomResistance(resistance_t resistance) const {
111+
QSettings settings;
112+
int resistanceMin =
113+
qRound(settings.value(QZSettings::zwift_erg_resistance_down,
114+
QZSettings::default_zwift_erg_resistance_down)
115+
.toDouble());
116+
int resistanceMax =
117+
qRound(settings.value(QZSettings::zwift_erg_resistance_up,
118+
QZSettings::default_zwift_erg_resistance_up)
119+
.toDouble());
120+
121+
if (resistanceMin > resistanceMax) {
122+
qSwap(resistanceMin, resistanceMax);
123+
}
124+
125+
return qBound(static_cast<resistance_t>(resistanceMin), resistance, static_cast<resistance_t>(resistanceMax));
126+
}
67127
/*
68128
void cscbike::writeCharacteristic(uint8_t* data, uint8_t data_len, QString info, bool disable_log, bool
69129
wait_for_response)
@@ -111,8 +171,10 @@ void cscbike::update() {
111171

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

114-
if (manualResistancePowerAdjustmentActive) {
174+
if (manualResistancePowerAdjustmentActive && jorotoBike) {
115175
m_watt = manualResistanceAdjustedWatts();
176+
} else if (manualResistancePowerAdjustmentActive && useCustomResistancePowerTable()) {
177+
m_watt = customResistanceAdjustedWatts();
116178
} else if (rogue_echo_bike) {
117179
double rpm = currentCadence().value();
118180
m_watt = 0.000602337 * pow(rpm, 3.11762) + 32.6404;

src/devices/cscbike/cscbike.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class cscbike : public bike {
4545
// bool wait_for_response = false);
4646
void startDiscover();
4747
uint16_t watts() override;
48+
bool useCustomResistancePowerTable() const;
49+
resistance_t clampedCustomResistance(resistance_t resistance) const;
4850

4951
QTimer *refresh;
5052

@@ -82,6 +84,7 @@ class cscbike : public bike {
8284
#endif
8385

8486
uint16_t manualResistanceAdjustedWatts();
87+
uint16_t customResistanceAdjustedWatts();
8588
double manualResistancePowerMultiplier();
8689

8790
signals:

src/qzsettings.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ const QString QZSettings::default_cadence_sensor_name = QStringLiteral("Disabled
329329
const QString QZSettings::cadence_sensor_as_bike = QStringLiteral("cadence_sensor_as_bike");
330330
const QString QZSettings::cadence_sensor_as_treadmill = QStringLiteral("cadence_sensor_as_treadmill");
331331
const QString QZSettings::cadence_sensor_speed_ratio = QStringLiteral("cadence_sensor_speed_ratio");
332+
const QString QZSettings::cscbike_custom_resistance_power_table = QStringLiteral("cscbike_custom_resistance_power_table");
333+
const QString QZSettings::cscbike_custom_resistance_level_1 = QStringLiteral("cscbike_custom_resistance_level_1");
334+
const QString QZSettings::cscbike_custom_watt_1 = QStringLiteral("cscbike_custom_watt_1");
335+
const QString QZSettings::cscbike_custom_resistance_level_2 = QStringLiteral("cscbike_custom_resistance_level_2");
336+
const QString QZSettings::cscbike_custom_watt_2 = QStringLiteral("cscbike_custom_watt_2");
332337
const QString QZSettings::power_hr_pwr1 = QStringLiteral("power_hr_pwr1");
333338
const QString QZSettings::power_hr_hr1 = QStringLiteral("power_hr_hr1");
334339
const QString QZSettings::power_hr_pwr2 = QStringLiteral("power_hr_pwr2");
@@ -1083,7 +1088,7 @@ const QString QZSettings::proform_carbon_tlx_v84_314_treadmill = QStringLiteral(
10831088
const QString QZSettings::proform_carbon_tl_PFTL59723_6 = QStringLiteral("proform_carbon_tl_PFTL59723_6");
10841089

10851090

1086-
const uint32_t allSettingsCount = 882;
1091+
const uint32_t allSettingsCount = 887;
10871092

10881093
QVariant allSettings[allSettingsCount][2] = {
10891094
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1352,6 +1357,11 @@ QVariant allSettings[allSettingsCount][2] = {
13521357
{QZSettings::cadence_sensor_as_bike, QZSettings::default_cadence_sensor_as_bike},
13531358
{QZSettings::cadence_sensor_as_treadmill, QZSettings::default_cadence_sensor_as_treadmill},
13541359
{QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio},
1360+
{QZSettings::cscbike_custom_resistance_power_table, QZSettings::default_cscbike_custom_resistance_power_table},
1361+
{QZSettings::cscbike_custom_resistance_level_1, QZSettings::default_cscbike_custom_resistance_level_1},
1362+
{QZSettings::cscbike_custom_watt_1, QZSettings::default_cscbike_custom_watt_1},
1363+
{QZSettings::cscbike_custom_resistance_level_2, QZSettings::default_cscbike_custom_resistance_level_2},
1364+
{QZSettings::cscbike_custom_watt_2, QZSettings::default_cscbike_custom_watt_2},
13551365
{QZSettings::power_hr_pwr1, QZSettings::default_power_hr_pwr1},
13561366
{QZSettings::power_hr_hr1, QZSettings::default_power_hr_hr1},
13571367
{QZSettings::power_hr_pwr2, QZSettings::default_power_hr_pwr2},

src/qzsettings.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,21 @@ class QZSettings {
969969
static const QString cadence_sensor_speed_ratio;
970970
static constexpr float default_cadence_sensor_speed_ratio = 0.33;
971971

972+
static const QString cscbike_custom_resistance_power_table;
973+
static constexpr bool default_cscbike_custom_resistance_power_table = false;
974+
975+
static const QString cscbike_custom_resistance_level_1;
976+
static constexpr float default_cscbike_custom_resistance_level_1 = 1;
977+
978+
static const QString cscbike_custom_watt_1;
979+
static constexpr float default_cscbike_custom_watt_1 = 100;
980+
981+
static const QString cscbike_custom_resistance_level_2;
982+
static constexpr float default_cscbike_custom_resistance_level_2 = 15;
983+
984+
static const QString cscbike_custom_watt_2;
985+
static constexpr float default_cscbike_custom_watt_2 = 300;
986+
972987
static const QString power_hr_pwr1;
973988
static constexpr float default_power_hr_pwr1 = 200;
974989

src/settings.qml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,11 @@ import Qt.labs.platform 1.1
13131313
property double power_sensor_speed_inclination_coeff_a: 0.0
13141314
property double power_sensor_speed_inclination_coeff_b: 0.0
13151315
property bool proform_carbon_tlx_v84_314_treadmill: false
1316+
property bool cscbike_custom_resistance_power_table: false
1317+
property real cscbike_custom_resistance_level_1: 1
1318+
property real cscbike_custom_watt_1: 100
1319+
property real cscbike_custom_resistance_level_2: 15
1320+
property real cscbike_custom_watt_2: 300
13161321
}
13171322

13181323

@@ -12029,6 +12034,138 @@ import Qt.labs.platform 1.1
1202912034
Layout.fillWidth: true
1203012035
color: Material.color(Material.Lime)
1203112036
}
12037+
12038+
IndicatorOnlySwitch {
12039+
text: qsTr("Custom CSC Resistance/Watt Table")
12040+
spacing: 0
12041+
bottomPadding: 0
12042+
topPadding: 0
12043+
rightPadding: 0
12044+
leftPadding: 0
12045+
clip: false
12046+
checked: settings.cscbike_custom_resistance_power_table
12047+
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
12048+
Layout.fillWidth: true
12049+
onClicked: { settings.cscbike_custom_resistance_power_table = checked; window.settings_restart_to_apply = true; }
12050+
}
12051+
12052+
Label {
12053+
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.")
12054+
font.bold: true
12055+
font.italic: true
12056+
font.pixelSize: Qt.application.font.pixelSize - 2
12057+
textFormat: Text.PlainText
12058+
wrapMode: Text.WordWrap
12059+
verticalAlignment: Text.AlignVCenter
12060+
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
12061+
Layout.fillWidth: true
12062+
color: Material.color(Material.Lime)
12063+
}
12064+
12065+
RowLayout {
12066+
spacing: 10
12067+
Label {
12068+
text: qsTr("Resistance Level 1:")
12069+
Layout.fillWidth: true
12070+
}
12071+
TextField {
12072+
id: cscBikeCustomResistanceLevel1TextField
12073+
text: settings.cscbike_custom_resistance_level_1
12074+
horizontalAlignment: Text.AlignRight
12075+
Layout.fillHeight: false
12076+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12077+
inputMethodHints: Qt.ImhFormattedNumbersOnly
12078+
onAccepted: settings.cscbike_custom_resistance_level_1 = text
12079+
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
12080+
}
12081+
Button {
12082+
text: "OK"
12083+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12084+
onClicked: { settings.cscbike_custom_resistance_level_1 = cscBikeCustomResistanceLevel1TextField.text; toast.show("Setting saved!"); }
12085+
}
12086+
}
12087+
12088+
RowLayout {
12089+
spacing: 10
12090+
Label {
12091+
text: qsTr("Watt 1:")
12092+
Layout.fillWidth: true
12093+
}
12094+
TextField {
12095+
id: cscBikeCustomWatt1TextField
12096+
text: settings.cscbike_custom_watt_1
12097+
horizontalAlignment: Text.AlignRight
12098+
Layout.fillHeight: false
12099+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12100+
inputMethodHints: Qt.ImhFormattedNumbersOnly
12101+
onAccepted: settings.cscbike_custom_watt_1 = text
12102+
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
12103+
}
12104+
Button {
12105+
text: "OK"
12106+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12107+
onClicked: { settings.cscbike_custom_watt_1 = cscBikeCustomWatt1TextField.text; toast.show("Setting saved!"); }
12108+
}
12109+
}
12110+
12111+
RowLayout {
12112+
spacing: 10
12113+
Label {
12114+
text: qsTr("Resistance Level 2:")
12115+
Layout.fillWidth: true
12116+
}
12117+
TextField {
12118+
id: cscBikeCustomResistanceLevel2TextField
12119+
text: settings.cscbike_custom_resistance_level_2
12120+
horizontalAlignment: Text.AlignRight
12121+
Layout.fillHeight: false
12122+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12123+
inputMethodHints: Qt.ImhFormattedNumbersOnly
12124+
onAccepted: settings.cscbike_custom_resistance_level_2 = text
12125+
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
12126+
}
12127+
Button {
12128+
text: "OK"
12129+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12130+
onClicked: { settings.cscbike_custom_resistance_level_2 = cscBikeCustomResistanceLevel2TextField.text; toast.show("Setting saved!"); }
12131+
}
12132+
}
12133+
12134+
RowLayout {
12135+
spacing: 10
12136+
Label {
12137+
text: qsTr("Watt 2:")
12138+
Layout.fillWidth: true
12139+
}
12140+
TextField {
12141+
id: cscBikeCustomWatt2TextField
12142+
text: settings.cscbike_custom_watt_2
12143+
horizontalAlignment: Text.AlignRight
12144+
Layout.fillHeight: false
12145+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12146+
inputMethodHints: Qt.ImhFormattedNumbersOnly
12147+
onAccepted: settings.cscbike_custom_watt_2 = text
12148+
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
12149+
}
12150+
Button {
12151+
text: "OK"
12152+
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
12153+
onClicked: { settings.cscbike_custom_watt_2 = cscBikeCustomWatt2TextField.text; toast.show("Setting saved!"); }
12154+
}
12155+
}
12156+
12157+
Label {
12158+
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.")
12159+
font.bold: true
12160+
font.italic: true
12161+
font.pixelSize: Qt.application.font.pixelSize - 2
12162+
textFormat: Text.PlainText
12163+
wrapMode: Text.WordWrap
12164+
verticalAlignment: Text.AlignVCenter
12165+
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
12166+
Layout.fillWidth: true
12167+
color: Material.color(Material.Lime)
12168+
}
1203212169
}
1203312170
}
1203412171

0 commit comments

Comments
 (0)