Files
GoldenCheetah/src/RealtimeWindow.cpp
Mark Liversedge e93970ed4d Introduce Toolbox to GUI
The left side of MainWindow is now a toolbox which contains
Ride Analysis and Racing and Training options. The Right side
tabs have been reorganised and associated with the toolbox.

To support the population of the Racing and Training left tool
the config dialog has been updated to enable the user to configure
the location of their workouts.

In addition, the config dialog now calls upon MainWindow to issue
a configUpdate() signal to notify widgets when config has been
updated. This is a refactoring of the existing mechanism that
only called realtimeWindow->updateConfig, now any widget can
connect to the MainWindow signal and re-read its config appropriately.
Currently, the TrainTool and RealtimeWindow widgets have been coded to
use this.
2009-12-20 21:39:38 -05:00

872 lines
30 KiB
C++

/*
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "MainWindow.h"
#include "RealtimeWindow.h"
#include "RealtimeData.h"
#include "RealtimePlot.h"
#include "math.h" // for round()
#include "Units.h" // for MILES_PER_KM
// Three current realtime device types supported are:
#include "ComputrainerController.h"
#include "ANTplusController.h"
#include "SimpleNetworkController.h"
#include "ErgFile.h"
#include "TrainTool.h"
void
RealtimeWindow::configUpdate()
{
// config has been updated! so re-read
// wipe out all the current entries
deviceSelector->clear();
streamSelector->clear();
// metric or imperial changed?
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant unit = settings->value(GC_UNIT);
useMetricUnits = (unit.toString() == "Metric");
// set labels accordingly
distanceLabel->setText(useMetricUnits ? tr("Distance (KM)") : tr("Distance (Miles)"));
speedLabel->setText(useMetricUnits ? tr("KPH") : tr("MPH"));
avgspeedLabel->setText(useMetricUnits ? tr("Avg KPH") : tr("Avg MPH"));
// get configured devices
DeviceConfigurations all;
Devices.clear();
Devices = all.getList();
streamSelector->addItem("No streaming", -1);
for (int i=0; i<Devices.count(); i++) {
// add streamers
if (Devices.at(i).type == DEV_GSERVER)
streamSelector->addItem(Devices.at(i).name, i);
// add data sources
deviceSelector->addItem(Devices.at(i).name, i);
}
deviceSelector->setCurrentIndex(0);
streamSelector->setCurrentIndex(0);
// reconnect - otherwise after no config they still get an error
if (Devices.count() > 0) {
disconnect(startButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
disconnect(pauseButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
disconnect(stopButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
connect(startButton, SIGNAL(clicked()), this, SLOT(Start()));
connect(pauseButton, SIGNAL(clicked()), this, SLOT(Pause()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(Stop()));
}
}
RealtimeWindow::RealtimeWindow(MainWindow *parent, TrainTool *trainTool, const QDir &home) : QWidget(parent)
{
// set home
this->home = home;
this->trainTool = trainTool;
main = parent;
deviceController = NULL;
streamController = NULL;
ergFile = NULL;
// metric or imperial?
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant unit = settings->value(GC_UNIT);
useMetricUnits = (unit.toString() == "Metric");
// main layout for the window
main_layout = new QVBoxLayout(this);
timer_layout = new QGridLayout();
// BUTTONS AND LCDS
button_layout = new QHBoxLayout();
option_layout = new QHBoxLayout();
controls_layout = new QVBoxLayout();
deviceSelector = new QComboBox(this);
streamSelector = new QComboBox(this);
// get configured devices
DeviceConfigurations all;
Devices = all.getList();
streamSelector->addItem("No streaming", -1);
for (int i=0; i<Devices.count(); i++) {
// add streamers
if ( Devices.at(i).type == DEV_GSERVER ||
Devices.at(i).type == DEV_GCLIENT )
streamSelector->addItem(Devices.at(i).name, i);
// add data sources
deviceSelector->addItem(Devices.at(i).name, i);
}
deviceSelector->setCurrentIndex(0);
streamSelector->setCurrentIndex(0);
recordSelector = new QCheckBox(this);
recordSelector->setText(tr("Save"));
recordSelector->setChecked(Qt::Checked);
startButton = new QPushButton(tr("Start"), this);
startButton->setMaximumHeight(100);
pauseButton = new QPushButton(tr("Pause"), this);
pauseButton->setMaximumHeight(100);
stopButton = new QPushButton(tr("Stop"), this);
stopButton->setMaximumHeight(100);
button_layout->addWidget(startButton);
button_layout->addWidget(pauseButton);
button_layout->addWidget(stopButton);
option_layout->addWidget(deviceSelector);
option_layout->addSpacing(10);
option_layout->addWidget(recordSelector);
option_layout->addSpacing(10);
option_layout->addWidget(streamSelector);
// XXX NETWORK STREAMING DISABLED IN THIS RELEASE SO HIDE THE COMBO
streamSelector->hide();
option_layout->addSpacing(10);
controls_layout->addItem(option_layout);
controls_layout->addItem(button_layout);
// handle config changes
connect(main, SIGNAL(configChanged()), this, SLOT(configUpdate()));
connect(deviceSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(SelectDevice(int)));
connect(recordSelector, SIGNAL(clicked()), this, SLOT(SelectRecord()));
connect(streamSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(SelectStream(int)));
connect(trainTool, SIGNAL(workoutSelected()), this, SLOT(SelectWorkout()));
if (Devices.count() > 0) {
connect(startButton, SIGNAL(clicked()), this, SLOT(Start()));
connect(pauseButton, SIGNAL(clicked()), this, SLOT(Pause()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(Stop()));
} else {
connect(startButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
connect(pauseButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(warnnoConfig()));
}
powerLabel = new QLabel(tr("WATTS"), this);
powerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
heartrateLabel = new QLabel(tr("BPM"), this);
heartrateLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
speedLabel = new QLabel(useMetricUnits ? tr("KPH") : tr("MPH"), this);
speedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
cadenceLabel = new QLabel(tr("RPM"), this);
cadenceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
lapLabel = new QLabel(tr("Lap/Interval"), this);
lapLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
loadLabel = new QLabel(tr("Load WATTS"), this);
loadLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
distanceLabel = new QLabel(useMetricUnits ? tr("Distance (KM)") : tr("Distance (Miles)"), this);
distanceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
avgpowerLabel = new QLabel(tr("Avg WATTS"), this);
avgpowerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
avgheartrateLabel = new QLabel(tr("Avg BPM"), this);
avgheartrateLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
avgspeedLabel = new QLabel(useMetricUnits ? tr("Avg KPH") : tr("Avg MPH"), this);
avgspeedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
avgcadenceLabel = new QLabel(tr("Avg RPM"), this);
avgcadenceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
avgloadLabel = new QLabel(tr("Avg Load WATTS"), this);
avgloadLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
laptimeLabel = new QLabel(tr("LAP TIME"), this);
laptimeLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
timeLabel = new QLabel(tr("TIME"), this);
timeLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom);
powerLCD = new QLCDNumber(this); powerLCD->setSegmentStyle(QLCDNumber::Filled);
heartrateLCD = new QLCDNumber(this); heartrateLCD->setSegmentStyle(QLCDNumber::Filled);
speedLCD = new QLCDNumber(this); speedLCD->setSegmentStyle(QLCDNumber::Filled);
cadenceLCD = new QLCDNumber(this); cadenceLCD->setSegmentStyle(QLCDNumber::Filled);
lapLCD = new QLCDNumber(this); lapLCD->setSegmentStyle(QLCDNumber::Filled);
loadLCD = new QLCDNumber(this); loadLCD->setSegmentStyle(QLCDNumber::Filled);
distanceLCD = new QLCDNumber(this); distanceLCD->setSegmentStyle(QLCDNumber::Filled);
avgpowerLCD = new QLCDNumber(this); avgpowerLCD->setSegmentStyle(QLCDNumber::Filled);
avgheartrateLCD = new QLCDNumber(this); avgheartrateLCD->setSegmentStyle(QLCDNumber::Filled);
avgspeedLCD = new QLCDNumber(this); avgspeedLCD->setSegmentStyle(QLCDNumber::Filled);
avgcadenceLCD = new QLCDNumber(this); avgcadenceLCD->setSegmentStyle(QLCDNumber::Filled);
avgloadLCD = new QLCDNumber(this); avgloadLCD->setSegmentStyle(QLCDNumber::Filled);
laptimeLCD = new QLCDNumber(this); laptimeLCD->setSegmentStyle(QLCDNumber::Filled);
laptimeLCD->setNumDigits(9);
timeLCD = new QLCDNumber(this); timeLCD->setSegmentStyle(QLCDNumber::Filled);
timeLCD->setNumDigits(9);
gridLayout = new QGridLayout();
gridLayout->addWidget(powerLabel, 1, 0);
gridLayout->addWidget(cadenceLabel, 1, 1);
gridLayout->addWidget(heartrateLabel, 1, 2);
gridLayout->addWidget(speedLabel, 1, 3);
gridLayout->addWidget(distanceLabel, 1, 4);
gridLayout->addWidget(lapLabel, 1, 5);
gridLayout->addWidget(powerLCD, 2, 0);
gridLayout->addWidget(cadenceLCD, 2, 1);
gridLayout->addWidget(heartrateLCD, 2, 2);
gridLayout->addWidget(speedLCD, 2, 3);
gridLayout->addWidget(distanceLCD, 2, 4);
gridLayout->addWidget(lapLCD, 2, 5);
gridLayout->addWidget(avgpowerLabel, 3, 0);
gridLayout->addWidget(avgcadenceLabel, 3, 1);
gridLayout->addWidget(avgheartrateLabel, 3, 2);
gridLayout->addWidget(avgspeedLabel, 3, 3);
gridLayout->addWidget(avgloadLabel, 3, 4);
gridLayout->addWidget(loadLabel, 3, 5);
gridLayout->addWidget(loadLCD, 4, 5);
gridLayout->addWidget(avgpowerLCD, 4, 0);
gridLayout->addWidget(avgcadenceLCD, 4, 1);
gridLayout->addWidget(avgheartrateLCD, 4, 2);
gridLayout->addWidget(avgspeedLCD, 4, 3);
gridLayout->addWidget(avgloadLCD, 4, 4);
gridLayout->setRowStretch(2, 4);
gridLayout->setRowStretch(4, 3);
// timers etc
timer_layout->addWidget(timeLabel, 0, 0);
timer_layout->addWidget(laptimeLabel, 0, 3);
timer_layout->addWidget(timeLCD, 1, 0);
timer_layout->addItem(controls_layout, 1,1);
timer_layout->addWidget(laptimeLCD, 1, 3);
timer_layout->setRowStretch(0, 1);
timer_layout->setRowStretch(1, 4);
// REALTIME PLOT
rtPlot = new RealtimePlot();
// COURSE PLOT
ergPlot = new ErgFilePlot(0);
ergPlot->setVisible(false);
// LAYOUT
main_layout->addWidget(ergPlot);
main_layout->addItem(timer_layout);
main_layout->addItem(gridLayout);
main_layout->addWidget(rtPlot);
displaymode=2;
main_layout->setStretch(0,1);
main_layout->setStretch(1,1);
main_layout->setStretch(2,2);
main_layout->setStretch(3, displaymode);
// now the GUI is setup lets sort our control variables
gui_timer = new QTimer(this);
disk_timer = new QTimer(this);
stream_timer = new QTimer(this);
load_timer = new QTimer(this);
recordFile = NULL;
status = 0;
status |= RT_RECORDING; // recording is on by default! - add others here
status |= RT_MODE_ERGO; // ergo mode by default
displayWorkoutLap = displayLap = 0;
pwrcount = 0;
cadcount = 0;
hrcount = 0;
spdcount = 0;
lodcount = 0;
load_msecs = total_msecs = lap_msecs = 0;
displayWorkoutDistance = displayDistance = displayPower = displayHeartRate =
displaySpeed = displayCadence = displayGradient = displayLoad = 0;
avgPower= avgHeartRate= avgSpeed= avgCadence= avgLoad= 0;
connect(gui_timer, SIGNAL(timeout()), this, SLOT(guiUpdate()));
connect(disk_timer, SIGNAL(timeout()), this, SLOT(diskUpdate()));
connect(stream_timer, SIGNAL(timeout()), this, SLOT(streamUpdate()));
connect(load_timer, SIGNAL(timeout()), this, SLOT(loadUpdate()));
// setup the controller based upon currently selected device
setDeviceController();
rtPlot->replot();
}
void RealtimeWindow::setDeviceController()
{
int deviceno = this->deviceSelector->currentIndex();
// zap the current one
if (deviceController != NULL) {
delete deviceController;
deviceController = NULL;
}
if (Devices.count() > 0) {
DeviceConfiguration temp = Devices.at(deviceno);
if (Devices.at(deviceno).type == DEV_ANTPLUS) {
deviceController = new ANTplusController(this, &temp);
} else if (Devices.at(deviceno).type == DEV_CT) {
deviceController = new ComputrainerController(this, &temp);
} else if (Devices.at(deviceno).type == DEV_GSERVER) {
deviceController = new SimpleNetworkController(this, &temp);
}
}
}
void RealtimeWindow::setStreamController()
{
int deviceno = streamSelector->itemData(streamSelector->currentIndex()).toInt();
// zap the current one
if (streamController != NULL) {
delete streamController;
streamController = NULL;
}
if (Devices.count() > 0) {
DeviceConfiguration temp = Devices.at(deviceno);
if (Devices.at(deviceno).type == DEV_ANTPLUS) {
streamController = new ANTplusController(this, &temp);
} else if (Devices.at(deviceno).type == DEV_CT) {
streamController = new ComputrainerController(this, &temp);
} else if (Devices.at(deviceno).type == DEV_GSERVER) {
streamController = new SimpleNetworkController(this, &temp);
}
}
}
void RealtimeWindow::SelectDevice(int index)
{
if (index != -1)
setDeviceController();
}
void RealtimeWindow::Start() // when start button is pressed
{
if (status&RT_RUNNING) {
newLap();
} else {
status |=RT_RUNNING;
deviceController->start(); // start device
startButton->setText(tr("Lap/Interval"));
recordSelector->setEnabled(false);
streamSelector->setEnabled(false);
deviceSelector->setEnabled(false);
if (status & RT_WORKOUT) {
load_timer->start(LOADRATE); // start recording
}
if (status & RT_RECORDING) {
// setup file
QDate date = QDate().currentDate();
QTime time = QTime().currentTime();
QDateTime now(date, time);
QChar zero = QLatin1Char ( '0' );
QString filename = QString ( "%1_%2_%3_%4_%5_%6.csv" )
.arg ( now.date().year(), 4, 10, zero )
.arg ( now.date().month(), 2, 10, zero )
.arg ( now.date().day(), 2, 10, zero )
.arg ( now.time().hour(), 2, 10, zero )
.arg ( now.time().minute(), 2, 10, zero )
.arg ( now.time().second(), 2, 10, zero );
QString fulltarget = home.absolutePath() + "/" + filename;
if (recordFile) delete recordFile;
recordFile = new QFile(fulltarget);
if (!recordFile->open(QFile::WriteOnly | QFile::Truncate)) {
status &= ~RT_RECORDING;
} else {
// CSV File header
QTextStream recordFileStream(recordFile);
recordFileStream << "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)\n";
disk_timer->start(SAMPLERATE); // start screen
}
}
// stream
if (status & RT_STREAMING) {
streamController->start();
stream_timer->start(STREAMRATE);
}
gui_timer->start(REFRESHRATE); // start recording
}
}
void RealtimeWindow::Pause() // pause capture to recalibrate
{
// we're not running fool!
if ((status&RT_RUNNING) == 0) return;
if (status&RT_PAUSED) {
status &=~RT_PAUSED;
deviceController->restart();
pauseButton->setText(tr("Pause"));
gui_timer->start(REFRESHRATE);
if (status & RT_STREAMING) stream_timer->start(STREAMRATE);
if (status & RT_RECORDING) disk_timer->start(SAMPLERATE);
if (status & RT_WORKOUT) load_timer->start(LOADRATE);
} else {
deviceController->pause();
pauseButton->setText(tr("Un-Pause"));
status |=RT_PAUSED;
gui_timer->stop();
if (status & RT_STREAMING) stream_timer->stop();
if (status & RT_RECORDING) disk_timer->stop();
if (status & RT_WORKOUT) load_timer->stop();
}
}
void RealtimeWindow::Stop() // when stop button is pressed
{
if ((status&RT_RUNNING) == 0) return;
status &= ~RT_RUNNING;
startButton->setText(tr("Start"));
deviceController->stop();
gui_timer->stop();
if (status & RT_RECORDING) {
disk_timer->stop();
// close and reset File
recordFile->close();
// add to the view - using basename ONLY
QString name;
name = recordFile->fileName();
main->addRide(QFileInfo(name).fileName(), true);
}
if (status & RT_STREAMING) {
stream_timer->stop();
streamController->stop();
}
if (status & RT_WORKOUT) {
load_timer->stop();
load_msecs = 0;
ergPlot->setNow(load_msecs);
ergPlot->replot();
}
// Re-enable gui elements
recordSelector->setEnabled(true);
streamSelector->setEnabled(true);
deviceSelector->setEnabled(true);
// reset counters etc
pwrcount = 0;
cadcount = 0;
hrcount = 0;
spdcount = 0;
lodcount = 0;
displayWorkoutLap = displayLap =0;
lap_msecs = 0;
total_msecs = 0;
avgPower= avgHeartRate= avgSpeed= avgCadence= avgLoad= 0;
displayWorkoutDistance = displayDistance = 0;
guiUpdate();
return;
}
// Called by push devices (e.g. ANT+)
void RealtimeWindow::updateData(RealtimeData &rtData)
{
displayPower = rtData.getWatts();
displayCadence = rtData.getRPM();
displayHeartRate = rtData.getHr();
displaySpeed = rtData.getSpeed();
displayLoad = rtData.getLoad();
// Gradient not supported
return;
}
//----------------------------------------------------------------------
// SCREEN UPDATE FUNCTIONS
//----------------------------------------------------------------------
void RealtimeWindow::guiUpdate() // refreshes the telemetry
{
RealtimeData rtData;
// get latest telemetry from device (if it is a pull device e.g. Computrainer //
if (status&RT_RUNNING && deviceController->doesPull() == true) {
deviceController->getRealtimeData(rtData);
displayPower = rtData.getWatts();
displayCadence = rtData.getRPM();
displayHeartRate = rtData.getHr();
displaySpeed = rtData.getSpeed();
displayLoad = rtData.getLoad();
}
// Distance assumes current speed for the last second. from km/h to km/sec
displayDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate
displayWorkoutDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate
// update those LCDs!
timeLCD->display(QString("%1:%2:%3.%4").arg(total_msecs/3600000)
.arg((total_msecs%3600000)/60000,2,10,QLatin1Char('0'))
.arg((total_msecs%60000)/1000,2,10,QLatin1Char('0'))
.arg((total_msecs%1000)/100));
laptimeLCD->display(QString("%1:%2:%3.%4").arg(lap_msecs/3600000,2)
.arg((lap_msecs%3600000)/60000,2,10,QLatin1Char('0'))
.arg((lap_msecs%60000)/1000,2,10,QLatin1Char('0'))
.arg((lap_msecs%1000)/100));
// Cadence, HR and Power needs to be rounded to 0 decimal places
powerLCD->display(round(displayPower));
speedLCD->display(round(displaySpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00);
cadenceLCD->display(round(displayCadence));
heartrateLCD->display(round(displayHeartRate));
lapLCD->display(displayWorkoutLap+displayLap);
// load or gradient depending on mode we are running
if (status&RT_MODE_ERGO) loadLCD->display(displayLoad);
else loadLCD->display(round(displayGradient*10)/10.00);
// distance
distanceLCD->display(round(displayDistance*(useMetricUnits ? 1.0 : MILES_PER_KM) *10.00) /10.00);
// NZ Averages.....
if (displayPower) { //NZAP is bogus - make it configurable!!!
pwrcount++; if (pwrcount ==1) avgPower = displayPower;
avgPower = ((avgPower * (double)pwrcount) + displayPower) /(double) (pwrcount+1);
}
if (displayCadence) {
cadcount++; if (cadcount ==1) avgCadence = displayCadence;
avgCadence = ((avgCadence * (double)cadcount) + displayCadence) /(double) (cadcount+1);
}
if (displayHeartRate) {
hrcount++; if (hrcount ==1) avgHeartRate = displayHeartRate;
avgHeartRate = ((avgHeartRate * (double)hrcount) + displayHeartRate) /(double) (hrcount+1);
}
if (displaySpeed) {
spdcount++; if (spdcount ==1) avgSpeed = displaySpeed;
avgSpeed = ((avgSpeed * (double)spdcount) + displaySpeed) /(double) (spdcount+1);
}
if (displayLoad && status&RT_MODE_ERGO) {
lodcount++; if (lodcount ==1) avgLoad = displayLoad;
avgLoad = ((avgLoad * (double)lodcount) + displayLoad) /(double) (lodcount+1);
avgloadLCD->display((int)avgLoad);
}
if (status&RT_MODE_SPIN) {
grdcount++; if (grdcount ==1) avgGradient = displayGradient;
avgGradient = ((avgGradient * (double)grdcount) + displayGradient) /(double) (grdcount+1);
avgloadLCD->display((int)avgGradient);
}
avgpowerLCD->display((int)avgPower);
avgspeedLCD->display(round(avgSpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00);
avgcadenceLCD->display((int)avgCadence);
avgheartrateLCD->display((int)avgHeartRate);
// now that plot....
rtPlot->pwrData.addData(displayPower); // add new data point
rtPlot->cadData.addData(displayCadence); // add new data point
rtPlot->spdData.addData(displaySpeed); // add new data point
rtPlot->hrData.addData(displayHeartRate); // add new data point
//rtPlot->lodData.addData(displayLoad); // add new Load point
rtPlot->replot(); // redraw
this->update();
// add time
total_msecs += REFRESHRATE;
lap_msecs += REFRESHRATE;
}
// can be called from the controller - when user presses "Lap" button
void RealtimeWindow::newLap()
{
displayLap++;
lap_msecs = 0;
pwrcount = 0;
cadcount = 0;
hrcount = 0;
spdcount = 0;
// set avg to current values to ensure averages represent from now onwards
// and not from beginning of workout
avgPower = displayPower;
avgCadence = displayCadence;
avgHeartRate = displayHeartRate;
avgSpeed = displaySpeed;
avgLoad = displayLoad;
}
// can be called from the controller
void RealtimeWindow::nextDisplayMode()
{
if (displaymode == 8) displaymode=2;
else displaymode += 1;
main_layout->setStretch(3,displaymode);
}
void RealtimeWindow::warnnoConfig()
{
QMessageBox::warning(this, tr("No Devices Configured"), "Please configure a device in Preferences.");
}
//----------------------------------------------------------------------
// STREAMING FUNCTION
//----------------------------------------------------------------------
void
RealtimeWindow::SelectStream(int index)
{
if (index > 0) {
status |= RT_STREAMING;
setStreamController();
} else {
status &= ~RT_STREAMING;
}
}
void
RealtimeWindow::streamUpdate()
{
RealtimeData rtData;
// get current telemetry...
rtData.setWatts(displayPower);
rtData.setRPM(displayCadence);
rtData.setHr(displayHeartRate);
rtData.setSpeed(displaySpeed);
rtData.setLoad(displayLoad);
rtData.setTime(0);
// send over the wire...
streamController->pushRealtimeData(rtData);
}
//----------------------------------------------------------------------
// DISK UPDATE FUNCTIONS
//----------------------------------------------------------------------
void
RealtimeWindow::SelectRecord()
{
if (recordSelector->isChecked()) {
status |= RT_RECORDING;
} else {
status &= ~RT_RECORDING;
}
}
void RealtimeWindow::diskUpdate()
{
double Minutes;
long Torq = 0, Altitude = 0;
QTextStream recordFileStream(recordFile);
// convert from milliseconds to minutes
Minutes = total_msecs;
Minutes /= 1000.00;
Minutes *= (1.0/60);
// PowerAgent Format "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)"
recordFileStream << Minutes
<< "," << Torq
<< "," << displaySpeed
<< "," << displayPower
<< "," << displayDistance
<< "," << displayCadence
<< "," << displayHeartRate
<< "," << (displayLap + displayWorkoutLap)
<< "," << Altitude
<< "," << "\n";
}
//----------------------------------------------------------------------
// WORKOUT MODE
//----------------------------------------------------------------------
void
RealtimeWindow::SelectWorkout()
{
int mode;
// wip away the current selected workout
if (ergFile) {
delete ergFile;
ergFile = NULL;
}
// which one is selected?
if (trainTool->currentWorkout() == NULL || trainTool->currentWorkout()->type() != WORKOUT_TYPE) return;
// is it the auto mode?
int index = trainTool->workoutItems()->indexOfChild((QTreeWidgetItem *)trainTool->currentWorkout());
if (index == 0) {
// ergo mode
mode = ERG;
status &= ~RT_WORKOUT;
ergPlot->setVisible(false);
} else if (index == 1) {
// slope mode
mode = CRS;
status &= ~RT_WORKOUT;
ergPlot->setVisible(false);
} else {
// workout mode
boost::shared_ptr<QSettings> settings = GetApplicationSettings();
QVariant workoutDir = settings->value(GC_WORKOUTDIR);
QString fileName = workoutDir.toString() + "/" + trainTool->currentWorkout()->text(0); // filename
// Get users CP for relative watts calculations
QDate today = QDate::currentDate();
double Cp=285; // default to 285 if zones are not set
int range = main->zones()->whichRange(today);
if (range != -1) Cp = main->zones()->getCP(range);
ergFile = new ErgFile(fileName, mode, Cp);
if (ergFile->isValid()) {
status |= RT_WORKOUT;
// success! we have a load file
// setup the course profile in the
// display!
ergPlot->setData(ergFile);
ergPlot->setVisible(true);
ergPlot->replot();
}
}
// set the device to the right mode
if (mode == ERG) {
status |= RT_MODE_ERGO;
status &= ~RT_MODE_SPIN;
if (deviceController != NULL) deviceController->setMode(RT_MODE_ERGO);
// set the labels on the gui
loadLabel->setText("Load WATTS");
avgloadLabel->setText("Avg Load WATTS");
} else { // SLOPE MODE
status |= RT_MODE_SPIN;
status &= ~RT_MODE_ERGO;
if (deviceController != NULL) deviceController->setMode(RT_MODE_SPIN);
// set the labels on the gui
loadLabel->setText("Gradient PERCENT");
avgloadLabel->setText("Avg Gradient PERCENT");
}
}
void RealtimeWindow::loadUpdate()
{
long load;
double gradient;
load_msecs += LOADRATE;
if (status&RT_MODE_ERGO) {
load = ergFile->wattsAt(load_msecs, displayWorkoutLap);
// we got to the end!
if (load == -100) {
Stop();
} else {
displayLoad = load;
deviceController->setLoad(displayLoad);
ergPlot->setNow(load_msecs);
}
} else {
gradient = ergFile->gradientAt(displayWorkoutDistance*1000, displayWorkoutLap);
// we got to the end!
if (gradient == -100) {
Stop();
} else {
displayGradient = gradient;
deviceController->setGradient(displayGradient);
ergPlot->setNow(displayWorkoutDistance * 1000); // now is in meters we keep it in kilometers
}
}
}
void RealtimeWindow::FFwd()
{
if (status&RT_MODE_ERGO) load_msecs += 10000; // jump forward 10 seconds
else displayWorkoutDistance += 1; // jump forward a kilometer in the workout
}
void RealtimeWindow::Rewind()
{
if (status&RT_MODE_ERGO) {
load_msecs -=10000; // jump back 10 seconds
if (load_msecs < 0) load_msecs = 0;
} else {
displayWorkoutDistance -=1; // jump back a kilometer
if (displayWorkoutDistance < 0) displayWorkoutDistance = 0;
}
}
// jump to next Lap marker (if there is one?)
void RealtimeWindow::FFwdLap()
{
double lapmarker;
if (status&RT_MODE_ERGO) {
lapmarker = ergFile->nextLap(load_msecs);
if (lapmarker != -1) load_msecs = lapmarker; // jump forward to lapmarker
} else {
lapmarker = ergFile->nextLap(displayWorkoutDistance*1000);
if (lapmarker != -1) displayWorkoutDistance = lapmarker/1000; // jump forward to lapmarker
}
}
// higher load/gradient
void RealtimeWindow::Higher()
{
if (status&RT_MODE_ERGO) displayLoad += 5;
else displayGradient += 0.1;
if (displayLoad >1500) displayLoad = 1500;
if (displayGradient >15) displayGradient = 15;
if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad);
else deviceController->setGradient(displayGradient);
}
// higher load/gradient
void RealtimeWindow::Lower()
{
if (status&RT_MODE_ERGO) displayLoad -= 5;
else displayGradient -= 0.1;
if (displayLoad <0) displayLoad = 0;
if (displayGradient <-10) displayGradient = -10;
if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad);
else deviceController->setGradient(displayGradient);
}