Files
qdomyos-zwift/src/trainprogram.cpp
Roberto Viola 63404b8088 it works!
2024-11-15 12:20:37 +01:00

1736 lines
80 KiB
C++

#include "trainprogram.h"
#include "zwiftworkout.h"
#include <QFile>
#include <QMutexLocker>
#include <QtXml/QtXml>
#include <algorithm>
#include <chrono>
#ifdef Q_OS_ANDROID
#include "androidactivityresultreceiver.h"
#include "keepawakehelper.h"
#include <QAndroidJniObject>
#elif defined(Q_OS_WINDOWS)
#include "windows_zwift_incline_paddleocr_thread.h"
#include "windows_zwift_workout_paddleocr_thread.h"
#endif
#ifdef Q_CC_MSVC
#include "zwift-api/zwift_messages.pb.h"
#endif
#include "localipaddress.h"
using namespace std::chrono_literals;
trainprogram::trainprogram(const QList<trainrow> &rows, bluetooth *b, QString *description, QString *tags,
bool videoAvailable) {
QSettings settings;
bool treadmill_force_speed =
settings.value(QZSettings::treadmill_force_speed, QZSettings::default_treadmill_force_speed).toBool();
this->bluetoothManager = b;
this->rows = rows;
this->loadedRows = rows;
if (description)
this->description = *description;
if (tags)
this->tags = *tags;
if(settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString().length() > 0) {
zwift_auth_token = new AuthToken(settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString(), settings.value(QZSettings::zwift_password, QZSettings::default_zwift_password).toString());
zwift_auth_token->getAccessToken();
}
/*
int c = 0;
for (c = 0; c < rows.length(); c++) {
qDebug() << qSetRealNumberPrecision(10)<< "Trainprogramdata"
<< c
<< rows.at(c).latitude
<< rows.at(c).longitude
<< rows.at(c).altitude
<< QTime(0, 0, 0).secsTo(rows.at(c).gpxElapsed)
<< rows.at(c).distance
<< rows.at(c).inclination
<< QTime(0, 0, 0).secsTo(rows.at(c).duration)
<< QTime(0, 0, 0).secsTo(rows.at(c).rampDuration)
<< QTime(0, 0, 0).secsTo(rows.at(c).rampElapsed)
<< rows.at(c).speed;
}
*/
// speed filter only to GPX workouts with timestamp
if (rows.length() && !isnan(rows.at(0).latitude) && !isnan(rows.at(0).longitude) &&
QTime(0, 0, 0).secsTo(rows.at(0).gpxElapsed) != 0 && !treadmill_force_speed && videoAvailable) {
applySpeedFilter();
}
this->videoAvailable = videoAvailable;
connect(&timer, SIGNAL(timeout()), this, SLOT(scheduler()));
timer.setInterval(1s);
timer.start();
}
QString trainrow::toString() const {
QString rv;
rv += QStringLiteral("duration = %1").arg(duration.toString());
rv += QStringLiteral(" distance = %1").arg(distance);
rv += QStringLiteral(" speed = %1").arg(speed);
rv += QStringLiteral(" lower_speed = %1").arg(lower_speed); // used for peloton
rv += QStringLiteral(" average_speed = %1").arg(average_speed); // used for peloton
rv += QStringLiteral(" upper_speed = %1").arg(upper_speed); // used for peloton
rv += QStringLiteral(" fanspeed = %1").arg(fanspeed);
rv += QStringLiteral(" inclination = %1").arg(inclination);
rv += QStringLiteral(" lower_inclination = %1").arg(lower_inclination); // used for peloton
rv += QStringLiteral(" average_inclination = %1").arg(average_inclination); // used for peloton
rv += QStringLiteral(" upper_inclination = %1").arg(upper_inclination); // used for peloton
rv += QStringLiteral(" resistance = %1").arg(resistance);
rv += QStringLiteral(" lower_resistance = %1").arg(lower_resistance);
rv += QStringLiteral(" average_resistance = %1").arg(average_resistance); // used for peloton
rv += QStringLiteral(" upper_resistance = %1").arg(upper_resistance);
rv += QStringLiteral(" requested_peloton_resistance = %1").arg(requested_peloton_resistance);
rv += QStringLiteral(" lower_requested_peloton_resistance = %1").arg(lower_requested_peloton_resistance);
rv += QStringLiteral(" average_requested_peloton_resistance = %1")
.arg(average_requested_peloton_resistance); // used for peloton
rv += QStringLiteral(" upper_requested_peloton_resistance = %1").arg(upper_requested_peloton_resistance);
rv += QStringLiteral(" pace_intensity = %1").arg(pace_intensity);
rv += QStringLiteral(" cadence = %1").arg(cadence);
rv += QStringLiteral(" lower_cadence = %1").arg(lower_cadence);
rv += QStringLiteral(" average_cadence = %1").arg(average_cadence); // used for peloton
rv += QStringLiteral(" upper_cadence = %1").arg(upper_cadence);
rv += QStringLiteral(" forcespeed = %1").arg(forcespeed);
rv += QStringLiteral(" loopTimeHR = %1").arg(loopTimeHR);
rv += QStringLiteral(" zoneHR = %1").arg(zoneHR);
rv += QStringLiteral(" HRmin = %1").arg(HRmin);
rv += QStringLiteral(" HRmax = %1").arg(HRmax);
rv += QStringLiteral(" maxSpeed = %1").arg(maxSpeed);
rv += QStringLiteral(" minSpeed = %1").arg(minSpeed);
rv += QStringLiteral(" maxResistance = %1").arg(maxResistance);
rv += QStringLiteral(" power = %1").arg(power);
rv += QStringLiteral(" mets = %1").arg(mets);
rv += QStringLiteral(" latitude = %1").arg(latitude);
rv += QStringLiteral(" longitude = %1").arg(longitude);
rv += QStringLiteral(" altitude = %1").arg(altitude);
rv += QStringLiteral(" azimuth = %1").arg(azimuth);
rv += QStringLiteral(" rampElapsed = %1").arg(rampElapsed.toString());
rv += QStringLiteral(" rampDuration = %1").arg(rampDuration.toString());
return rv;
}
void trainprogram::applySpeedFilter() {
if (rows.length() == 0)
return;
int r = 0;
double weight[] = {0.15, 0.15, 0.1, 0.05, 0.05, 0.1, 0.1, 0.15, 0.15};
QList<double> newdistance;
newdistance.reserve(rows.length() + 1);
while (r < rows.length()) {
int ws = (r - 4);
int we = (r + 4);
// filtering starting point
if (ws < 1)
ws = 1;
if (we >= rows.length())
we = (rows.length() -
2); // Subtract 2 Points because duration is calculated with row+1! Fixes inf calculation in #973
int wc = 0;
double wma = 0;
int rowduration = 0;
for (wc = 0; wc <= (we - ws); wc++) {
int currow = (ws + wc);
// filtering starting point
if (currow <= 1)
rowduration = QTime(0, 0, 0).secsTo(rows.at(currow).gpxElapsed);
else
rowduration = ((QTime(0, 0, 0).secsTo(rows.at(currow).gpxElapsed)) -
(QTime(0, 0, 0).secsTo(rows.at(currow - 1).gpxElapsed)));
// generally avoid a division by 0 or negative (who knows what's coming from gpx)
if (rowduration > 0)
wma += ((rows.at(currow).distance) / ((double)(rowduration)) * weight[wc]);
}
// filtering starting point
if (r <= 1)
rowduration = QTime(0, 0, 0).secsTo(rows.at(r).gpxElapsed);
else
rowduration =
((QTime(0, 0, 0).secsTo(rows.at(r).gpxElapsed)) - (QTime(0, 0, 0).secsTo(rows.at(r - 1).gpxElapsed)));
/* it takes a lot of time during the opening of the file*/
/*
qDebug() << qSetRealNumberPrecision(10)<< "TrainprogramapplySpeedFilter"
<< r
<< rows.at(r).latitude
<< rows.at(r).longitude
<< rows.at(r).altitude
<< QTime(0, 0, 0).secsTo(rows.at(r).gpxElapsed)
<< rows.at(r).distance
<< (wma * ((double)(rowduration)))
<< wma
<< rowduration
<< rows.at(r).inclination;*/
newdistance.append(wma * ((double)(rowduration)));
r++;
}
for (r = 0; r < rows.length(); r++) {
rows[r].distance = newdistance.at(r);
}
}
uint32_t trainprogram::calculateTimeForRow(int32_t row) {
if (row >= rows.length())
return 0;
if (rows.at(row).distance == -1)
return (rows.at(row).duration.second() + (rows.at(row).duration.minute() * 60) +
(rows.at(row).duration.hour() * 3600));
else {
if(rows.at(row).started.isValid() && rows.at(row).ended.isValid())
return rows.at(row).started.secsTo(rows.at(row).ended);
}
return 0;
}
double trainprogram::calculateDistanceForRow(int32_t row) {
if (row >= rows.length())
return 0;
if (rows.at(row).distance == -1)
return 0;
else
return rows.at(row).distance;
}
// meters, inclination
QList<MetersByInclination> trainprogram::inclinationNext300Meters() {
int c = currentStep;
double km = 0;
QList<MetersByInclination> next300;
while (1) {
if (c < rows.length()) {
if (km > 0.3) {
return next300;
}
MetersByInclination p;
if (c == currentStep) {
p.meters = (rows.at(c).distance - currentStepDistance) * 1000.0;
km += (rows.at(c).distance - currentStepDistance);
} else {
p.meters = (rows.at(c).distance) * 1000.0;
km += (rows.at(c).distance);
}
p.inclination = rows.at(c).inclination;
next300.append(p);
} else {
return next300;
}
c++;
}
return next300;
}
// meters, inclination
QList<MetersByInclination> trainprogram::avgInclinationNext300Meters() {
int c = currentStep;
double km = 0;
QList<MetersByInclination> next300;
while (1) {
if (c < rows.length()) {
if (km > 0.3) {
return next300;
}
MetersByInclination p;
if (c == currentStep) {
p.meters = (rows.at(c).distance - currentStepDistance) * 1000.0;
km += (rows.at(c).distance - currentStepDistance);
} else {
p.meters = (rows.at(c).distance) * 1000.0;
km += (rows.at(c).distance);
}
p.inclination = avgInclinationNext100Meters(c);
next300.append(p);
} else {
return next300;
}
c++;
}
return next300;
}
// speed in Km/h
double trainprogram::avgSpeedFromGpxStep(int gpxStep, int seconds) {
int start = gpxStep;
if (gpxStep >= rows.length())
return 0.0;
double km = (rows.at(gpxStep).distance);
int timesum = 0;
if (gpxStep > 0)
timesum = (QTime(0, 0, 0).secsTo(rows.at(gpxStep).gpxElapsed) -
QTime(0, 0, 0).secsTo(rows.at(gpxStep - 1).gpxElapsed));
else
timesum = QTime(0, 0, 0).secsTo(rows.at(gpxStep).gpxElapsed);
int c = gpxStep + 1;
while (1) {
if ((timesum >= seconds) || (c >= rows.length())) {
return (km / ((double)timesum) * 3600.0);
}
km += (rows.at(c).distance);
if (c > 0)
timesum = (timesum + QTime(0, 0, 0).secsTo(rows.at(c).gpxElapsed) -
QTime(0, 0, 0).secsTo(rows.at(c - 1).gpxElapsed));
c++;
}
return (km / ((double)timesum) * 3600.0);
}
int trainprogram::TotalGPXSecs() {
if (rows.length() == 0)
return 0;
return QTime(0, 0, 0).secsTo(rows.at(rows.length() - 1).gpxElapsed);
}
double trainprogram::TimeRateFromGPX(double gpxsecs, double videosecs, double currentspeed, int recordingFactor) {
// no rows available, return 1
if (rows.length() <= 0) {
qDebug() << "TimeRateFromGPX no Rows";
return 1.0;
}
if (videosecs == 0.0) {
qDebug() << "TimeRateFromGPX Videopos = 0";
return 1.0;
}
double prevAvgSpeed = lastGpxSpeedSet;
double avgNextSpeed = -1.0;
if (prevAvgSpeed == 0.0)
avgNextSpeed = avgSpeedFromGpxStep(currentStep, 5);
else {
int testpos = currentStep;
while (testpos < (currentStep + 6)) {
double avgTestSpeed = avgSpeedFromGpxStep(testpos, 5);
double deviation = (avgTestSpeed / prevAvgSpeed);
if (deviation >= 0.85 && deviation <= 1.15) {
avgNextSpeed = avgTestSpeed;
testpos = (currentStep + 6);
}
testpos++;
}
}
if (avgNextSpeed == -1.0) {
avgNextSpeed = avgSpeedFromGpxStep(currentStep, 5);
}
// Avoid a Division by Zero
if (avgNextSpeed == 0.0) {
qDebug() << "TimeRateFromGPX Nextspeed = 0";
return 1.0;
}
// set the maximum Speed that the player can reached based on the Video speed.
// if Rate get too high the Video jumps
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
double avgSpeedForLimit = avgSpeedFromGpxStep(currentStep + 1, 5);
if (avgSpeedForLimit > 0.0) {
bike *dev = (bike *)bluetoothManager->device();
// bepo70: Replay allows Factor 2 max, so set the speed Limit to 2 * Video recording Factor speed to
// avoid any jumps in Video
dev->setSpeedLimit(avgSpeedForLimit * (double)recordingFactor * 2.0 / 3.0);
}
}
if (gpxsecs == lastGpxRateSetAt) {
qDebug() << "TimeRateFromGPX Gpxpos=lastPos" << lastGpxRateSet;
return lastGpxRateSet;
}
// Calculate the Factor between current Players Speed and the next average GPX Speed
double playedToGpxSpeedFactor = (currentspeed / avgNextSpeed);
// Calculate where the gpx would be in 1 Second
double gpxTarget = (gpxsecs + playedToGpxSpeedFactor);
// Get needed Rate for the next second
double rate = (gpxTarget - videosecs);
// If rate < 0 Video is highly before the gpx and Video would be rewinded. Wait with Video for gpx to reach it
if (rate < 0.0) {
rate = 0.1;
}
qDebug() << qSetRealNumberPrecision(10) << "TimeRateFromGPX" << gpxsecs << videosecs << (gpxsecs - videosecs)
<< currentspeed << avgNextSpeed << gpxTarget << lastGpxRateSetAt << lastGpxRateSet << rate;
// Save the last Gpx Timestamp and the last Rate for later calls.
lastGpxSpeedSet = avgNextSpeed;
if (lastGpxRateSetAt != gpxsecs) {
lastGpxRateSetAt = gpxsecs;
lastGpxRateSet = rate;
}
return rate;
}
// Calculate the Median Inclination for a given Step. Median is built from the given Step -2 Steps and +2 Steps (5 Steps
// in total)
double trainprogram::medianInclination(int step) {
QList<double> inclinations;
inclinations.reserve(5);
if (rows.length() == 0)
return 0;
if ((step > 1) && (rows.length() > step - 2))
inclinations.append(rows.at(step - 2).inclination);
else
inclinations.append(0);
if ((step > 0) && (rows.length() > step - 1))
inclinations.append(rows.at(step - 1).inclination);
else
inclinations.append(0);
if (rows.length() > step)
inclinations.append(rows.at(step).inclination);
else
inclinations.append(0);
if (rows.length() > step + 1)
inclinations.append(rows.at(step + 1).inclination);
else
inclinations.append(0);
if (rows.length() > step + 2)
inclinations.append(rows.at(step + 2).inclination);
else
inclinations.append(0);
std::sort(inclinations.begin(), inclinations.end());
return (inclinations.at(2));
}
// Calculates a weighted Inclination for a given Step. Inclination is calculated for the given Step + windowsize Steps
// (7) The inclination for each Point needed goes through a Median Filter first to eliminate/minimize Errors in the
// recorded elevation Data
double trainprogram::weightedInclination(int step) {
int windowsize = 7;
int firststep = step;
double inc = 0;
double sumweights = 0;
double pointweight = 0;
if (rows.length() == 0)
return 0;
// Determine first and last possible Steps
if (firststep < 0)
firststep = 0;
int laststep = step + windowsize;
if (laststep >= rows.length()) {
firststep = rows.length() - 1 - (windowsize * 2);
if (firststep < 0)
firststep = 0;
}
// Loop through the determined Steps
for (int s = firststep; s <= laststep; s++) {
// Calculate the Weight used for the inclination
pointweight = ((((double)windowsize * 2.0) - 1.0) - ((s - firststep) * 2.0));
// Calculate the sum of weights
sumweights = (sumweights + pointweight);
// Calculate the sum of weighted median inclinations
inc = (inc + (medianInclination(s)) * pointweight);
}
// avoid a Division by 0
if (sumweights == 0)
return 0;
// Return the sum of weighted median inclinations / sum of all weights
return (inc / sumweights);
}
double trainprogram::avgInclinationNext100Meters(int step) {
int c = step;
double km = 0;
double avg = 0;
int sum = 0;
while (1) {
if (c < rows.length()) {
if (km > 0.1) {
if (sum == 1) {
return rows.at(currentStep).inclination;
}
return avg / (double)km;
}
if (c == currentStep)
km += (rows.at(c).distance - currentStepDistance);
else
km += (rows.at(c).distance);
avg += rows.at(c).inclination * rows.at(c).distance;
sum++;
} else {
if (sum == 1) {
return rows.at(currentStep).inclination;
}
return avg / (double)km;
}
c++;
}
if (sum == 1) {
return rows.at(currentStep).inclination;
}
return avg / (double)km;
}
double trainprogram::avgAzimuthNext300Meters() {
int c = currentStep;
double km = 0;
double sinTotal = 0;
double cosTotal = 0;
if (!isnan(rows.at(c).latitude) && !isnan(rows.at(c).longitude)) {
while (1) {
if (c < rows.length()) {
if (km > 0.3) {
double averageDirection = atan(sinTotal / cosTotal) * (180 / M_PI);
if (cosTotal < 0) {
averageDirection += 180;
} else if (sinTotal < 0) {
averageDirection += 360;
}
return averageDirection;
}
for (double i = 0; i < rows.at(c).distance; i += 0.001) {
sinTotal += sin(rows.at(c).azimuth * (M_PI / 180));
cosTotal += cos(rows.at(c).azimuth * (M_PI / 180));
}
km += rows.at(c).distance;
} else {
double averageDirection = atan(sinTotal / cosTotal) * (180 / M_PI);
if (cosTotal < 0) {
averageDirection += 180;
} else if (sinTotal < 0) {
averageDirection += 360;
}
return averageDirection;
}
c++;
}
}
return 0;
}
void trainprogram::clearRows() {
QMutexLocker(&this->schedulerMutex);
rows.clear();
}
void trainprogram::pelotonOCRprocessPendingDatagrams() {
qDebug() << "in !";
QHostAddress sender;
QSettings settings;
uint16_t port;
while (pelotonOCRsocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(pelotonOCRsocket->pendingDatagramSize());
pelotonOCRsocket->readDatagram(datagram.data(), datagram.size(), &sender, &port);
qDebug() << "PelotonOCR Message From :: " << sender.toString();
qDebug() << "PelotonOCR Port From :: " << port;
qDebug() << "PelotonOCR Message :: " << datagram;
QString s = datagram;
pelotonOCRcomputeTime(s);
QString url = "http://" + localipaddress::getIP(sender).toString() + ":" +
QString::number(settings.value("template_inner_QZWS_port", 6666).toInt()) +
"/floating/floating.htm";
int r = pelotonOCRsocket->writeDatagram(QByteArray(url.toLatin1()), sender, 8003);
qDebug() << "url floating" << url << r;
}
}
void trainprogram::pelotonOCRcomputeTime(QString t) {
static bool pelotonOCRcomputeTime_intro = false;
static bool pelotonOCRcomputeTime_syncing = false;
QRegularExpression re("\\d\\d:\\d\\d");
QRegularExpressionMatch match = re.match(t.left(5));
if (t.contains(QStringLiteral("INTRO")) || t.contains(QStringLiteral("UNTIL START"))) {
qDebug() << QStringLiteral("PELOTON OCR: SKIPPING INTRO, restarting training program");
if (!pelotonOCRcomputeTime_intro) {
pelotonOCRcomputeTime_intro = true;
emit toastRequest("Peloton Syncing! Skipping intro...");
}
restart();
} else if (match.hasMatch()) {
int minutes = t.left(2).toInt();
int seconds = t.left(5).right(2).toInt();
seconds -= 1; //(due to the OCR delay)
seconds += minutes * 60;
QTime ocrRemaining = QTime(0, 0, 0, 0).addSecs(seconds);
QTime currentRemaining = remainingTime();
qDebug() << QStringLiteral("PELOTON OCR USING: ocrRemaining") << ocrRemaining
<< QStringLiteral("currentRemaining") << currentRemaining;
uint32_t abs = qAbs(ocrRemaining.secsTo(currentRemaining));
if (abs < 120) {
qDebug() << QStringLiteral("PELOTON OCR SYNCING!");
if (!pelotonOCRcomputeTime_syncing) {
pelotonOCRcomputeTime_syncing = true;
emit toastRequest("Peloton Syncing!");
}
// applying the differences
if (ocrRemaining > currentRemaining)
decreaseElapsedTime(abs);
else
increaseElapsedTime(abs);
}
}
}
void trainprogram::scheduler() {
QMutexLocker(&this->schedulerMutex);
QSettings settings;
// outside the if case about a valid train program because the information for the floating window url should be
// sent anyway
if (settings.value(QZSettings::peloton_companion_workout_ocr, QZSettings::default_companion_peloton_workout_ocr)
.toBool()) {
if (!pelotonOCRsocket) {
pelotonOCRsocket = new QUdpSocket(this);
bool result = pelotonOCRsocket->bind(QHostAddress::AnyIPv4, 8003);
qDebug() << result;
pelotonOCRprocessPendingDatagrams();
connect(pelotonOCRsocket, SIGNAL(readyRead()), this, SLOT(pelotonOCRprocessPendingDatagrams()));
}
}
if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr ||
(bluetoothManager->device()->currentSpeed().value() <= 0 &&
!settings.value(QZSettings::continuous_moving, QZSettings::default_continuous_moving).toBool()) ||
bluetoothManager->device()->isPaused()) {
if(bluetoothManager->device() && (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL || bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) &&
settings.value(QZSettings::zwift_username, QZSettings::default_zwift_username).toString().length() > 0 && zwift_auth_token &&
zwift_auth_token->access_token.length() > 0) {
if(!zwift_world) {
zwift_world = new World(1, zwift_auth_token->getAccessToken());
qDebug() << "creating zwift api world";
}
else {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
if(!h)
h = new lockscreen();
#endif
#endif
if(zwift_player_id == -1) {
QString id = zwift_world->player_id();
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(id.toLocal8Bit(), &parseError);
QJsonObject ride = document.object();
qDebug() << "zwift api player" << ride;
zwift_player_id = ride[QStringLiteral("id")].toInt();
emit zwiftLoginState(true);
} else {
static int zwift_counter = 5;
int timeout = settings.value(QZSettings::zwift_api_poll, QZSettings::default_zwift_api_poll).toInt();
if(timeout < 5)
timeout = 5;
if(zwift_counter++ >= (timeout - 1)) {
zwift_counter = 0;
QByteArray bb = zwift_world->playerStatus(zwift_player_id);
qDebug() << " ZWIFT API PROTOBUF << " + bb.toHex(' ');
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
h->zwift_api_decodemessage_player(bb.data(), bb.length());
float alt = h->zwift_api_getaltitude();
float distance = h->zwift_api_getdistance();
#else
float alt = 0;
float distance = 0;
#endif
#elif defined(Q_OS_ANDROID)
QAndroidJniEnvironment env;
jbyteArray d = env->NewByteArray(bb.length());
jbyte *b = env->GetByteArrayElements(d, 0);
for (int i = 0; i < bb.length(); i++)
b[i] = bb[i];
env->SetByteArrayRegion(d, 0, bb.length(), b);
QAndroidJniObject::callStaticMethod<void>(
"org/cagnulen/qdomyoszwift/ZwiftAPI", "zwift_api_decodemessage_player", "([B)V", d);
env->DeleteLocalRef(d);
float alt = QAndroidJniObject::callStaticMethod<float>("org/cagnulen/qdomyoszwift/ZwiftAPI", "getAltitude", "()F");
float distance = QAndroidJniObject::callStaticMethod<float>("org/cagnulen/qdomyoszwift/ZwiftAPI", "getDistance", "()F");
#elif defined Q_CC_MSVC
PlayerState state;
float alt = 0;
float distance = 0;
if (state.ParseFromArray(bb.constData(), bb.size())) {
// Parsing riuscito, ora puoi accedere ai dati in `state`
alt = state.altitude();
distance = state.distance();
} else {
// Errore durante il parsing
qDebug() << "Error parsing PlayerState";
}
#else
float alt = 0;
float distance = 0;
#endif
static float old_distance = 0;
static float old_alt = 0;
qDebug() << "zwift api incline1" << old_distance << old_alt << distance << alt;
if(old_distance > 0) {
float delta = distance - old_distance;
float deltaA = alt - old_alt;
float incline = (deltaA / delta);
if(delta > 1) {
bool zwift_negative_inclination_x2 =
settings.value(QZSettings::zwift_negative_inclination_x2, QZSettings::default_zwift_negative_inclination_x2)
.toBool();
double offset =
settings.value(QZSettings::zwift_inclination_offset, QZSettings::default_zwift_inclination_offset).toDouble();
double gain =
settings.value(QZSettings::zwift_inclination_gain, QZSettings::default_zwift_inclination_gain).toDouble();
double grade = (incline * gain) + offset;
if (zwift_negative_inclination_x2 && incline < 0) {
grade = ((incline * 2.0) * gain) + offset;
}
bool zwift_api_autoinclination = settings.value(QZSettings::zwift_api_autoinclination, QZSettings::default_zwift_api_autoinclination).toBool();
qDebug() << "zwift api incline" << incline << grade << delta << deltaA << zwift_api_autoinclination;
if(zwift_api_autoinclination) {
if(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL ||
(bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL && ((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware())) {
bluetoothManager->device()->changeInclination(grade, grade);
}
if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL &&
(!((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware() ||
((elliptical*)bluetoothManager->device())->inclinationSeparatedFromResistance())) {
QSettings settings;
double bikeResistanceOffset = settings.value(QZSettings::bike_resistance_offset, bikeResistanceOffset).toInt();
double bikeResistanceGain = settings.value(QZSettings::bike_resistance_gain_f, bikeResistanceGain).toDouble();
bluetoothManager->device()->changeResistance((resistance_t)(round(grade * bikeResistanceGain)) + bikeResistanceOffset + 1); // resistance start from 1
}
}
}
}
old_distance = distance;
old_alt = alt;
}
}
}
}
// in case no workout has been selected
// Zwift OCR
if ((settings.value(QZSettings::zwift_ocr, QZSettings::default_zwift_ocr).toBool() ||
settings.value(QZSettings::zwift_ocr_climb_portal, QZSettings::default_zwift_ocr_climb_portal).toBool()) &&
bluetoothManager && bluetoothManager->device() &&
(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL ||
bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL)) {
#ifdef Q_OS_ANDROID
{
QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText");
QString t = text.toString();
QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended");
// 2272 1027
jint w = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/ScreenCaptureService",
"getImageWidth", "()I");
jint h = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/ScreenCaptureService",
"getImageHeight", "()I");
QString tExtended = textExtended.toString();
QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName");
QString packageName = packageNameJava.toString();
if (packageName.contains("com.zwift.zwiftgame")) {
qDebug() << QStringLiteral("ZWIFT OCR ACCEPTED") << packageName << w << h << t << tExtended;
foreach (QString s, tExtended.split("§§")) {
// qDebug() << s;
QStringList ss = s.split("$$");
if (ss.length() > 1) {
// (2195, 75 - 2254, 106)"
qDebug() << ss[0] << ss[1];
QString inc = ss[1].replace("Rect(", "").replace(")", "");
if (inc.split(",").length() > 2) {
int w_minbound = w * 0.93;
int h_minbound = h * 0.08;
int h_maxbound = h * 0.15;
int x = inc.split(",").at(0).toInt();
int y = inc.split(",").at(2).toInt();
qDebug() << x << w_minbound << h_maxbound << y << h_minbound;
if (x > w_minbound && y < h_maxbound && y > h_minbound) {
ss[0] = ss[0].replace("%", "");
ss[0] = ss[0].replace("O", "0");
ss[0] = ss[0].replace("l", "1");
ss[0] = ss[0].replace(" ", "");
if (ss[0].toInt() < 15 && ss[0].toInt() > -15) {
bluetoothManager->device()->changeInclination(ss[0].toInt(), ss[0].toInt());
} else {
qDebug() << "filtering" << ss[0].toInt();
}
}
}
}
}
} else {
qDebug() << QStringLiteral("ZWIFT OCR IGNORING") << packageName << t;
}
}
#elif defined(Q_OS_WINDOWS)
static windows_zwift_incline_paddleocr_thread *windows_zwift_ocr_thread = nullptr;
if (!windows_zwift_ocr_thread) {
windows_zwift_ocr_thread = new windows_zwift_incline_paddleocr_thread(bluetoothManager->device());
connect(windows_zwift_ocr_thread, &windows_zwift_incline_paddleocr_thread::debug, bluetoothManager,
&bluetooth::debug);
connect(windows_zwift_ocr_thread, &windows_zwift_incline_paddleocr_thread::onInclination, this,
&trainprogram::changeInclination);
windows_zwift_ocr_thread->start();
}
#endif
} else if (settings.value(QZSettings::zwift_workout_ocr, QZSettings::default_zwift_workout_ocr).toBool() &&
bluetoothManager && bluetoothManager->device() &&
(bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL ||
bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL)) {
#ifdef Q_OS_WINDOWS
static windows_zwift_workout_paddleocr_thread *windows_zwift_workout_ocr_thread = nullptr;
if (!windows_zwift_workout_ocr_thread) {
windows_zwift_workout_ocr_thread =
new windows_zwift_workout_paddleocr_thread(bluetoothManager->device());
connect(windows_zwift_workout_ocr_thread, &windows_zwift_workout_paddleocr_thread::debug,
bluetoothManager, &bluetooth::debug);
connect(windows_zwift_workout_ocr_thread, &windows_zwift_workout_paddleocr_thread::onInclination, this,
&trainprogram::changeInclination);
connect(windows_zwift_workout_ocr_thread, &windows_zwift_workout_paddleocr_thread::onSpeed, this,
&trainprogram::changeSpeed);
windows_zwift_workout_ocr_thread->start();
}
#endif
}
return;
}
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::peloton_workout_ocr, QZSettings::default_peloton_workout_ocr).toBool()) {
QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText");
QString t = text.toString();
QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName");
QString packageName = packageNameJava.toString();
if (packageName.contains("com.onepeloton.callisto")) {
qDebug() << QStringLiteral("PELOTON OCR ACCEPTED") << packageName << t;
pelotonOCRcomputeTime(t);
} else {
qDebug() << QStringLiteral("PELOTON OCR IGNORING") << packageName << t;
}
}
#endif
ticks++;
double odometerFromTheDevice = bluetoothManager->device()->odometer();
if(ticks < 0) {
qDebug() << "waiting for the start...";
return;
}
// entry point
if (ticks == 1 && currentStep == 0) {
rows[currentStep].started = QDateTime::currentDateTime();
currentStepDistance = 0;
lastOdometer = odometerFromTheDevice;
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
if (rows.at(0).forcespeed && rows.at(0).speed) {
qDebug() << QStringLiteral("trainprogram change speed") + QString::number(rows.at(0).speed);
emit changeSpeed(rows.at(0).speed);
}
if (rows.at(0).inclination != -200) {
double inc;
if (!isnan(rows.at(0).latitude) && !isnan(rows.at(0).longitude)) {
inc = avgInclinationNext100Meters(currentStep);
} else {
inc = rows.at(0).inclination;
}
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
emit changeNextInclination300Meters(avgInclinationNext300Meters());
}
if (rows.at(0).power != -1) {
qDebug() << QStringLiteral("trainprogram change power") + QString::number(rows.at(0).power);
emit changePower(rows.at(0).power);
}
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
if (rows.at(0).forcespeed && rows.at(0).speed) {
qDebug() << QStringLiteral("trainprogram change speed") + QString::number(rows.at(0).speed);
emit changeSpeed(rows.at(0).speed);
}
if (rows.at(0).cadence != -1) {
qDebug() << QStringLiteral("trainprogram change cadence") + QString::number(rows.at(0).cadence);
emit changeCadence(rows.at(0).cadence);
}
if (rows.at(0).power != -1) {
qDebug() << QStringLiteral("trainprogram change power") + QString::number(rows.at(0).power);
emit changePower(rows.at(0).power);
}
if (rows.at(0).resistance != -1) {
qDebug() << QStringLiteral("trainprogram change resistance") + QString::number(rows.at(0).resistance);
emit changeResistance(rows.at(0).resistance);
}
} else {
if (rows.at(0).resistance != -1) {
qDebug() << QStringLiteral("trainprogram change resistance") + QString::number(rows.at(0).resistance);
emit changeResistance(rows.at(0).resistance);
}
if (rows.at(0).cadence != -1) {
qDebug() << QStringLiteral("trainprogram change cadence") + QString::number(rows.at(0).cadence);
emit changeCadence(rows.at(0).cadence);
}
if (rows.at(0).power != -1) {
qDebug() << QStringLiteral("trainprogram change power") + QString::number(rows.at(0).power);
emit changePower(rows.at(0).power);
}
if (rows.at(0).requested_peloton_resistance != -1) {
qDebug() << QStringLiteral("trainprogram change requested peloton resistance") +
QString::number(rows.at(0).requested_peloton_resistance);
emit changeRequestedPelotonResistance(rows.at(0).requested_peloton_resistance);
}
if (rows.at(0).inclination != -200 && (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE ||
(bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL && !((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware()))) {
// this should be converted in a signal as all the other signals...
double bikeResistanceOffset =
settings.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset)
.toInt();
double bikeResistanceGain =
settings.value(QZSettings::bike_resistance_gain_f, QZSettings::default_bike_resistance_gain_f)
.toDouble();
double inc = rows.at(0).inclination;
bluetoothManager->device()->changeResistance((resistance_t)(round(inc * bikeResistanceGain)) +
bikeResistanceOffset + 1); // resistance start from 1)
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE && !((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
bluetoothManager->device()->setInclination(inc);
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
emit changeNextInclination300Meters(inclinationNext300Meters());
}
}
if (rows.at(0).fanspeed != -1) {
qDebug() << QStringLiteral("trainprogram change fanspeed") + QString::number(rows.at(0).fanspeed);
emit changeFanSpeed(rows.at(0).fanspeed);
}
if (!isnan(rows.at(0).latitude) || !isnan(rows.at(0).longitude) || !isnan(rows.at(0).altitude)) {
qDebug() << qSetRealNumberPrecision(10) << QStringLiteral("trainprogram change GEO position")
<< rows.at(0).latitude << rows.at(0).longitude << rows.at(0).altitude << rows.at(0).azimuth;
QGeoCoordinate p;
p.setAltitude(rows.at(0).altitude);
p.setLatitude(rows.at(0).latitude);
p.setLongitude(rows.at(0).longitude);
emit changeGeoPosition(p, rows.at(0).azimuth, avgAzimuthNext300Meters());
}
}
uint32_t currentRowLen = calculateTimeForRow(currentStep);
qDebug() << QStringLiteral("trainprogram elapsed ") + QString::number(ticks) + QStringLiteral("current row len") +
QString::number(currentRowLen);
uint32_t calculatedLine;
uint32_t calculatedElapsedTime = 0;
for (calculatedLine = 0; calculatedLine < static_cast<uint32_t>(rows.length()); calculatedLine++) {
calculatedElapsedTime += calculateTimeForRow(calculatedLine);
if (calculateDistanceForRow(calculatedLine) > 0 && calculatedLine >= currentStep) {
break;
}
if (calculatedElapsedTime > static_cast<uint32_t>(ticks) && calculatedLine >= currentStep) {
break;
}
}
bool distanceEvaluation = false;
int sameIteration = 0;
do {
currentStepDistance += (odometerFromTheDevice - lastOdometer);
lastOdometer = odometerFromTheDevice;
if(currentStep >= rows.length()) {
qDebug() << "currentStep greater than row.length" << currentStep << rows.length();
end();
return;
}
bool distanceStep = (rows.at(currentStep).distance > 0);
distanceEvaluation = (distanceStep && currentStepDistance >= rows.at(currentStep).distance);
qDebug() << qSetRealNumberPrecision(10) << QStringLiteral("currentStepDistance") << currentStepDistance
<< QStringLiteral("distanceStep") << distanceStep << QStringLiteral("distanceEvaluation")
<< distanceEvaluation << QStringLiteral("rows distance") << rows.at(currentStep).distance
<< QStringLiteral("same iteration") << sameIteration;
if ((calculatedLine != currentStep && !distanceStep) || distanceEvaluation) {
if (calculateTimeForRow(calculatedLine) || calculateDistanceForRow(calculatedLine) > 0) {
if(rows.at(currentStep).distance != -1)
lastOdometer -= (currentStepDistance - rows.at(currentStep).distance);
rows[currentStep].ended = QDateTime::currentDateTime();
if (!distanceStep)
currentStep = calculatedLine;
else
currentStep++;
if(currentStep >= rows.length()) {
qDebug() << "currentStep greater than row.length" << currentStep << rows.length();
end();
return;
}
calculatedLine = currentStep;
rows[currentStep].started = QDateTime::currentDateTime();
currentStepDistance = 0;
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
if (rows.at(currentStep).forcespeed && rows.at(currentStep).speed) {
qDebug() << QStringLiteral("trainprogram change speed ") +
QString::number(rows.at(currentStep).speed);
double speed;
if (!isnan(rows.at(currentStep).latitude) && !isnan(rows.at(currentStep).longitude)) {
speed = avgSpeedFromGpxStep(currentStep, 60);
} else {
speed = rows.at(currentStep).speed;
}
emit changeSpeed(speed);
}
if (rows.at(currentStep).inclination != -200) {
double inc;
if (!isnan(rows.at(currentStep).latitude) && !isnan(rows.at(currentStep).longitude)) {
inc = avgInclinationNext100Meters(currentStep);
} else {
inc = rows.at(currentStep).inclination;
}
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
emit changeNextInclination300Meters(avgInclinationNext300Meters());
}
if (rows.at(currentStep).power != -1) {
qDebug() << QStringLiteral("trainprogram change power ") +
QString::number(rows.at(currentStep).power);
emit changePower(rows.at(currentStep).power);
}
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
if (rows.at(currentStep).forcespeed && rows.at(currentStep).speed) {
qDebug() << QStringLiteral("trainprogram change speed ") +
QString::number(rows.at(currentStep).speed);
double speed;
if (!isnan(rows.at(currentStep).latitude) && !isnan(rows.at(currentStep).longitude)) {
speed = avgSpeedFromGpxStep(currentStep, 60);
} else {
speed = rows.at(currentStep).speed;
}
emit changeSpeed(speed);
}
if (rows.at(currentStep).cadence != -1) {
qDebug() << QStringLiteral("trainprogram change cadence ") +
QString::number(rows.at(currentStep).cadence);
emit changeCadence(rows.at(currentStep).cadence);
}
if (rows.at(currentStep).power != -1) {
qDebug() << QStringLiteral("trainprogram change power ") +
QString::number(rows.at(currentStep).power);
emit changePower(rows.at(currentStep).power);
}
if (rows.at(currentStep).resistance != -1) {
qDebug() << QStringLiteral("trainprogram change resistance ") +
QString::number(rows.at(currentStep).resistance);
emit changeResistance(rows.at(currentStep).resistance);
}
} else {
if (rows.at(currentStep).resistance != -1) {
qDebug() << QStringLiteral("trainprogram change resistance ") +
QString::number(rows.at(currentStep).resistance);
emit changeResistance(rows.at(currentStep).resistance);
}
if (rows.at(currentStep).cadence != -1) {
qDebug() << QStringLiteral("trainprogram change cadence ") +
QString::number(rows.at(currentStep).cadence);
emit changeCadence(rows.at(currentStep).cadence);
}
if (rows.at(currentStep).power != -1) {
qDebug() << QStringLiteral("trainprogram change power ") +
QString::number(rows.at(currentStep).power);
emit changePower(rows.at(currentStep).power);
}
if (rows.at(currentStep).requested_peloton_resistance != -1) {
qDebug() << QStringLiteral("trainprogram change requested peloton resistance ") +
QString::number(rows.at(currentStep).requested_peloton_resistance);
emit changeRequestedPelotonResistance(rows.at(currentStep).requested_peloton_resistance);
}
if (rows.at(currentStep).inclination != -200 &&
(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE ||
(bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL && !((elliptical*)bluetoothManager->device())->inclinationAvailableByHardware()))) {
// this should be converted in a signal as all the other signals...
double bikeResistanceOffset =
settings
.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset)
.toInt();
double bikeResistanceGain =
settings
.value(QZSettings::bike_resistance_gain_f, QZSettings::default_bike_resistance_gain_f)
.toDouble();
double inc = rows.at(currentStep).inclination;
bluetoothManager->device()->changeResistance((resistance_t)(round(inc * bikeResistanceGain)) +
bikeResistanceOffset +
1); // resistance start from 1)
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE && !((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
bluetoothManager->device()->setInclination(inc);
qDebug() << QStringLiteral("trainprogram change inclination") + QString::number(inc);
emit changeInclination(inc, inc);
emit changeNextInclination300Meters(inclinationNext300Meters());
}
}
if (rows.at(currentStep).fanspeed != -1) {
qDebug() << QStringLiteral("trainprogram change fanspeed ") +
QString::number(rows.at(currentStep).fanspeed);
emit changeFanSpeed(rows.at(currentStep).fanspeed);
}
if (!isnan(rows.at(currentStep).latitude) || !isnan(rows.at(currentStep).longitude) ||
!isnan(rows.at(currentStep).altitude)) {
qDebug() << qSetRealNumberPrecision(10) << QStringLiteral("trainprogram change GEO position")
<< rows.at(currentStep).latitude << rows.at(currentStep).longitude
<< rows.at(currentStep).altitude << rows.at(currentStep).distance
<< rows.at(currentStep).azimuth;
QGeoCoordinate p;
p.setLatitude(rows.at(currentStep).latitude);
p.setLongitude(rows.at(currentStep).longitude);
p.setAltitude(rows.at(currentStep).altitude);
// qDebug() << qSetRealNumberPrecision(10)<< c << rows.at(currentStep+1).latitude <<
// rows.at(currentStep + 1).longitude <<
/*QGeoCoordinate c;
c.setLatitude(rows.at(currentStep+1).latitude);
c.setLongitude(rows.at(currentStep+1).longitude);
c.setAltitude(rows.at(currentStep+1).altitude);
qDebug() << qSetRealNumberPrecision(10)<< "distance" << p.distanceTo(c) <<
rows.at(currentStep).distance;*/
if (odometerFromTheDevice - lastOdometer > 0)
p = p.atDistanceAndAzimuth((odometerFromTheDevice - lastOdometer),
rows.at(currentStep).azimuth);
qDebug() << qSetRealNumberPrecision(10) << "positionOffset"
<< (odometerFromTheDevice - lastOdometer);
emit changeGeoPosition(p, rows.at(currentStep).azimuth, avgAzimuthNext300Meters());
}
} else {
end();
distanceEvaluation = false;
}
} else {
if (rows.length() > currentStep && rows.at(currentStep).power != -1) {
qDebug() << QStringLiteral("trainprogram change power ") +
QString::number(rows.at(currentStep).power);
emit changePower(rows.at(currentStep).power);
}
if (rows.at(currentStep).inclination != -200 &&
(!isnan(rows.at(currentStep).latitude) && !isnan(rows.at(currentStep).longitude))) {
double inc = avgInclinationNext100Meters(currentStep);
// if Bike used and it is a gpx with Video use the new weightedInclination
if ((videoAvailable) && (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)) {
inc = weightedInclination(currentStep);
}
double bikeResistanceOffset =
settings.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset)
.toInt();
double bikeResistanceGain =
settings.value(QZSettings::bike_resistance_gain_f, QZSettings::default_bike_resistance_gain_f)
.toDouble();
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
bluetoothManager->device()->changeResistance((resistance_t)(round(inc * bikeResistanceGain)) +
bikeResistanceOffset + 1); // resistance start from 1)
if (!((bike *)bluetoothManager->device())->inclinationAvailableByHardware())
bluetoothManager->device()->setInclination(inc);
}
qDebug() << QStringLiteral("trainprogram change inclination due to gps") + QString::number(inc);
emit changeInclination(inc, inc);
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL)
emit changeNextInclination300Meters(avgInclinationNext300Meters());
else
emit changeNextInclination300Meters(inclinationNext300Meters());
double ratioDistance = 0.0;
double distanceRow = rows.at(currentStep).distance;
int steptime = 0;
if (lastStepTimestampChanged != currentStep) {
lastCurrentStepDistance = 0.0;
lastCurrentStepTime = QTime(0, 0, 0);
if (currentStep > 0) {
lastCurrentStepTime = rows.at(currentStep - 1).gpxElapsed;
}
lastStepTimestampChanged = currentStep;
}
if ((currentStep > 1) && (distanceRow != 0.0)) {
steptime = ((QTime(0, 0, 0).secsTo(rows.at(currentStep).gpxElapsed)) -
(QTime(0, 0, 0).secsTo(rows.at(currentStep - 1).gpxElapsed)));
if (steptime == 0)
steptime = 1;
distanceRow = (distanceRow / ((double)(steptime)));
ratioDistance = ((currentStepDistance - lastCurrentStepDistance) / distanceRow);
lastCurrentStepTime = lastCurrentStepTime.addMSecs(ratioDistance * 1000.0);
}
lastCurrentStepDistance = currentStepDistance;
qDebug() << qSetRealNumberPrecision(10) << QStringLiteral("changingTimestamp") << currentStep
<< distanceRow << currentStepDistance << lastCurrentStepDistance << ratioDistance
<< rows.at(currentStep).gpxElapsed << lastCurrentStepTime << ticks;
emit changeTimestamp(lastCurrentStepTime, QTime(0, 0, 0).addSecs(ticks));
}
}
sameIteration++;
} while (distanceEvaluation);
}
void trainprogram::end() {
QSettings settings;
qDebug() << QStringLiteral("trainprogram ends!");
// circuit?
if (!isnan(rows.first().latitude) && !isnan(rows.first().longitude) &&
QGeoCoordinate(rows.first().latitude, rows.first().longitude)
.distanceTo(bluetoothManager->device()->currentCordinate()) < 50) {
emit lap();
restart();
} else {
started = false;
if (settings
.value(QZSettings::trainprogram_stop_at_end, QZSettings::default_trainprogram_stop_at_end)
.toBool())
emit stop(false);
}
}
bool trainprogram::overridePowerForCurrentRow(double power) {
if (started && currentStep < rows.length() && currentRow().power != -1) {
qDebug() << "overriding power from" << rows.at(currentStep).power << "to" << power;
rows[currentStep].power = power;
return true;
}
return false;
}
void trainprogram::increaseElapsedTime(int32_t i) {
offset += i;
ticks += i;
}
void trainprogram::decreaseElapsedTime(int32_t i) {
offset -= i;
ticks -= i;
}
void trainprogram::onTapeStarted() { started = true; }
void trainprogram::restart() {
if (bluetoothManager && bluetoothManager->device())
lastOdometer = bluetoothManager->device()->odometer();
ticks = 0;
offset = 0;
currentStep = 0;
started = true;
}
bool trainprogram::saveXML(const QString &filename, const QList<trainrow> &rows) {
QFile output(filename);
if (!rows.isEmpty() && output.open(QIODevice::WriteOnly)) {
QXmlStreamWriter stream(&output);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement(QStringLiteral("rows"));
for (const trainrow &row : qAsConst(rows)) {
stream.writeStartElement(QStringLiteral("row"));
stream.writeAttribute(QStringLiteral("duration"), row.duration.toString());
if (row.distance >= 0) {
stream.writeAttribute(QStringLiteral("distance"), QString::number(row.distance));
}
if (row.speed >= 0) {
stream.writeAttribute(QStringLiteral("speed"), QString::number(row.speed));
}
if (row.minSpeed >= 0) {
stream.writeAttribute(QStringLiteral("minspeed"), QString::number(row.minSpeed));
}
if (row.inclination >= -50) {
stream.writeAttribute(QStringLiteral("inclination"), QString::number(row.inclination));
}
if (row.resistance >= 0) {
stream.writeAttribute(QStringLiteral("resistance"), QString::number(row.resistance));
}
if (row.lower_resistance >= 0) {
stream.writeAttribute(QStringLiteral("lower_resistance"), QString::number(row.lower_resistance));
}
if (row.mets >= 0) {
stream.writeAttribute(QStringLiteral("mets"), QString::number(row.mets));
}
if (!isnan(row.altitude)) {
stream.writeAttribute(QStringLiteral("altitude"), QString::number(row.altitude));
}
if (!isnan(row.azimuth)) {
stream.writeAttribute(QStringLiteral("azimuth"), QString::number(row.azimuth));
}
if (!isnan(row.latitude)) {
stream.writeAttribute(QStringLiteral("latitude"), QString::number(row.latitude));
}
if (!isnan(row.longitude)) {
stream.writeAttribute(QStringLiteral("longitude"), QString::number(row.longitude));
}
if (row.upper_resistance >= 0) {
stream.writeAttribute(QStringLiteral("upper_resistance"), QString::number(row.upper_resistance));
}
if (row.requested_peloton_resistance >= 0) {
stream.writeAttribute(QStringLiteral("requested_peloton_resistance"),
QString::number(row.requested_peloton_resistance));
}
if (row.lower_requested_peloton_resistance >= 0) {
stream.writeAttribute(QStringLiteral("lower_requested_peloton_resistance"),
QString::number(row.lower_requested_peloton_resistance));
}
if (row.upper_requested_peloton_resistance >= 0) {
stream.writeAttribute(QStringLiteral("upper_requested_peloton_resistance"),
QString::number(row.upper_requested_peloton_resistance));
}
if (row.pace_intensity >= 0) {
stream.writeAttribute(QStringLiteral("pace_intensity"), QString::number(row.pace_intensity));
}
if (row.cadence >= 0) {
stream.writeAttribute(QStringLiteral("cadence"), QString::number(row.cadence));
}
if (row.lower_cadence >= 0) {
stream.writeAttribute(QStringLiteral("lower_cadence"), QString::number(row.lower_cadence));
}
if (row.upper_cadence >= 0) {
stream.writeAttribute(QStringLiteral("upper_cadence"), QString::number(row.upper_cadence));
}
if (row.power >= 0) {
stream.writeAttribute(QStringLiteral("power"), QString::number(row.power));
}
stream.writeAttribute(QStringLiteral("forcespeed"),
row.forcespeed ? QStringLiteral("1") : QStringLiteral("0"));
if (row.fanspeed >= 0) {
stream.writeAttribute(QStringLiteral("fanspeed"), QString::number(row.fanspeed));
}
if (row.maxSpeed >= 0) {
stream.writeAttribute(QStringLiteral("maxspeed"), QString::number(row.maxSpeed));
}
if (row.maxResistance >= 0) {
stream.writeAttribute(QStringLiteral("maxresistance"), QString::number(row.maxResistance));
}
if (row.zoneHR >= 0) {
stream.writeAttribute(QStringLiteral("zonehr"), QString::number(row.zoneHR));
}
if (row.HRmin >= 0) {
stream.writeAttribute(QStringLiteral("hrmin"), QString::number(row.HRmin));
}
if (row.HRmax >= 0) {
stream.writeAttribute(QStringLiteral("hrmax"), QString::number(row.HRmax));
}
if (row.loopTimeHR >= 0) {
stream.writeAttribute(QStringLiteral("looptimehr"), QString::number(row.loopTimeHR));
}
stream.writeEndElement();
}
stream.writeEndElement();
stream.writeEndDocument();
return true;
} else
return false;
}
void trainprogram::save(const QString &filename) { saveXML(filename, rows); }
trainprogram *trainprogram::load(const QString &filename, bluetooth *b, QString Extension) {
if (!Extension.toUpper().compare(QStringLiteral("ZWO"))
#ifdef Q_OS_ANDROID
|| filename.toUpper().contains(".ZWO")
#endif
) {
QString description = "";
QString tags = "";
return new trainprogram(zwiftworkout::load(filename, &description, &tags), b, &description, &tags);
} else {
bluetoothdevice::BLUETOOTH_TYPE dtype = bluetoothdevice::BLUETOOTH_TYPE::BIKE;
if(b && b->device())
dtype = b->device()->deviceType();
return new trainprogram(loadXML(filename, dtype), b);
}
}
QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::BLUETOOTH_TYPE device_type) {
QList<trainrow> list;
QFile input(filename);
input.open(QIODevice::ReadOnly);
QXmlStreamReader stream(&input);
while (!stream.atEnd()) {
stream.readNext();
trainrow row;
QXmlStreamAttributes atts = stream.attributes();
bool ramp = false;
if (!atts.isEmpty()) {
if (atts.hasAttribute(QStringLiteral("duration"))) {
row.duration = QTime::fromString(atts.value(QStringLiteral("duration")).toString(), QStringLiteral("hh:mm:ss"));
}
if (atts.hasAttribute(QStringLiteral("distance"))) {
row.distance = atts.value(QStringLiteral("distance")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("speed"))) {
row.speed = atts.value(QStringLiteral("speed")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("minspeed"))) {
row.minSpeed = atts.value(QStringLiteral("minspeed")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("fanspeed"))) {
row.fanspeed = atts.value(QStringLiteral("fanspeed")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("inclination"))) {
row.inclination = atts.value(QStringLiteral("inclination")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("resistance"))) {
row.resistance = atts.value(QStringLiteral("resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("lower_resistance"))) {
row.lower_resistance = atts.value(QStringLiteral("lower_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("mets"))) {
row.mets = atts.value(QStringLiteral("mets")).toInt();
}
if (atts.hasAttribute(QStringLiteral("latitude"))) {
row.latitude = atts.value(QStringLiteral("latitude")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("longitude"))) {
row.longitude = atts.value(QStringLiteral("longitude")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("altitude"))) {
row.longitude = atts.value(QStringLiteral("altitude")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("azimuth"))) {
row.azimuth = atts.value(QStringLiteral("azimuth")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("upper_resistance"))) {
row.upper_resistance = atts.value(QStringLiteral("upper_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("requested_peloton_resistance"))) {
row.requested_peloton_resistance = atts.value(QStringLiteral("requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("lower_requested_peloton_resistance"))) {
row.lower_requested_peloton_resistance =
atts.value(QStringLiteral("lower_requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("upper_requested_peloton_resistance"))) {
row.upper_requested_peloton_resistance =
atts.value(QStringLiteral("upper_requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("pace_intensity"))) {
row.pace_intensity = atts.value(QStringLiteral("pace_intensity")).toInt();
}
if (atts.hasAttribute(QStringLiteral("cadence"))) {
row.cadence = atts.value(QStringLiteral("cadence")).toInt();
}
if (atts.hasAttribute(QStringLiteral("lower_cadence"))) {
row.lower_cadence = atts.value(QStringLiteral("lower_cadence")).toInt();
}
if (atts.hasAttribute(QStringLiteral("upper_cadence"))) {
row.upper_cadence = atts.value(QStringLiteral("upper_cadence")).toInt();
}
if (atts.hasAttribute(QStringLiteral("power"))) {
row.power = atts.value(QStringLiteral("power")).toInt();
}
if (atts.hasAttribute(QStringLiteral("maxspeed"))) {
row.maxSpeed = atts.value(QStringLiteral("maxspeed")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("maxresistance"))) {
row.maxResistance = atts.value(QStringLiteral("maxresistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("zonehr"))) {
row.zoneHR = atts.value(QStringLiteral("zonehr")).toInt();
}
if (atts.hasAttribute(QStringLiteral("hrmin"))) {
row.HRmin = atts.value(QStringLiteral("hrmin")).toInt();
}
if (atts.hasAttribute(QStringLiteral("hrmax"))) {
row.HRmax = atts.value(QStringLiteral("hrmax")).toInt();
}
if (atts.hasAttribute(QStringLiteral("looptimehr"))) {
row.loopTimeHR = atts.value(QStringLiteral("looptimehr")).toInt();
}
if (atts.hasAttribute(QStringLiteral("forcespeed"))) {
row.forcespeed = atts.value(QStringLiteral("forcespeed")).toInt() ? true : false;
}
if (atts.hasAttribute(QStringLiteral("powerzone"))) {
QSettings settings;
if(device_type == bluetoothdevice::TREADMILL) {
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
}
if (atts.hasAttribute(QStringLiteral("speedfrom")) && atts.hasAttribute(QStringLiteral("speedto")) && atts.hasAttribute(QStringLiteral("duration"))) {
double speedFrom = atts.value(QStringLiteral("speedfrom")).toDouble();
double speedTo = atts.value(QStringLiteral("speedto")).toDouble();
QTime duration = QTime::fromString(atts.value(QStringLiteral("duration")).toString(), QStringLiteral("hh:mm:ss"));
int durationS = duration.hour() * 3600 + duration.minute() * 60 + duration.second();
int speedDelta = (fabs(speedTo - speedFrom) / 0.1); // 0.1 min step for speed
int durationStep;
double speedStep;
int spareSeconds;
if(speedDelta <= durationS) {
durationStep = durationS / speedDelta;
speedStep = 0.1;
spareSeconds = durationS % speedDelta;
} else {
durationStep = 1;
speedStep = fabs(speedTo - speedFrom) / (durationS - 1);
speedDelta = durationS - 1;
spareSeconds = 0;
}
int spareSum = 0;
for (int i = 0; i <= speedDelta; i++) {
trainrow rowI(row);
int spare = 0;
if (spareSeconds)
spare = (i % spareSeconds == 0 && i > 0) ? 1 : 0;
spareSum += spare;
if (i == speedDelta && spareSum < spareSeconds) {
spare += (spareSeconds - spareSum) - durationStep;
spareSum = spareSeconds;
}
rowI.duration = QTime(0, 0, 0, 0).addSecs(durationStep + spare);
rowI.rampElapsed = QTime(0, 0, 0, 0).addSecs((durationStep * i) + spareSum);
rowI.rampDuration = QTime(0, 0, 0, 0).addSecs(durationS - (durationStep * i) - spareSum);
rowI.forcespeed = 1;
if (speedFrom < speedTo) {
rowI.speed = speedFrom + (speedStep * i);
} else {
rowI.speed = speedFrom - (speedStep * i);
}
qDebug() << "TrainRow" << rowI.toString();
list.append(rowI);
}
ramp = true;
}
if (atts.hasAttribute(QStringLiteral("powerzonefrom")) && atts.hasAttribute(QStringLiteral("powerzoneto")) && atts.hasAttribute(QStringLiteral("duration"))) {
QSettings settings;
double speedFrom = atts.value(QStringLiteral("powerzonefrom")).toDouble();
double speedTo = atts.value(QStringLiteral("powerzoneto")).toDouble();
QTime duration = QTime::fromString(atts.value(QStringLiteral("duration")).toString(), QStringLiteral("hh:mm:ss"));
int durationS = duration.hour() * 3600 + duration.minute() * 60 + duration.second();
int speedDelta = (fabs(speedTo - speedFrom) / 0.01); // 0.01 min step for speed
int durationStep;
double speedStep;
int spareSeconds;
if(speedDelta == 0)
speedDelta = 1;
if(speedDelta <= durationS) {
durationStep = durationS / speedDelta;
speedStep = 0.01;
spareSeconds = durationS % speedDelta;
} else {
durationStep = 1;
speedStep = fabs(speedTo - speedFrom) / (durationS - 1);
speedDelta = durationS - 1;
spareSeconds = 0;
}
int spareSum = 0;
for (int i = 0; i <= speedDelta; i++) {
trainrow rowI(row);
int spare = 0;
if (spareSeconds)
spare = (i % spareSeconds == 0 && i > 0) ? 1 : 0;
spareSum += spare;
if (i == speedDelta && spareSum < spareSeconds) {
spare += (spareSeconds - spareSum) - durationStep;
spareSum = spareSeconds;
}
rowI.duration = QTime(0, 0, 0, 0).addSecs(durationStep + spare);
rowI.rampElapsed = QTime(0, 0, 0, 0).addSecs((durationStep * i) + spareSum);
rowI.rampDuration = QTime(0, 0, 0, 0).addSecs(durationS - (durationStep * i) - spareSum);
rowI.forcespeed = 1;
if (speedFrom < speedTo) {
if(device_type == bluetoothdevice::TREADMILL) {
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
} else {
if(device_type == bluetoothdevice::TREADMILL) {
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
}
qDebug() << "TrainRow" << rowI.toString();
list.append(rowI);
}
ramp = true;
}
if(!ramp) {
list.append(row);
qDebug() << row.toString();
}
}
}
return list;
}
QTime trainprogram::totalElapsedTime() { return QTime(0, 0, ticks); }
trainrow trainprogram::currentRow() {
if (started && !rows.isEmpty()) {
return rows.at(currentStep);
}
return trainrow();
}
trainrow trainprogram::getRowFromCurrent(uint32_t offset) {
if (started && !rows.isEmpty() && (currentStep + offset) < (uint32_t)rows.length()) {
return rows.at(currentStep + offset);
}
return trainrow();
}
double trainprogram::currentTargetMets() {
if (currentRow().mets)
return currentRow().mets;
else
return 0;
}
QTime trainprogram::currentRowElapsedTime() {
uint32_t calculatedLine;
uint32_t calculatedElapsedTime = 0;
if (rows.length() == 0)
return QTime(0, 0, 0);
for (calculatedLine = 0; calculatedLine < static_cast<uint32_t>(rows.length()); calculatedLine++) {
uint32_t currentLine = calculateTimeForRow(calculatedLine);
calculatedElapsedTime += currentLine;
uint32_t rampElapsed = 0;
if (calculatedElapsedTime > static_cast<uint32_t>(ticks)) {
if (rows.at(calculatedLine).rampElapsed != QTime(0, 0, 0)) {
rampElapsed = (rows.at(calculatedLine).rampElapsed.second() +
(rows.at(calculatedLine).rampElapsed.minute() * 60) +
(rows.at(calculatedLine).rampElapsed.hour() * 3600));
}
return QTime(0, 0, 0).addSecs(rampElapsed + ticks - (calculatedElapsedTime - currentLine));
}
}
return QTime(0, 0, 0);
}
QTime trainprogram::currentRowRemainingTime() {
uint32_t calculatedLine;
uint32_t calculatedElapsedTime = 0;
if (rows.length() == 0)
return QTime(0, 0, 0);
if (currentStep < rows.length() && rows.at(currentStep).distance > 0 && bluetoothManager &&
bluetoothManager->device()) {
double speed = bluetoothManager->device()->currentSpeed().value();
double distance = rows.at(currentStep).distance;
distance -= currentStepDistance;
int seconds = (distance / speed) * 3600.0;
int hours = seconds / 3600;
return QTime(hours, (seconds / 60) - (hours * 60), seconds % 60);
} else {
for (calculatedLine = 0; calculatedLine < static_cast<uint32_t>(rows.length()); calculatedLine++) {
uint32_t currentLine = calculateTimeForRow(calculatedLine);
calculatedElapsedTime += currentLine;
if (calculatedElapsedTime > static_cast<uint32_t>(ticks)) {
if (rows.at(calculatedLine).rampDuration != QTime(0, 0, 0)) {
calculatedElapsedTime += ((rows.at(calculatedLine).rampDuration.second() +
(rows.at(calculatedLine).rampDuration.minute() * 60) +
(rows.at(calculatedLine).rampDuration.hour() * 3600))) -
1;
}
int seconds = calculatedElapsedTime - ticks;
int hours = seconds / 3600;
return QTime(hours, (seconds / 60) - (hours * 60), seconds % 60);
}
}
}
return QTime(0, 0, 0);
}
QTime trainprogram::remainingTime() {
uint32_t calculatedLine;
uint32_t calculatedTotalTime = 0;
if (rows.length() == 0)
return QTime(0, 0, 0);
for (calculatedLine = 0; calculatedLine < static_cast<uint32_t>(rows.length()); calculatedLine++) {
calculatedTotalTime += calculateTimeForRow(calculatedLine);
}
return QTime(0, 0, 0).addSecs(calculatedTotalTime - ticks);
}
QTime trainprogram::duration() {
QTime total(0, 0, 0, 0);
for (const trainrow &row : qAsConst(rows)) {
total = total.addSecs((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second());
}
return total;
}
double trainprogram::totalDistance() {
double distance = 0;
for (const trainrow &row : qAsConst(rows)) {
if (row.duration.hour() || row.duration.minute() || row.duration.second()) {
if (!row.forcespeed) {
return -1;
}
distance += ((row.duration.hour() * 3600) + (row.duration.minute() * 60) + row.duration.second()) *
(row.speed / 3600);
}
}
return distance;
}