Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests for bike power functions #2174

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
678facc
#2174 Added tests for bike power functions
drmason789 Mar 1, 2024
15d2753
#2174 restrict the samples when exploring the boundaries of the power…
drmason789 Mar 1, 2024
bf4bf1e
Merge remote-tracking branch 'remotes/origin/master' into bike_power_…
drmason789 Mar 7, 2024
c74fe53
#2174 moved erg test functionality into the new Erg folder from other PR
drmason789 Mar 7, 2024
6fc5022
Merge remote-tracking branch 'remotes/origin/master' into bike_power_…
drmason789 Mar 19, 2024
d9c9f29
Merge remote-tracking branch 'remotes/origin/master' into bike_power_…
drmason789 Mar 25, 2024
e335ac2
#2174 added minmax type and introduced minimum and maximum resistance…
drmason789 Mar 25, 2024
2ab31e8
#2174 avoid template in cpp
drmason789 Mar 26, 2024
6d76f98
#2174 added bike cadence limits.
drmason789 Mar 26, 2024
26d2aac
#2174
drmason789 Mar 26, 2024
439570b
#2174
drmason789 Mar 27, 2024
d2be67c
#2174 removed typo
drmason789 Mar 27, 2024
ce54799
#2174 consistent interface for bike::wattsFromResistance
drmason789 Mar 27, 2024
6e018a1
#2174 added tests for conversion to and from resistance and Peloton r…
drmason789 Mar 27, 2024
55cb801
#2174 increase use of minmax<T> in test code. Some work to make Pelot…
drmason789 Mar 29, 2024
30dfa77
Merge branch 'cagnulein:master' into bike_power_tests
drmason789 Mar 29, 2024
a561444
Merge branch 'cagnulein:master' into bike_power_tests
drmason789 Apr 2, 2024
2ca3644
#2174 replaced powerFromResistanceRequest with wattsFromResistance
drmason789 Apr 2, 2024
da9ebd8
Merge branch 'bike_power_tests' of https://github.com/drmason789/qdom…
drmason789 Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/devices/apexbike/apexbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,7 @@ void apexbike::update() {
}

if (requestResistance != -1) {
if (requestResistance > max_resistance)
requestResistance = max_resistance;
else if (requestResistance <= 0)
requestResistance = 1;
requestResistance = this->resistanceLimits().clip(requestResistance);

if (requestResistance != currentResistance().value()) {
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
Expand Down
4 changes: 3 additions & 1 deletion src/devices/apexbike/apexbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ class apexbike : public bike {
apexbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset, double bikeResistanceGain);
bool connected() override;

minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(1,32);}

private:
const resistance_t max_resistance = 32;

void btinit();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
Expand Down
62 changes: 58 additions & 4 deletions src/devices/bike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,21 @@ void bike::changeInclination(double grade, double percentage) {

// originally made for renphobike, but i guess it could be very generic
uint16_t bike::powerFromResistanceRequest(resistance_t requestResistance) {
auto minMaxR = this->resistanceLimits();
if(requestResistance<=minMaxR.min())
return 0;

// this bike has resistance level to N.m so the formula is Power (kW) = Torque (N.m) x Speed (RPM) / 9.5488
double cadence = RequestedCadence.value();
double cadence = this->RequestedCadence.value();
if (cadence <= 0)
cadence = Cadence.value();
return (requestResistance * cadence) / 9.5488;

if(cadence <= this->cadenceLimits().min())
return 0;

requestResistance = minMaxR.clip(requestResistance);

return (requestResistance * this->cadenceLimits().clip(cadence)) / 9.5488;
}

void bike::changeRequestedPelotonResistance(int8_t resistance) { RequestedPelotonResistance = resistance; }
Expand Down Expand Up @@ -103,8 +113,52 @@ uint8_t bike::fanSpeed() { return FanSpeed; }
bool bike::connected() { return false; }
uint16_t bike::watts() { return 0; }
metric bike::pelotonResistance() { return m_pelotonResistance; }
resistance_t bike::pelotonToBikeResistance(int pelotonResistance) { return pelotonResistance; }
resistance_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something

resistance_t bike::pelotonToBikeResistance(int pelotonResistance) {

auto minMaxR = this->resistanceLimits();

for (resistance_t i = minMaxR.min(); i < minMaxR.max(); i++) {
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) >= pelotonResistance) {
return i;
}
}
if (pelotonResistance < bikeResistanceToPeloton(minMaxR.min()))
return minMaxR.min();
else
return minMaxR.max();
}
resistance_t bike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();

QSettings settings;

double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();

auto minMaxR = this->resistanceLimits();

uint16_t power0 = (wattsFromResistance(minMaxR.min()) * watt_gain) + watt_offset;

// Is the requested power at or below the power of the minimum resistance the device provides?
if (power <= power0)
return minMaxR.min();

// Search from the 1st resistance level above minimum to the maximum
for (resistance_t i = 1 + minMaxR.min(); i < minMaxR.max(); i++) {
uint16_t power1 = wattsFromResistance(i)*watt_gain + watt_offset;

if(power0 <= power && power1>=power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << power0 << power1 << power;
return i;
}

power0 = power1;
}

// requested power requires resistance beyond the maximum
return minMaxR.max();
}
void bike::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power, false); }

Expand Down
3 changes: 3 additions & 0 deletions src/devices/bike.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ class bike : public bluetoothdevice {
bool connected() override;
virtual uint16_t watts();
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
virtual double bikeResistanceToPeloton(double resistance) { return resistance; }
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t wattsFromResistance(double resistance) { return this->powerFromResistanceRequest(resistance); }
virtual uint16_t powerFromResistanceRequest(resistance_t requestResistance);
virtual bool ergManagedBySS2K() { return false; }
bluetoothdevice::BLUETOOTH_TYPE deviceType() override;
Expand All @@ -48,6 +50,7 @@ class bike : public bluetoothdevice {
virtual bool inclinationAvailableByHardware();
bool ergModeSupportedAvailableByHardware() { return ergModeSupported; }

virtual minmax<int16_t> cadenceLimits() { return minmax<int16_t>(0, 250); }
public Q_SLOTS:
void changeResistance(resistance_t res) override;
virtual void changeCadence(int16_t cad);
Expand Down
11 changes: 0 additions & 11 deletions src/devices/bkoolbike/bkoolbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,17 +732,6 @@ void bkoolbike::controllerStateChanged(QLowEnergyController::ControllerState sta
}
}

resistance_t bkoolbike::pelotonToBikeResistance(int pelotonResistance) {
for (resistance_t i = 0; i < max_resistance; i++) {
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) >= pelotonResistance) {
return i;
}
}
if (pelotonResistance < bikeResistanceToPeloton(1))
return 0;
else
return max_resistance;
}

double bkoolbike::bikeResistanceToPeloton(double resistance) {
QSettings settings;
Expand Down
6 changes: 2 additions & 4 deletions src/devices/bkoolbike/bkoolbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,18 @@ class bkoolbike : public bike {
bkoolbike(bool noWriteResistance, bool noHeartService);
void changePower(int32_t power) override;
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance);

minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(1,100);}
private:
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceInclination(double inclination);
uint16_t watts() override;
double bikeResistanceToPeloton(double resistance);
double bikeResistanceToPeloton(double resistance) override;

QTimer *refresh;

const int max_resistance = 100;

QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyCharacteristic gattWriteCharCustomId;
Expand Down
2 changes: 1 addition & 1 deletion src/devices/bluetooth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2291,7 +2291,7 @@ void bluetooth::connectedAndDiscovered() {
#else
settings.setValue(QZSettings::ftms_accessory_address, b.deviceUuid().toString());
#endif
ftmsAccessory = new smartspin2k(false, false, this->device()->maxResistance(), (bike *)this->device());
ftmsAccessory = new smartspin2k(false, false, this->device()->resistanceLimits().max(), (bike *)this->device());
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));

connect(ftmsAccessory, &smartspin2k::debug, this, &bluetooth::debug);
Expand Down
1 change: 0 additions & 1 deletion src/devices/bluetoothdevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,6 @@ QStringList bluetoothdevice::metrics() {
return r;
}

resistance_t bluetoothdevice::maxResistance() { return 100; }

uint8_t bluetoothdevice::metrics_override_heartrate() {

Expand Down
13 changes: 12 additions & 1 deletion src/devices/bluetoothdevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define BLUETOOTHDEVICE_H

#include "definitions.h"
#include "minmax.h"
#include "metric.h"
#include "qzsettings.h"

Expand Down Expand Up @@ -420,10 +421,20 @@ class bluetoothdevice : public QObject {
*/
virtual uint8_t metrics_override_heartrate();

/**
* @brief Overridden in subclasses to specify the range of resistance levels supported by the device.
*/
virtual minmax<resistance_t> resistanceLimits() { return minmax<resistance_t>(0,100); }

/**
* @brief Overridden in subclasses to specify the maximum resistance level supported by the device.
*/
virtual resistance_t maxResistance();
// virtual resistance_t maxResistance() { return 100; }

/**
* @brief Overridden in subclasses to specify the minimum resistance level supported by the device.
*/
// virtual resistance_t minResistance() { return 0; }

public Q_SLOTS:
virtual void start();
Expand Down
6 changes: 1 addition & 5 deletions src/devices/chronobike/chronobike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,7 @@ void chronobike::update() {
}

if (requestResistance != -1) {
if (requestResistance > 15) {
requestResistance = 15;
} else if (requestResistance == 0) {
requestResistance = 1;
}
requestResistance = this->resistanceLimits().clip(requestResistance);

if (requestResistance != currentResistance().value()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
Expand Down
2 changes: 2 additions & 0 deletions src/devices/chronobike/chronobike.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class chronobike : public bike {
chronobike(bool noWriteResistance, bool noHeartService);
bool connected() override;

minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(1,15);}

private:
// void writeCharacteristic(uint8_t *data, uint8_t data_len, QString info, bool disable_log = false, // Unused
// bool wait_for_response = false);
Expand Down
28 changes: 2 additions & 26 deletions src/devices/computrainerbike/computrainerbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,6 @@ computrainerbike::computrainerbike(bool noWriteResistance, bool noHeartService,
// ********************************************************************************************************
}

resistance_t computrainerbike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();

QSettings settings;

double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();

for (resistance_t i = 1; i < max_resistance; i++) {
if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest")
<< ((wattsFromResistance(i) * watt_gain) + watt_offset)
<< ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
return i;
}
}
if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
return 1;
else
return max_resistance;
}

uint16_t computrainerbike::wattsFromResistance(resistance_t resistance) {

Expand Down Expand Up @@ -183,10 +161,8 @@ void computrainerbike::innerWriteResistance() {
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();

if (requestResistance != -1) {
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
} else if (requestResistance < min_resistance) {
requestResistance = min_resistance;
if(!this->resistanceLimits().contains(requestResistance)) {
requestResistance = resistanceLimits().clip(requestResistance);
} else if (requestResistance == 0) {
requestResistance = 1;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cagnulein What's going on with these few bikes that have this form of clipping of the resistance request?

i.e. here the min_resistance constant is -20 and the max_resistance constant is 100, and you clip the requestedResistance to that range, except if it's 0 then you make it 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also in the proformwifi and proformtelnet bikes.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min_resistance can't never be less than 0 in QZ. I mean I never encountered a bike with a negative resistance. So the current master can't handle this. That's why for inclination, instead, i'm using a -100 value as a "no action" value (because inclination can go lower than 0 (but not to -100 of course)).

did I answer to your question?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of the use of -1 and -100 to specify "no action" for resistance and inclination. But I don't know the purpose of the -20..100 range you're using here.

I'll use 1..100 for this exercise unless you specify something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, you might have noticed in some test code I've switched to C++17 and used the std::optional template. This enables you to specify something like

std::optional<resistance_t> resistanceRequest;

and use resistanceRequest.has_value() and resistanceRequest.reset() instead of having a "no action constant". If you are agreeable, I will do this for the resistance, inclination and power requests, in a different PR.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the delay @drmason789

the -20 in this case was copied by another project that I found so actually I didn't care about it. It could be that this bike can have also negative resistance (so it will make sense to use -100 for the resistance too). I didn't remember this.

and use resistanceRequest.has_value() and resistanceRequest.reset() instead of having a "no action constant". If you are agreeable, I will do this for the resistance, inclination and power requests, in a different PR.

yes sure! thanks!

Expand Down
7 changes: 2 additions & 5 deletions src/devices/computrainerbike/computrainerbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ class computrainerbike : public bike {
computrainerbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t resistanceFromPowerRequest(uint16_t power) override;
resistance_t maxResistance() override { return max_resistance; }

minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(-20,100);}
bool inclinationAvailableByHardware() override;
bool connected() override;

private:
resistance_t max_resistance = 100;
resistance_t min_resistance = -20;
uint16_t wattsFromResistance(resistance_t resistance);
double GetDistanceFromPacket(QByteArray packet);
QTime GetElapsedFromPacket(QByteArray packet);
Expand Down
6 changes: 1 addition & 5 deletions src/devices/cscbike/cscbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ void cscbike::update() {
}

if (requestResistance != -1) {
if (requestResistance > 15) {
requestResistance = 15;
} else if (requestResistance == 0) {
requestResistance = 1;
}
requestResistance = this->resistanceLimits().clip(requestResistance);

if (requestResistance != currentResistance().value()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
Expand Down
1 change: 1 addition & 0 deletions src/devices/cscbike/cscbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class cscbike : public bike {
cscbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice);
bool connected() override;

minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(1,15);}
private:
// void writeCharacteristic(uint8_t *data, uint8_t data_len, QString info, bool disable_log = false, //Unused
// bool wait_for_response = false);
Expand Down
16 changes: 6 additions & 10 deletions src/devices/domyosbike/domyosbike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,7 @@ void domyosbike::update() {
}

if (requestResistance != -1) {
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
} else if (requestResistance < 1) {
requestResistance = 1;
}
requestResistance = this->resistanceLimits().clip(requestResistance);

if (requestResistance != currentResistance().value()) {
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
Expand Down Expand Up @@ -391,7 +387,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
Resistance = 1;
}
emit resistanceRead(Resistance.value());
m_pelotonResistance = (Resistance.value() * 100) / max_resistance;
m_pelotonResistance = (Resistance.value() * 100) / this->resistanceLimits().max();

bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
Expand Down Expand Up @@ -666,21 +662,21 @@ bool domyosbike::connected() {
}

resistance_t domyosbike::pelotonToBikeResistance(int pelotonResistance) {
return (pelotonResistance * max_resistance) / 100;
return (pelotonResistance * this->resistanceLimits().max()) / 100;
}

resistance_t domyosbike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << currentCadence().value();

for (resistance_t i = 1; i < max_resistance; i++) {
for (resistance_t i = 1; i < this->resistanceLimits().max(); i++) {
if (wattsFromResistance(i) <= power && wattsFromResistance(i + 1) >= power) {
return i;
}
}
if (power < wattsFromResistance(1))
if (power < wattsFromResistance(this->resistanceLimits().min()))
return 1;
else
return max_resistance;
return this->resistanceLimits().max();
}

uint16_t domyosbike::wattsFromResistance(double resistance) {
Expand Down
4 changes: 2 additions & 2 deletions src/devices/domyosbike/domyosbike.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class domyosbike : public bike {
uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
resistance_t resistanceFromPowerRequest(uint16_t power) override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
minmax<resistance_t> resistanceLimits() override {return minmax<resistance_t>(1,15);}

~domyosbike() override;
bool connected() override;

Expand All @@ -59,7 +60,6 @@ class domyosbike : public bike {
void startDiscover();
uint16_t watts() override;

const resistance_t max_resistance = 15;
QTimer *refresh;
uint8_t firstVirtual = 0;
uint8_t firstStateChanged = 0;
Expand Down