/* * 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 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; iaddItem(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 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; iaddItem(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 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); }