Skip to content

Commit

Permalink
Estimating Training Stress Score (TSS) #516
Browse files Browse the repository at this point in the history
  • Loading branch information
cagnulein committed Dec 4, 2024
1 parent 969476f commit a045d24
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 2 deletions.
3 changes: 2 additions & 1 deletion src/devices/bike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ void bike::clearStats() {
WattKg.clear(false);
for(int i=0; i<maxHeartZone(); i++) {
hrZonesSeconds[i].clear(false);
}
}
tssCalculator.reset();
}

void bike::setPaused(bool p) {
Expand Down
2 changes: 2 additions & 0 deletions src/devices/bike.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "devices/bluetoothdevice.h"
#include "virtualdevices/virtualbike.h"
#include "tss_calculator.h"
#include <QObject>

class bike : public bluetoothdevice {
Expand Down Expand Up @@ -43,6 +44,7 @@ class bike : public bluetoothdevice {
void setSpeedLimit(double speed) { m_speedLimit = speed; }
double speedLimit() { return m_speedLimit; }
virtual bool ifitCompatible() {return false;}
TSSCalculator tssCalculator;

/**
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
Expand Down
8 changes: 7 additions & 1 deletion src/devices/bluetoothdevice.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "devices/bluetoothdevice.h"
#include "bike.h"

#include <QFile>
#include <QSettings>
Expand Down Expand Up @@ -187,8 +188,13 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts, const b
!power_as_bike && !power_as_treadmill)
watt_calc = false;

if(deviceType() == bluetoothdevice::BIKE && !from_accessory) // append only if it's coming from the bike, not from the power sensor
if(deviceType() == bluetoothdevice::BIKE && !from_accessory) { // append only if it's coming from the bike, not from the power sensor
_ergTable.collectData(Cadence.value(), m_watt.value(), Resistance.value());
if(!paused) {
((bike*)this)->tssCalculator.addPowerData(m_watt.value());
qDebug() << "getTSS" << ((bike*)this)->tssCalculator.getTSS(elapsed.value());
}
}

if (!_firstUpdate && !paused) {
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {
Expand Down
1 change: 1 addition & 0 deletions src/qdomyos-zwift.pri
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ HEADERS += \
$$PWD/mqtt/qmqtttopicname.h \
$$PWD/mqtt/qmqtttype.h \
$$PWD/treadmillErgTable.h \
$$PWD/tss_calculator.h \
$$PWD/wheelcircumference.h \
QTelnet.h \
devices/bkoolbike/bkoolbike.h \
Expand Down
102 changes: 102 additions & 0 deletions src/tss_calculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// tss_calculator.h
#ifndef TSS_CALCULATOR_H
#define TSS_CALCULATOR_H

#include <vector>
#include <cmath>
#include <QSettings>
#include <QObject>
#include <QDateTime>
#include <QDebug>
#include "qzsettings.h"

class TSSCalculator : public QObject {
Q_OBJECT

private:
static const int ROLLING_WINDOW_SECONDS = 30;
struct PowerSample {
double watts;
qint64 timestamp;

PowerSample(double w = 0, qint64 ts = 0) : watts(w), timestamp(ts) {}
};

std::vector<PowerSample> powerHistory;
double rollingSum;
double totalPower4;
int sampleCount;
qint64 lastTimestamp = 0;

double getCurrentFTP() const {
QSettings settings;
return settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}

public:
explicit TSSCalculator(QObject *parent = nullptr) :
QObject(parent),
powerHistory(ROLLING_WINDOW_SECONDS),
rollingSum(0.0),
totalPower4(0.0),
sampleCount(0),
lastTimestamp(0)
{
}

void reset() {
powerHistory.clear();
powerHistory.resize(ROLLING_WINDOW_SECONDS);
rollingSum = 0.0;
totalPower4 = 0.0;
sampleCount = 0;
lastTimestamp = 0;
}

void addPowerData(double watts) {
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();

if (lastTimestamp == 0) {
lastTimestamp = timestamp;
return;
}

double intervalSeconds = (timestamp - lastTimestamp) / 1000.0;

if (intervalSeconds < 0.8 || intervalSeconds > 1.2) {
return;
}

rollingSum -= powerHistory[sampleCount % ROLLING_WINDOW_SECONDS].watts;
powerHistory[sampleCount % ROLLING_WINDOW_SECONDS] = PowerSample(watts, timestamp);
rollingSum += watts;

if (sampleCount >= ROLLING_WINDOW_SECONDS - 1) {
double avgPower = rollingSum / ROLLING_WINDOW_SECONDS;
totalPower4 += pow(avgPower, 4);
}

sampleCount++;
lastTimestamp = timestamp;
}

double getNormalizedPower() const {
if (sampleCount < ROLLING_WINDOW_SECONDS) return 0.0;
double avgPower4 = totalPower4 / (sampleCount - ROLLING_WINDOW_SECONDS + 1);
return pow(avgPower4, 0.25);
}

double getIntensityFactor() const {
double np = getNormalizedPower();
return np / getCurrentFTP();
}

double getTSS(int elapsedSeconds) const {
if (elapsedSeconds == 0 || sampleCount < ROLLING_WINDOW_SECONDS) return 0.0;
double if_ = getIntensityFactor();
double hours = elapsedSeconds / 3600.0;
return 100.0 * hours * if_ * if_;
}
};

#endif // TSS_CALCULATOR_H

0 comments on commit a045d24

Please sign in to comment.