mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
9 Commits
master
...
build-1057
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
648f798697 | ||
|
|
d69685224c | ||
|
|
7be7920fd4 | ||
|
|
a07d3d3e9f | ||
|
|
8f9270dca3 | ||
|
|
5654b2f950 | ||
|
|
b40839d466 | ||
|
|
1d61778db1 | ||
|
|
57172ec5dc |
@@ -532,6 +532,12 @@
|
||||
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */; };
|
||||
87DAE16A26E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */; };
|
||||
87DAE16B26E9FF5000B0527E /* moc_solef80treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */; };
|
||||
87DC27EA2D9BDB53007A1B9D /* echelonstairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */; };
|
||||
87DC27EB2D9BDB53007A1B9D /* stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */; };
|
||||
87DC27EE2D9BDB8F007A1B9D /* moc_stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */; };
|
||||
87DC27EF2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */; };
|
||||
87DC27F32D9BDC43007A1B9D /* moc_moxy5sensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */; };
|
||||
87DC27F42D9BDC43007A1B9D /* moxy5sensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */; };
|
||||
87DED80627D1273900BE4FBB /* filedownloader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DED80427D1273800BE4FBB /* filedownloader.cpp */; };
|
||||
87DED80827D1274600BE4FBB /* moc_filedownloader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DED80727D1274500BE4FBB /* moc_filedownloader.cpp */; };
|
||||
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DF68B625E2673600FCDA46 /* eslinkertreadmill.cpp */; };
|
||||
@@ -1559,6 +1565,15 @@
|
||||
87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_shuaa5treadmill.cpp; sourceTree = "<group>"; };
|
||||
87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr2treadmill.cpp; sourceTree = "<group>"; };
|
||||
87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_solef80treadmill.cpp; sourceTree = "<group>"; };
|
||||
87DC27E62D9BDB53007A1B9D /* echelonstairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = echelonstairclimber.h; path = ../src/devices/echelonstairclimber/echelonstairclimber.h; sourceTree = SOURCE_ROOT; };
|
||||
87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = echelonstairclimber.cpp; path = ../src/devices/echelonstairclimber/echelonstairclimber.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87DC27E82D9BDB53007A1B9D /* stairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stairclimber.h; path = ../src/devices/stairclimber.h; sourceTree = SOURCE_ROOT; };
|
||||
87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = stairclimber.cpp; path = ../src/devices/stairclimber.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonstairclimber.cpp; sourceTree = "<group>"; };
|
||||
87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_stairclimber.cpp; sourceTree = "<group>"; };
|
||||
87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_moxy5sensor.cpp; sourceTree = "<group>"; };
|
||||
87DC27F12D9BDC43007A1B9D /* moxy5sensor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = moxy5sensor.h; path = ../src/devices/moxy5sensor/moxy5sensor.h; sourceTree = SOURCE_ROOT; };
|
||||
87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = moxy5sensor.cpp; path = ../src/devices/moxy5sensor/moxy5sensor.cpp; sourceTree = SOURCE_ROOT; };
|
||||
87DED80427D1273800BE4FBB /* filedownloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = filedownloader.cpp; path = ../src/filedownloader.cpp; sourceTree = "<group>"; };
|
||||
87DED80527D1273900BE4FBB /* filedownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = filedownloader.h; path = ../src/filedownloader.h; sourceTree = "<group>"; };
|
||||
87DED80727D1274500BE4FBB /* moc_filedownloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_filedownloader.cpp; sourceTree = "<group>"; };
|
||||
@@ -2179,6 +2194,11 @@
|
||||
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */,
|
||||
87DC27F12D9BDC43007A1B9D /* moxy5sensor.h */,
|
||||
87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */,
|
||||
87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */,
|
||||
87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */,
|
||||
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */,
|
||||
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */,
|
||||
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */,
|
||||
@@ -2674,6 +2694,10 @@
|
||||
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
|
||||
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
|
||||
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
|
||||
87DC27E62D9BDB53007A1B9D /* echelonstairclimber.h */,
|
||||
87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */,
|
||||
87DC27E82D9BDB53007A1B9D /* stairclimber.h */,
|
||||
87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */,
|
||||
);
|
||||
name = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -3441,6 +3465,8 @@
|
||||
873824C027E64707004F1B46 /* moc_dirconmanager.cpp in Compile Sources */,
|
||||
2F0E70F826316F3600E11F3A /* virtualbike_zwift.swift in Compile Sources */,
|
||||
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */,
|
||||
87DC27EE2D9BDB8F007A1B9D /* moc_stairclimber.cpp in Compile Sources */,
|
||||
87DC27EF2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp in Compile Sources */,
|
||||
87062646259480B200D06586 /* ViewController.swift in Compile Sources */,
|
||||
87D269A425F535340076AA48 /* moc_m3ibike.cpp in Compile Sources */,
|
||||
87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */,
|
||||
@@ -3576,6 +3602,8 @@
|
||||
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */,
|
||||
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
|
||||
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
|
||||
87DC27F32D9BDC43007A1B9D /* moc_moxy5sensor.cpp in Compile Sources */,
|
||||
87DC27F42D9BDC43007A1B9D /* moxy5sensor.cpp in Compile Sources */,
|
||||
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
|
||||
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */,
|
||||
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */,
|
||||
@@ -3805,6 +3833,8 @@
|
||||
876BFCA027BE35D8001D7645 /* moc_proformelliptical.cpp in Compile Sources */,
|
||||
873824BD27E64707004F1B46 /* moc_resolver_p.cpp in Compile Sources */,
|
||||
876ED21A25C3E9010065F3DC /* moc_material.cpp in Compile Sources */,
|
||||
87DC27EA2D9BDB53007A1B9D /* echelonstairclimber.cpp in Compile Sources */,
|
||||
87DC27EB2D9BDB53007A1B9D /* stairclimber.cpp in Compile Sources */,
|
||||
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */,
|
||||
87D10552290996EA00B3935B /* mepanelbike.cpp in Compile Sources */,
|
||||
9D9484EED654597C394345DE /* moc_echelonconnectsport.cpp in Compile Sources */,
|
||||
@@ -4255,7 +4285,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
|
||||
@@ -4449,7 +4479,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -4679,7 +4709,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
@@ -4775,7 +4805,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 6335M7T29D;
|
||||
ENABLE_BITCODE = YES;
|
||||
@@ -4867,7 +4897,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4983,7 +5013,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1037;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
|
||||
ENABLE_BITCODE = YES;
|
||||
|
||||
@@ -193,7 +193,7 @@ void ftmsbike::zwiftPlayInit() {
|
||||
}
|
||||
|
||||
void ftmsbike::forcePower(int16_t requestPower) {
|
||||
if(resistance_lvl_mode) {
|
||||
if(resistance_lvl_mode || TITAN_7000) {
|
||||
forceResistance(resistanceFromPowerRequest(requestPower));
|
||||
} else {
|
||||
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
|
||||
@@ -239,11 +239,15 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
|
||||
QSettings settings;
|
||||
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
|
||||
resistance_lvl_mode == false && _3G_Cardio_RB == false && JFBK5_0 == false) {
|
||||
|
||||
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
|
||||
|
||||
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
|
||||
requestResistance = fr;
|
||||
|
||||
if(TITAN_7000)
|
||||
Resistance = requestResistance;
|
||||
|
||||
write[3] = ((uint16_t)requestResistance * 10) & 0xFF;
|
||||
write[4] = ((uint16_t)requestResistance * 10) >> 8;
|
||||
|
||||
@@ -585,7 +589,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
|
||||
if(BIKE_)
|
||||
d = d / 10.0;
|
||||
// for this bike, i will use the resistance that I set directly because the bike sends a different ratio.
|
||||
if(!SL010)
|
||||
if(!SL010 && !TITAN_7000)
|
||||
Resistance = d;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit resistanceRead(Resistance.value());
|
||||
@@ -792,9 +796,11 @@ settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).t
|
||||
}
|
||||
|
||||
if (Flags.resistanceLvl) {
|
||||
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
emit resistanceRead(Resistance.value());
|
||||
if(!TITAN_7000) {
|
||||
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
|
||||
(uint16_t)((uint8_t)newValue.at(index))));
|
||||
emit resistanceRead(Resistance.value());
|
||||
}
|
||||
index += 2;
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
} else if(!DU30_bike) {
|
||||
@@ -1327,6 +1333,9 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
REEBOK = true;
|
||||
max_resistance = 32;
|
||||
resistance_lvl_mode = true;
|
||||
} else if ((bluetoothDevice.name().toUpper().startsWith("Titan 7000"))) {
|
||||
qDebug() << QStringLiteral("Titan 7000 found");
|
||||
TITAN_7000 = true;
|
||||
}
|
||||
|
||||
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
|
||||
|
||||
@@ -139,6 +139,7 @@ class ftmsbike : public bike {
|
||||
bool LYDSTO = false;
|
||||
bool SL010 = false;
|
||||
bool REEBOK = false;
|
||||
bool TITAN_7000 = false;
|
||||
|
||||
uint8_t battery_level = 0;
|
||||
|
||||
|
||||
270
src/ergtable.h
270
src/ergtable.h
@@ -6,24 +6,82 @@
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <algorithm>
|
||||
#include "qzsettings.h"
|
||||
|
||||
struct ergDataPoint {
|
||||
uint16_t cadence = 0; // RPM
|
||||
uint16_t wattage = 0; // Watts
|
||||
uint16_t resistance = 0; // Some unit
|
||||
uint16_t cadence = 0;
|
||||
uint16_t wattage = 0;
|
||||
uint16_t resistance = 0;
|
||||
|
||||
ergDataPoint() = default;
|
||||
|
||||
ergDataPoint(uint16_t c, uint16_t w, uint16_t r) : cadence(c), wattage(w), resistance(r) {}
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(ergDataPoint)
|
||||
|
||||
struct CadenceResistancePair {
|
||||
uint16_t cadence;
|
||||
uint16_t resistance;
|
||||
|
||||
bool operator<(const CadenceResistancePair& other) const {
|
||||
if (resistance != other.resistance) return resistance < other.resistance;
|
||||
return cadence < other.cadence;
|
||||
}
|
||||
};
|
||||
|
||||
class WattageStats {
|
||||
public:
|
||||
static const int MAX_SAMPLES = 100;
|
||||
static const int MIN_SAMPLES_REQUIRED = 10;
|
||||
|
||||
void addSample(uint16_t wattage) {
|
||||
samples.append(wattage);
|
||||
if (samples.size() > MAX_SAMPLES) {
|
||||
samples.removeFirst();
|
||||
}
|
||||
medianNeedsUpdate = true;
|
||||
}
|
||||
|
||||
uint16_t getMedian() {
|
||||
if (!medianNeedsUpdate) return cachedMedian;
|
||||
if (samples.isEmpty()) return 0;
|
||||
|
||||
QList<uint16_t> sortedSamples = samples;
|
||||
std::sort(sortedSamples.begin(), sortedSamples.end());
|
||||
|
||||
int middle = sortedSamples.size() / 2;
|
||||
if (sortedSamples.size() % 2 == 0) {
|
||||
cachedMedian = (sortedSamples[middle-1] + sortedSamples[middle]) / 2;
|
||||
} else {
|
||||
cachedMedian = sortedSamples[middle];
|
||||
}
|
||||
|
||||
medianNeedsUpdate = false;
|
||||
return cachedMedian;
|
||||
}
|
||||
|
||||
int sampleCount() const {
|
||||
return samples.size();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
samples.clear();
|
||||
cachedMedian = 0;
|
||||
medianNeedsUpdate = true;
|
||||
}
|
||||
|
||||
private:
|
||||
QList<uint16_t> samples;
|
||||
uint16_t cachedMedian = 0;
|
||||
bool medianNeedsUpdate = true;
|
||||
};
|
||||
|
||||
class ergTable : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
public:
|
||||
ergTable(QObject *parent = nullptr) : QObject(parent) {
|
||||
loadSettings();
|
||||
}
|
||||
@@ -32,126 +90,139 @@ public:
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
wattageData.clear();
|
||||
consolidatedData.clear();
|
||||
lastResistanceValue = 0xFFFF;
|
||||
lastResistanceTime = QDateTime::currentDateTime();
|
||||
|
||||
// Clear the settings completely
|
||||
QSettings settings;
|
||||
settings.remove(QZSettings::ergDataPoints);
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
void collectData(uint16_t cadence, uint16_t wattage, uint16_t resistance, bool ignoreResistanceTiming = false) {
|
||||
if(resistance != lastResistanceValue) {
|
||||
if (resistance != lastResistanceValue) {
|
||||
qDebug() << "resistance changed";
|
||||
lastResistanceTime = QDateTime::currentDateTime();
|
||||
lastResistanceValue = resistance;
|
||||
}
|
||||
if(lastResistanceTime.msecsTo(QDateTime::currentDateTime()) < 1000 && ignoreResistanceTiming == false) {
|
||||
|
||||
if (lastResistanceTime.msecsTo(QDateTime::currentDateTime()) < 1000 && !ignoreResistanceTiming) {
|
||||
qDebug() << "skipping collecting data due to resistance changing too fast";
|
||||
return;
|
||||
}
|
||||
if (wattage > 0 && cadence > 0 && !ergDataPointExists(cadence, wattage, resistance)) {
|
||||
qDebug() << "newPointAdded" << "C" << cadence << "W" << wattage << "R" << resistance;
|
||||
ergDataPoint point(cadence, wattage, resistance);
|
||||
dataTable.append(point);
|
||||
saveergDataPoint(point); // Save each new point to QSettings
|
||||
} else {
|
||||
qDebug() << "discarded" << "C" << cadence << "W" << wattage << "R" << resistance;
|
||||
|
||||
if (wattage > 0 && cadence > 0) {
|
||||
CadenceResistancePair pair{cadence, resistance};
|
||||
wattageData[pair].addSample(wattage);
|
||||
|
||||
if (wattageData[pair].sampleCount() >= WattageStats::MIN_SAMPLES_REQUIRED) {
|
||||
updateDataTable(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double estimateWattage(uint16_t givenCadence, uint16_t givenResistance) {
|
||||
QList<ergDataPoint> filteredByResistance;
|
||||
double minResDiff = std::numeric_limits<double>::max();
|
||||
if (consolidatedData.isEmpty()) return 0;
|
||||
|
||||
// Initial filtering by resistance
|
||||
for (const ergDataPoint& point : dataTable) {
|
||||
double resDiff = std::abs(point.resistance - givenResistance);
|
||||
if (resDiff < minResDiff) {
|
||||
filteredByResistance.clear();
|
||||
filteredByResistance.append(point);
|
||||
minResDiff = resDiff;
|
||||
} else if (resDiff == minResDiff) {
|
||||
filteredByResistance.append(point);
|
||||
// Get all points with matching resistance
|
||||
QList<ergDataPoint> sameResPoints;
|
||||
for (const auto& point : consolidatedData) {
|
||||
if (point.resistance == givenResistance) {
|
||||
sameResPoints.append(point);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback search if no close resistance match is found
|
||||
if (filteredByResistance.isEmpty()) {
|
||||
double minSimilarity = std::numeric_limits<double>::max();
|
||||
ergDataPoint closestPoint;
|
||||
// If no exact resistance match, find closest resistance
|
||||
if (sameResPoints.isEmpty()) {
|
||||
uint16_t minResDiff = UINT16_MAX;
|
||||
uint16_t closestRes = 0;
|
||||
|
||||
for (const ergDataPoint& point : dataTable) {
|
||||
double cadenceDiff = std::abs(point.cadence - givenCadence);
|
||||
double resDiff = std::abs(point.resistance - givenResistance);
|
||||
// Weighted similarity measure: Giving more weight to resistance
|
||||
double similarity = resDiff * 2 + cadenceDiff;
|
||||
|
||||
if (similarity < minSimilarity) {
|
||||
minSimilarity = similarity;
|
||||
closestPoint = point;
|
||||
for (const auto& point : consolidatedData) {
|
||||
uint16_t resDiff = abs(int(point.resistance) - int(givenResistance));
|
||||
if (resDiff < minResDiff) {
|
||||
minResDiff = resDiff;
|
||||
closestRes = point.resistance;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "case1" << closestPoint.wattage;
|
||||
// Use the wattage of the closest match based on similarity
|
||||
return closestPoint.wattage;
|
||||
}
|
||||
|
||||
// Find lower and upper points based on cadence within the filtered list
|
||||
double lowerDiff = std::numeric_limits<double>::max();
|
||||
double upperDiff = std::numeric_limits<double>::max();
|
||||
ergDataPoint lowerPoint, upperPoint;
|
||||
|
||||
for (const ergDataPoint& point : filteredByResistance) {
|
||||
double cadenceDiff = std::abs(point.cadence - givenCadence);
|
||||
|
||||
if (point.cadence <= givenCadence && cadenceDiff < lowerDiff) {
|
||||
lowerDiff = cadenceDiff;
|
||||
lowerPoint = point;
|
||||
} else if (point.cadence > givenCadence && cadenceDiff < upperDiff) {
|
||||
upperDiff = cadenceDiff;
|
||||
upperPoint = point;
|
||||
for (const auto& point : consolidatedData) {
|
||||
if (point.resistance == closestRes) {
|
||||
sameResPoints.append(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double r;
|
||||
// Find points for interpolation
|
||||
double lowerWatts = 0, upperWatts = 0;
|
||||
uint16_t lowerCadence = 0, upperCadence = 0;
|
||||
|
||||
// Estimate wattage
|
||||
if (lowerDiff != std::numeric_limits<double>::max() && upperDiff != std::numeric_limits<double>::max() && lowerDiff !=0 && upperDiff != 0) {
|
||||
// Interpolation between lower and upper points
|
||||
double cadenceRatio = 1.0;
|
||||
if (upperPoint.cadence != lowerPoint.cadence) { // Avoid division by zero
|
||||
cadenceRatio = (givenCadence - lowerPoint.cadence) / (double)(upperPoint.cadence - lowerPoint.cadence);
|
||||
for (const auto& point : sameResPoints) {
|
||||
if (point.cadence <= givenCadence && point.cadence > lowerCadence) {
|
||||
lowerWatts = point.wattage;
|
||||
lowerCadence = point.cadence;
|
||||
}
|
||||
if (point.cadence >= givenCadence && (upperCadence == 0 || point.cadence < upperCadence)) {
|
||||
upperWatts = point.wattage;
|
||||
upperCadence = point.cadence;
|
||||
}
|
||||
r = lowerPoint.wattage + (upperPoint.wattage - lowerPoint.wattage) * cadenceRatio;
|
||||
//qDebug() << "case2" << r << lowerPoint.wattage << upperPoint.wattage << lowerPoint.cadence << upperPoint.cadence << cadenceRatio << lowerDiff << upperDiff;
|
||||
return r;
|
||||
} else if (lowerDiff == 0) {
|
||||
//qDebug() << "case3" << lowerPoint.wattage;
|
||||
return lowerPoint.wattage;
|
||||
} else if (upperDiff == 0) {
|
||||
//qDebug() << "case4" << upperPoint.wattage;
|
||||
return upperPoint.wattage;
|
||||
} else {
|
||||
r = (lowerDiff < upperDiff) ? lowerPoint.wattage : upperPoint.wattage;
|
||||
//qDebug() << "case5" << r;
|
||||
// Use the closest point if only one match is found
|
||||
return r;
|
||||
}
|
||||
|
||||
// Interpolate or use closest value
|
||||
if (lowerCadence != 0 && upperCadence != 0 && lowerCadence != upperCadence) {
|
||||
double ratio = (givenCadence - lowerCadence) / double(upperCadence - lowerCadence);
|
||||
return lowerWatts + ratio * (upperWatts - lowerWatts);
|
||||
} else if (lowerCadence != 0) {
|
||||
return lowerWatts;
|
||||
} else if (upperCadence != 0) {
|
||||
return upperWatts;
|
||||
}
|
||||
|
||||
// Fallback to closest point
|
||||
return sameResPoints.first().wattage;
|
||||
}
|
||||
|
||||
QList<ergDataPoint> getConsolidatedData() const {
|
||||
return consolidatedData;
|
||||
}
|
||||
|
||||
private:
|
||||
QList<ergDataPoint> dataTable;
|
||||
QMap<CadenceResistancePair, WattageStats> getWattageData() const {
|
||||
return wattageData;
|
||||
}
|
||||
|
||||
private:
|
||||
QMap<CadenceResistancePair, WattageStats> wattageData;
|
||||
QList<ergDataPoint> consolidatedData;
|
||||
uint16_t lastResistanceValue = 0xFFFF;
|
||||
QDateTime lastResistanceTime = QDateTime::currentDateTime();
|
||||
|
||||
bool ergDataPointExists(uint16_t cadence, uint16_t wattage, uint16_t resistance) {
|
||||
for (const ergDataPoint& point : dataTable) {
|
||||
if (point.cadence == cadence && point.resistance == resistance && cadence != 0 && wattage != 0) {
|
||||
return true; // Found duplicate
|
||||
void updateDataTable(const CadenceResistancePair& pair) {
|
||||
uint16_t medianWattage = wattageData[pair].getMedian();
|
||||
|
||||
// Remove existing point if it exists
|
||||
for (int i = consolidatedData.size() - 1; i >= 0; --i) {
|
||||
if (consolidatedData[i].cadence == pair.cadence &&
|
||||
consolidatedData[i].resistance == pair.resistance) {
|
||||
consolidatedData.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false; // No duplicate
|
||||
|
||||
// Add new point
|
||||
consolidatedData.append(ergDataPoint(pair.cadence, medianWattage, pair.resistance));
|
||||
qDebug() << "Added/Updated point:"
|
||||
<< "C:" << pair.cadence
|
||||
<< "W:" << medianWattage
|
||||
<< "R:" << pair.resistance;
|
||||
}
|
||||
|
||||
void loadSettings() {
|
||||
QSettings settings;
|
||||
QString data = settings.value(QZSettings::ergDataPoints, QZSettings::default_ergDataPoints).toString();
|
||||
QStringList dataList = data.split(";");
|
||||
QString data = settings.value(QZSettings::ergDataPoints,
|
||||
QZSettings::default_ergDataPoints).toString();
|
||||
QStringList dataList = data.split(";", Qt::SkipEmptyParts);
|
||||
|
||||
for (const QString& triple : dataList) {
|
||||
QStringList fields = triple.split("|");
|
||||
@@ -159,29 +230,22 @@ private:
|
||||
uint16_t cadence = fields[0].toUInt();
|
||||
uint16_t wattage = fields[1].toUInt();
|
||||
uint16_t resistance = fields[2].toUInt();
|
||||
|
||||
//qDebug() << "inputs.append(ergDataPoint(" << cadence << ", " << wattage << ", "<< resistance << "));";
|
||||
|
||||
dataTable.append(ergDataPoint(cadence, wattage, resistance));
|
||||
consolidatedData.append(ergDataPoint(cadence, wattage, resistance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void saveSettings() {
|
||||
QSettings settings;
|
||||
QString data;
|
||||
for (const ergDataPoint& point : dataTable) {
|
||||
QString triple = QString::number(point.cadence) + "|" + QString::number(point.wattage) + "|" + QString::number(point.resistance);
|
||||
data += triple + ";";
|
||||
}
|
||||
settings.setValue(QZSettings::ergDataPoints, data);
|
||||
}
|
||||
QStringList dataStrings;
|
||||
|
||||
void saveergDataPoint(const ergDataPoint& point) {
|
||||
QSettings settings;
|
||||
QString data = settings.value(QZSettings::ergDataPoints, QZSettings::default_ergDataPoints).toString();
|
||||
data += QString::number(point.cadence) + "|" + QString::number(point.wattage) + "|" + QString::number(point.resistance) + ";";
|
||||
settings.setValue(QZSettings::ergDataPoints, data);
|
||||
for (const ergDataPoint& point : consolidatedData) {
|
||||
dataStrings.append(QString("%1|%2|%3").arg(point.cadence)
|
||||
.arg(point.wattage)
|
||||
.arg(point.resistance));
|
||||
}
|
||||
|
||||
settings.setValue(QZSettings::ergDataPoints, dataStrings.join(";"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user