diff --git a/INSTALL-LINUX b/INSTALL-LINUX index 442b7dcdc..ec71d0273 100644 --- a/INSTALL-LINUX +++ b/INSTALL-LINUX @@ -31,6 +31,7 @@ CONTENTS - flex - bison - libical - Diary window and CalDAV support (google/mobileme calendar integration) + - libvlc - Video playback in training mode 1. BASIC INSTALLATION WITH MANDATORY DEPENDENCIES @@ -87,6 +88,13 @@ and it should read like this (take off trailing /include): BOOST_INCLUDE=$${BOOST_INSTALL) +If you are building for your local host you may find that you get better performance if +compiling with gcc -O3 (tree vectorization can have a significat impact) + +If so you might like to uncomment: + +QMAKE_CXXFLAGS += -O3 + Save and exit $ cd .. @@ -358,3 +366,18 @@ NOTE: That upload to MobileMe and Google requires a functioning https lib in QT. upon the version installed this might not be the case and will need to be built and configured -- this is beyond the scope of this walkthough. Sorry. +LIBVLC - Video playback in Realtime (Experimental) +-------------------------------------------------- + +You will need libvlc 1.1.9 or higher (1.1.8 is ok but will segv on exit) +$ sudo apt-get install libvlc-dev + +$ vi gcconfig.pri + +Comment out VLC_INSTALL and it should read: + +VLC_INSTALL = /usr/include/vlc/ + +$ make clean +$ qmake +$ make diff --git a/src/ANTlocalController.cpp b/src/ANTlocalController.cpp index 1e6d1704b..aa7e17fea 100644 --- a/src/ANTlocalController.cpp +++ b/src/ANTlocalController.cpp @@ -22,7 +22,7 @@ #include "ANT.h" #include "RealtimeData.h" -ANTlocalController::ANTlocalController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ANTlocalController::ANTlocalController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myANTlocal = new ANT (parent, dc); } diff --git a/src/ANTlocalController.h b/src/ANTlocalController.h index b64bc40d5..5d3d1c097 100644 --- a/src/ANTlocalController.h +++ b/src/ANTlocalController.h @@ -32,7 +32,7 @@ class ANTlocalController : public RealtimeController { public: - ANTlocalController (RealtimeWindow *parent =0, DeviceConfiguration *dc =0); + ANTlocalController (TrainTool *parent =0, DeviceConfiguration *dc =0); ANT *myANTlocal; // the device itself diff --git a/src/ANTplusController.cpp b/src/ANTplusController.cpp index 5a050d47e..c257a0e3f 100644 --- a/src/ANTplusController.cpp +++ b/src/ANTplusController.cpp @@ -21,7 +21,7 @@ #include "QuarqdClient.h" #include "RealtimeData.h" -ANTplusController::ANTplusController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ANTplusController::ANTplusController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myANTplus = new QuarqdClient (parent, dc); } diff --git a/src/ANTplusController.h b/src/ANTplusController.h index 811063b37..7a7897b63 100644 --- a/src/ANTplusController.h +++ b/src/ANTplusController.h @@ -32,7 +32,7 @@ class ANTplusController : public RealtimeController { public: - ANTplusController (RealtimeWindow *parent =0, DeviceConfiguration *dc =0); + ANTplusController (TrainTool *parent =0, DeviceConfiguration *dc =0); QuarqdClient *myANTplus; // the device itself diff --git a/src/ComputrainerController.cpp b/src/ComputrainerController.cpp index 3c9393790..3362387e7 100644 --- a/src/ComputrainerController.cpp +++ b/src/ComputrainerController.cpp @@ -20,7 +20,7 @@ #include "Computrainer.h" #include "RealtimeData.h" -ComputrainerController::ComputrainerController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ComputrainerController::ComputrainerController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myComputrainer = new Computrainer (parent, dc->portSpec); } @@ -77,7 +77,6 @@ ComputrainerController::getRealtimeData(RealtimeData &rtData) if(!myComputrainer->isRunning()) { - parent->Stop(); QMessageBox msgBox; msgBox.setText("Cannot Connect to Computrainer"); msgBox.setIcon(QMessageBox::Critical); diff --git a/src/ComputrainerController.h b/src/ComputrainerController.h index 483b7e3b8..29829ae72 100644 --- a/src/ComputrainerController.h +++ b/src/ComputrainerController.h @@ -22,7 +22,6 @@ // Abstract base class for Realtime device controllers - #ifndef _GC_ComputrainerController_h #define _GC_ComputrainerController_h 1 @@ -30,7 +29,7 @@ class ComputrainerController : public RealtimeController { public: - ComputrainerController (RealtimeWindow *, DeviceConfiguration *); + ComputrainerController (TrainTool *, DeviceConfiguration *); Computrainer *myComputrainer; // the device itself diff --git a/src/ConfigDialog.cpp b/src/ConfigDialog.cpp index 0fbc1f892..64b370daf 100644 --- a/src/ConfigDialog.cpp +++ b/src/ConfigDialog.cpp @@ -4,7 +4,6 @@ #include "MainWindow.h" #include "ConfigDialog.h" -#include "RealtimeWindow.h" #include "Pages.h" #include "Settings.h" #include "Zones.h" diff --git a/src/ErgFilePlot.cpp b/src/ErgFilePlot.cpp index b7b7cbacf..a20b0c838 100644 --- a/src/ErgFilePlot.cpp +++ b/src/ErgFilePlot.cpp @@ -82,7 +82,7 @@ ErgFilePlot::setData(ErgFile *ergfile) // set up again ergFile = ergfile; - setTitle(ergFile->Name); + //setTitle(ergFile->Name); courseData = &ergfile->Points; MaxWatts = ergfile->MaxWatts; diff --git a/src/GcWindowRegistry.cpp b/src/GcWindowRegistry.cpp index 5c43a076b..4a3ce1e1c 100644 --- a/src/GcWindowRegistry.cpp +++ b/src/GcWindowRegistry.cpp @@ -39,13 +39,11 @@ #include "PfPvWindow.h" #include "HrPwWindow.h" #include "RaceWindow.h" // XXX not done -#include "RealtimeWindow.h" #include "RideEditor.h" #include "RideSummaryWindow.h" #include "ScatterWindow.h" #include "SummaryWindow.h" #include "MetadataWindow.h" -#include "TrainWindow.h" // XXX not done #include "TreeMapWindow.h" #include "WeeklySummaryWindow.h" #include "DialWindow.h" @@ -65,16 +63,15 @@ GcWindowRegistry GcWindows[] = { { "Performance Manager", GcWindowTypes::PerformanceManager }, { "PfPv", GcWindowTypes::PfPv }, { "HrPw", GcWindowTypes::HrPw }, - { "Training", GcWindowTypes::Training }, { "Ride Editor", GcWindowTypes::RideEditor }, { "Ride Summary", GcWindowTypes::RideSummary }, - { "Scatter", GcWindowTypes::Scatter }, + { "Metadata Fields", GcWindowTypes::MetadataWindow }, { "Ride Summary & Fields", GcWindowTypes::Summary }, + { "Scatter", GcWindowTypes::Scatter }, { "Treemap", GcWindowTypes::TreeMap }, { "Weekly Summary", GcWindowTypes::WeeklySummary }, { "Video Player", GcWindowTypes::VideoPlayer }, { "Realtime Dial", GcWindowTypes::DialWindow }, - { "Metadata Fields", GcWindowTypes::MetadataWindow }, { "Realtime Plot", GcWindowTypes::RealtimePlot }, { "Workout Plot", GcWindowTypes::WorkoutPlot }, { "", GcWindowTypes::None }}; @@ -105,7 +102,6 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil case GcWindowTypes::PerformanceManager: returning = new PerformanceManagerWindow(main); break; case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break; case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break; - case GcWindowTypes::Training: returning = new TrainWindow(main, main->home); break; case GcWindowTypes::RideEditor: returning = new RideEditor(main); break; case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break; case GcWindowTypes::Scatter: returning = new ScatterWindow(main, main->home); break; diff --git a/src/GcWindowRegistry.h b/src/GcWindowRegistry.h index 34536edba..f34c525bb 100644 --- a/src/GcWindowRegistry.h +++ b/src/GcWindowRegistry.h @@ -38,7 +38,6 @@ enum gcwinid { PerformanceManager =9, PfPv =10, Race =11, - Training =12, RideEditor =13, RideSummary =14, Scatter =15, diff --git a/src/HomeWindow.cpp b/src/HomeWindow.cpp index 05c750825..911ca1f02 100644 --- a/src/HomeWindow.cpp +++ b/src/HomeWindow.cpp @@ -20,7 +20,7 @@ #include "HomeWindow.h" #include "LTMSettings.h" -HomeWindow::HomeWindow(MainWindow *mainWindow, QString name) : +HomeWindow::HomeWindow(MainWindow *mainWindow, QString name, QString windowtitle) : GcWindow(mainWindow), mainWindow(mainWindow), name(name), active(false), clicked(NULL), dropPending(false), chartCursor(-2), loaded(false) { @@ -36,7 +36,7 @@ HomeWindow::HomeWindow(MainWindow *mainWindow, QString name) : bigandbold.setWeight(QFont::Bold); QHBoxLayout *titleBar = new QHBoxLayout; - title = new QLabel(" Home", this); + title = new QLabel(windowtitle, this); title->setFont(bigandbold); QPalette mypalette; mypalette.setColor(title->foregroundRole(), Qt::white); @@ -1105,6 +1105,12 @@ HomeWindow::restoreState() { // restore window state QString filename = mainWindow->home.absolutePath() + "/" + name + "-layout.xml"; + QFileInfo finfo(filename); + + // use a default if not there + if (!finfo.exists()) filename = QString(":xml/%1-layout.xml").arg(name); + + // now go read... QFile file(filename); // setup the handler diff --git a/src/HomeWindow.h b/src/HomeWindow.h index c8bd104e1..f3b2de311 100644 --- a/src/HomeWindow.h +++ b/src/HomeWindow.h @@ -39,7 +39,7 @@ class HomeWindow : public GcWindow public: - HomeWindow(MainWindow *, QString name); + HomeWindow(MainWindow *, QString name, QString title); ~HomeWindow(); //int view() const { return viewMode->currentIndex(); } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f7056f8fa..7a277379b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -43,7 +43,6 @@ #include "ManualRideDialog.h" #include "HistogramWindow.h" #include "ModelWindow.h" -#include "RealtimeWindow.h" #include "RideItem.h" #include "IntervalItem.h" #include "IntervalSummaryWindow.h" @@ -79,7 +78,6 @@ #include "MetricAggregator.h" #include "SplitRideDialog.h" #include "PerformanceManagerWindow.h" -#include "TrainWindow.h" #include "TreeMapWindow.h" #include "TwitterDialog.h" #include "WithingsDownload.h" @@ -193,9 +191,9 @@ MainWindow::MainWindow(const QDir &home) : toolbar->setFloatable(false); toolbar->setIconSize(QSize(32,32)); #ifndef Q_OS_MAC - //toolbar->setAutoFillBackground(false); //toolbar->setStyleSheet("background-image: url(\":/images/aluLight.jpg\"); border: 0px;"); toolbar->setContentsMargins(0,0,0,0); + toolbar->setAutoFillBackground(true); #else QIcon tickIcon(":images/toolbar/main/tick.png"); QPushButton *showControls = new QPushButton(tickIcon, "", this); @@ -382,12 +380,13 @@ MainWindow::MainWindow(const QDir &home) : homeControls->setContentsMargins(0,0,0,0); masterControls->addWidget(homeControls); - homeWindow = new HomeWindow(this, "home"); + homeWindow = new HomeWindow(this, "home", "Home"); homeControls->addWidget(homeWindow->controls()); homeControls->setCurrentIndex(0); + // setup trainWindow - trainWindow = new TrainWindow(this, home); - trainControls->addWidget(trainWindow->controls()); + trainWindow = new HomeWindow(this, "train", "Training"); + trainControls->addWidget(new TrainTool(this, this->home)); // ToolBox has one thing at the mo... will change soon toolBox = new QToolBox(this); @@ -754,18 +753,6 @@ MainWindow::tabViewTriggered(bool) appsettings->setValue(GC_TABS_TO_HIDE, tabsToHide.join(",")); } -void -MainWindow::selectView(int view) -{ - if (view == VIEW_ANALYSIS) - views->setCurrentIndex(0); // set stacked widget to Analysis - else if (view == VIEW_TRAIN) - views->setCurrentIndex(1); // set stacked widget to Train - - // notify with a signal - viewChanged(view); -} - void MainWindow::dragEnterEvent(QDragEnterEvent *event) { @@ -1509,6 +1496,7 @@ MainWindow::closeEvent(QCloseEvent* event) // save the state of all the pages homeWindow->saveState(); + trainWindow->saveState(); // clear the clipboard if neccessary QApplication::clipboard()->setText(""); @@ -1881,6 +1869,7 @@ MainWindow::selectTrain() { masterControls->setCurrentIndex(1); views->setCurrentIndex(1); + trainWindow->selected(); // tell it! } void diff --git a/src/MainWindow.h b/src/MainWindow.h index 682a1588f..4f986a1e1 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -152,7 +152,6 @@ class MainWindow : public QMainWindow void zonesChanged(); void seasonsChanged(); void configChanged(); - void viewChanged(int); void rideAdded(RideItem *); void rideDeleted(RideItem *); void rideDirty(); @@ -261,7 +260,6 @@ class MainWindow : public QMainWindow IntervalItem *activeInterval; // currently active for context menu popup RideItem *activeRide; // currently active for context menu popup - ViewSelection *viewSelection; QStackedWidget *views; // each view has its own controls XXX more to come @@ -286,7 +284,7 @@ class MainWindow : public QMainWindow SummaryWindow *summaryWindow; DiaryWindow *diaryWindow; HomeWindow *homeWindow; - TrainWindow *trainWindow; + HomeWindow *trainWindow; AllPlotWindow *allPlotWindow; HistogramWindow *histogramWindow; WeeklySummaryWindow *weeklySummaryWindow; diff --git a/src/NullController.cpp b/src/NullController.cpp index c4e44e70d..2582391da 100644 --- a/src/NullController.cpp +++ b/src/NullController.cpp @@ -23,7 +23,7 @@ #include "RaceDispatcher.h" #include "RealtimeData.h" -NullController::NullController(RealtimeWindow *parent, +NullController::NullController(TrainTool *parent, DeviceConfiguration *) : RealtimeController(parent), parent(parent), load(100) { diff --git a/src/NullController.h b/src/NullController.h index e8872ae08..659b0f267 100644 --- a/src/NullController.h +++ b/src/NullController.h @@ -34,11 +34,11 @@ class NullController : public RealtimeController { public: - RealtimeWindow *parent; + TrainTool *parent; // hostname and port are the hostname/port of the server to which // this NullControlller should connect. - NullController(RealtimeWindow *parent, + NullController(TrainTool *parent, DeviceConfiguration *dc); ~NullController() { } diff --git a/src/RealtimeController.cpp b/src/RealtimeController.cpp index 58f212103..804efe6f6 100644 --- a/src/RealtimeController.cpp +++ b/src/RealtimeController.cpp @@ -17,13 +17,13 @@ */ #include "RealtimeController.h" -#include "RealtimeWindow.h" +#include "TrainTool.h" #include "RealtimeData.h" #include "Units.h" // Abstract base class for Realtime device controllers -RealtimeController::RealtimeController(RealtimeWindow *parent, DeviceConfiguration *dc) : parent(parent), dc(dc) +RealtimeController::RealtimeController(TrainTool *parent, DeviceConfiguration *dc) : parent(parent), dc(dc) { if (dc != NULL) { diff --git a/src/RealtimeController.h b/src/RealtimeController.h index 00a8d5b9b..fe7657ae6 100644 --- a/src/RealtimeController.h +++ b/src/RealtimeController.h @@ -19,7 +19,7 @@ // Abstract base class for Realtime device controllers #include "RealtimeData.h" -#include "RealtimeWindow.h" +#include "TrainTool.h" #ifndef _GC_RealtimeController_h #define _GC_RealtimeController_h 1 @@ -31,9 +31,9 @@ class RealtimeController { public: - RealtimeWindow *parent; // for push devices + TrainTool *parent; // for push devices - RealtimeController (RealtimeWindow *parent, DeviceConfiguration *dc = 0); + RealtimeController (TrainTool *parent, DeviceConfiguration *dc = 0); virtual ~RealtimeController() {} virtual int start(); diff --git a/src/RealtimeWindow.cpp b/src/RealtimeWindow.cpp deleted file mode 100644 index 982c5df7b..000000000 --- a/src/RealtimeWindow.cpp +++ /dev/null @@ -1,971 +0,0 @@ -/* - * 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 -#include - -// user selections for device/server/workout here -#include "TrainTool.h" - -// Three current realtime device types supported are: -#include "RealtimeController.h" -#include "ComputrainerController.h" -#include "ANTplusController.h" -#include "ANTlocalController.h" -#include "NullController.h" -#include "ErgFile.h" - -// And connect to server is via a GoldenServer object -#include "GoldenClient.h" - - -void -RealtimeWindow::configUpdate() -{ - - FTP=285; // default to 285 if zones are not set - int range = main->zones()->whichRange(QDate::currentDate()); - if (range != -1) FTP = main->zones()->getCP(range); - - // metric or imperial changed? - QVariant unit = appsettings->value(this, 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")); - - // devices - DeviceConfigurations all; - Devices.clear(); - Devices = all.getList(); -} - -RealtimeWindow::RealtimeWindow(MainWindow *parent, TrainTool *trainTool, const QDir &home) : GcWindow(parent) -{ - - setInstanceName("Solo Ride Window"); - setControls(NULL); - - // set home - this->home = home; - this->trainTool = trainTool; - main = parent; - deviceController = NULL; - streamController = NULL; - ergFile = NULL; - - // metric or imperial? - QVariant unit = appsettings->value(this, 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(); - - // handle config changes - connect(main, SIGNAL(configChanged()), this, SLOT(configUpdate())); - - connect(trainTool, SIGNAL(workoutSelected()), this, SLOT(SelectWorkout())); - - // connect train tool buttons! - connect(trainTool, SIGNAL(start()), this, SLOT(Start())); - connect(trainTool, SIGNAL(pause()), this, SLOT(Pause())); - connect(trainTool, SIGNAL(stop()), this, SLOT(Stop())); - - 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); - kjouleLabel = new QLabel(tr("kJoules"),this); - kjouleLabel->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); - xpowerLabel = new QLabel(tr("xPower"), this); - xpowerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - bikescoreLabel = new QLabel(tr("BikeScore"),this); - bikescoreLabel->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); - distanceLCD->setNumDigits(9); // just to be same size as timers! - kjouleLCD = new QLCDNumber(this); distanceLCD->setSegmentStyle(QLCDNumber::Filled); - bikescoreLCD = 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); - xpowerLCD = new QLCDNumber(this); xpowerLCD->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); - - // 4 x 6 grid. - int row = 0; - int colm = 0; - gridLayout = new QGridLayout(); - - gridLayout->addWidget(powerLabel, row, colm); - gridLayout->addWidget(powerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(cadenceLabel, row, colm); - gridLayout->addWidget(cadenceLCD, row+1, colm); - - colm++; - gridLayout->addWidget(heartrateLabel, row, colm); - gridLayout->addWidget(heartrateLCD, row+1, colm); - - colm++; - gridLayout->addWidget(speedLabel, row, colm); - gridLayout->addWidget(speedLCD, row+1, colm); - - colm++; - gridLayout->addWidget(lapLabel, row, colm); - gridLayout->addWidget(lapLCD, row+1, colm); - - colm++; - gridLayout->addWidget(kjouleLabel,row,colm); - gridLayout->addWidget(kjouleLCD, row +1,colm); - - // end of first row of data - colm = 0; - row += 2; - gridLayout->addWidget(avgpowerLabel, row, colm); - gridLayout->addWidget(avgpowerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgcadenceLabel, row, colm); - gridLayout->addWidget(avgcadenceLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgheartrateLabel, row, colm); - gridLayout->addWidget(avgheartrateLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgspeedLabel, row, colm); - gridLayout->addWidget(avgspeedLCD, row+1, colm); - - colm++; - gridLayout->addWidget(xpowerLabel, row, colm); - gridLayout->addWidget(xpowerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(bikescoreLabel, row, colm); - gridLayout->addWidget(bikescoreLCD, row+1, colm); - - colm++; - gridLayout->addWidget(loadLabel, row, colm); - gridLayout->addWidget(loadLCD, row+1, colm); - - gridLayout->setRowStretch(1, 3); - gridLayout->setRowStretch(3, 3); - - // timers etc - timer_layout->addWidget(timeLabel, 0, 0); - timer_layout->addWidget(distanceLabel, 0, 1); - timer_layout->addWidget(laptimeLabel, 0, 2); - timer_layout->addWidget(timeLCD, 1, 0); - timer_layout->addWidget(distanceLCD, 1, 1); - timer_layout->addWidget(laptimeLCD, 1, 2); - timer_layout->setRowStretch(0, 1); - timer_layout->setRowStretch(1, 4); - - // REALTIME PLOT - rtPlot = new RealtimePlot(); - connect(main, SIGNAL(configChanged()), rtPlot, SLOT(configChanged())); - - // 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); - metrics_timer = new QTimer(this); - - session_time = QTime(); - session_elapsed_msec = 0; - lap_time = QTime(); - lap_elapsed_msec = 0; - - recordFile = NULL; - status = 0; - 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; - - rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); - - 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())); - connect(metrics_timer, SIGNAL(timeout()), this, SLOT(metricsUpdate())); - - - configUpdate(); // apply current config - - rtPlot->replot(); -} - -void RealtimeWindow::setDeviceController() -{ - int deviceno = trainTool->selectedDeviceNumber(); - - if (deviceno == -1) // not selected, maybe they are spectating - return; - - // 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_NULL) { - deviceController = new NullController(this, &temp); - } else if (Devices.at(deviceno).type == DEV_ANTLOCAL) { - deviceController = new ANTlocalController(this, &temp); - } - } - -} - -// open a connection to the GoldenServer via a GoldenClient -void RealtimeWindow::setStreamController() -{ - int deviceno = trainTool->selectedServerNumber(); - - if (deviceno == -1) return; - - // zap the current one - if (streamController != NULL) { - delete streamController; - streamController = NULL; - } - - if (Devices.count() > 0) { - DeviceConfiguration config = Devices.at(deviceno); - streamController = new GoldenClient; - - // connect - QStringList speclist = config.portSpec.split(":", QString::SkipEmptyParts); - bool rc = streamController->connect(speclist[0], // host - speclist[1].toInt(), // port - "9cf638294030cea7b1590a4ca32e7f58", // raceid - appsettings->cvalue(main->cyclist, GC_NICKNAME).toString(), // name - FTP, // CP60 - appsettings->cvalue(main->cyclist, GC_WEIGHT).toDouble()); // weight - - // no connection - if (rc == false) { - streamController->closeAndExit(); - streamController = NULL; - status &= ~RT_STREAMING; - QMessageBox msgBox; - msgBox.setText(QString(tr("Cannot Connect to Server %1 on port %2").arg(speclist[0]).arg(speclist[1]))); - msgBox.setIcon(QMessageBox::Critical); - msgBox.exec(); - } - } -} - -void RealtimeWindow::Start() // when start button is pressed -{ - - if (status&RT_RUNNING) { - newLap(); - } else { - - // open the controller if it is selected - setDeviceController(); - if (deviceController == NULL) return; - else deviceController->start(); // start device - - // we're away! - status |=RT_RUNNING; - - // should we be streaming too? - setStreamController(); - if (streamController != NULL) status |= RT_STREAMING; - - trainTool->setStartText(tr("Lap/Interval")); - - load_period.restart(); - session_time.start(); - session_elapsed_msec = 0; - lap_time.start(); - lap_elapsed_msec = 0; - - if (status & RT_WORKOUT) { - load_timer->start(LOADRATE); // start recording - } - - if (trainTool->recordSelector->isChecked()) { - status |= RT_RECORDING; - } - - if (status & RT_RECORDING) { - QDateTime now = QDateTime::currentDateTime(); - - // setup file - QString filename = now.toString(QString("yyyy_MM_dd_hh_mm_ss")) + QString(".csv"); - - 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 - } - } - - // create a new rideFile - rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); - - - // stream - if (status & RT_STREAMING) { - stream_timer->start(STREAMRATE); - } - - gui_timer->start(REFRESHRATE); // start recording - metrics_timer->start(METRICSRATE); - - } -} - -void RealtimeWindow::Pause() // pause capture to recalibrate -{ - if (deviceController == NULL) return; - - // we're not running fool! - if ((status&RT_RUNNING) == 0) return; - - if (status&RT_PAUSED) { - session_time.start(); - lap_time.start(); - status &=~RT_PAUSED; - deviceController->restart(); - trainTool->setPauseText(tr("Pause")); - gui_timer->start(REFRESHRATE); - metrics_timer->start(METRICSRATE); - if (status & RT_STREAMING) stream_timer->start(STREAMRATE); - if (status & RT_RECORDING) disk_timer->start(SAMPLERATE); - load_period.restart(); - if (status & RT_WORKOUT) load_timer->start(LOADRATE); - } else { - session_elapsed_msec += session_time.elapsed(); - lap_elapsed_msec += lap_time.elapsed(); - deviceController->pause(); - trainTool->setPauseText(tr("Un-Pause")); - status |=RT_PAUSED; - gui_timer->stop(); - metrics_timer->stop(); - if (status & RT_STREAMING) stream_timer->stop(); - if (status & RT_RECORDING) disk_timer->stop(); - if (status & RT_WORKOUT) load_timer->stop(); - load_msecs += load_period.restart(); - } -} - -void RealtimeWindow::Stop(int deviceStatus) // when stop button is pressed -{ - if (deviceController == NULL) return; - - if ((status&RT_RUNNING) == 0) return; - - status &= ~RT_RUNNING; - trainTool->setStartText(tr("Start")); - - // wipe connection - deviceController->stop(); - delete deviceController; - deviceController = NULL; - - gui_timer->stop(); - metrics_timer->stop(); - - QDateTime now = QDateTime::currentDateTime(); - - if (status & RT_RECORDING) { - disk_timer->stop(); - - // close and reset File - recordFile->close(); - - if(deviceStatus == DEVICE_ERROR) - { - recordFile->remove(); - } - else { - // 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->closeAndExit(); - delete streamController; - streamController = NULL; - } - - if (status & RT_WORKOUT) { - load_timer->stop(); - load_msecs = 0; - main->notifySetNow(load_msecs); - ergPlot->setNow(load_msecs); - ergPlot->replot(); - } - - // Re-enable gui elements - //recordSelector->setEnabled(true); - - // reset counters etc - pwrcount = 0; - cadcount = 0; - hrcount = 0; - spdcount = 0; - lodcount = 0; - displayWorkoutLap = displayLap =0; - session_elapsed_msec = 0; - session_time.restart(); - lap_elapsed_msec = 0; - lap_time.restart(); - 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.getCadence(); - displayHeartRate = rtData.getHr(); - displaySpeed = rtData.getSpeed(); - displayLoad = rtData.getLoad(); - // Gradient not supported - return; -} - -//---------------------------------------------------------------------- -// SCREEN UPDATE FUNCTIONS -//---------------------------------------------------------------------- - -void RealtimeWindow::guiUpdate() // refreshes the telemetry -{ - RealtimeData rtData; - - if (deviceController == NULL) return; - - // get latest telemetry from device (if it is a pull device e.g. Computrainer // - if (status&RT_RUNNING && deviceController->doesPull() == true) { - - deviceController->getRealtimeData(rtData); - - // 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 - rtData.setDistance(displayDistance); - - // time - total_msecs = session_elapsed_msec + session_time.elapsed(); - lap_msecs = lap_elapsed_msec + lap_time.elapsed(); - rtData.setMsecs(total_msecs); - rtData.setLapMsecs(lap_msecs); - - // metrics - rtData.setJoules(kjoules); - rtData.setBikeScore(bikescore); - rtData.setXPower(xpower); - - // 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)); - - // local stuff ... - displayPower = rtData.getWatts(); - displayCadence = rtData.getCadence(); - displayHeartRate = rtData.getHr(); - displaySpeed = rtData.getSpeed(); - displayLoad = rtData.getLoad(); - - // go update the displays... - main->notifyTelemetryUpdate(rtData); // signal everyone to update telemetry - } - - // Cadence, HR and Power needs to be rounded to 0 decimal places - powerLCD->display(round(displayPower)); - double val = round(displaySpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00; - speedLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - 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 - { - val = round(displayGradient*10)/10.00; - loadLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - } - - // distance - val = round(displayDistance*(useMetricUnits ? 1.0 : MILES_PER_KM) *10.00) /10.00; - distanceLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - - // NZ Averages..... - if (displayPower) { //NZAP is bogus - make it configurable!!! - pwrcount++; if (pwrcount ==1) avgPower = displayPower; - avgPower = ((avgPower * (double)pwrcount) + displayPower) /(double) (pwrcount+1); - kjoules = avgPower * total_msecs /(1000*1000); - } - 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 0 - if (displayLoad && status&RT_MODE_ERGO) { - lodcount++; if (lodcount ==1) avgLoad = displayLoad; - avgLoad = ((avgLoad * (double)lodcount) + displayLoad) /(double) (lodcount+1); - //avgloadLCD->display((int)avgLoad); - } -#endif - 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); - val = round(avgSpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00; - avgspeedLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - avgcadenceLCD->display((int)avgCadence); - avgheartrateLCD->display((int)avgHeartRate); - kjouleLCD->display(round(kjoules)); - bikescoreLCD->display(round(bikescore)); - xpowerLCD->display(round(xpower)); - - // 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(); - -} - -// can be called from the controller - when user presses "Lap" button -void RealtimeWindow::newLap() -{ - displayLap++; - - pwrcount = 0; - cadcount = 0; - hrcount = 0; - spdcount = 0; - - lap_time.restart(); - lap_elapsed_msec = 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 -//---------------------------------------------------------------------- -#if 0 -RealtimeWindow::SelectStream(int index) -{ - - if (index > 0) { - status |= RT_STREAMING; - setStreamController(); - } else { - status &= ~RT_STREAMING; - } -} -#endif - -void -RealtimeWindow::streamUpdate() -{ - // send over the wire... - if (streamController) { - - // send my data - streamController->sendTelemetry(displayPower, - displayCadence, - displayDistance, - displayHeartRate, - displaySpeed); - - // get standings for everyone else - RaceStatus current = streamController->getStandings(); - - // send out to all the widgets... - trainTool->notifyRaceStandings(current); - - // has the race finished? - if (current.race_finished == true) { - Stop(); // all over dude - QMessageBox msgBox; - msgBox.setText(tr("Race Over!")); - msgBox.setIcon(QMessageBox::Information); - msgBox.exec(); - } - } -} - -//---------------------------------------------------------------------- -// DISK UPDATE FUNCTIONS -//---------------------------------------------------------------------- -void RealtimeWindow::diskUpdate() -{ - double Minutes; - - long Torq = 0, Altitude = 0; - QTextStream recordFileStream(recordFile); - - // convert from milliseconds to minutes - total_msecs = session_elapsed_msec + session_time.elapsed(); - 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"; - - rideFile->appendPoint(total_msecs/1000,displayCadence,displayHeartRate,displayDistance,displaySpeed,0, - displayPower,Altitude,0,0,0,displayLap + displayWorkoutLap); -} - -void RealtimeWindow::metricsUpdate() -{ - // calculate bike score, xpower - const RideMetricFactory &factory = RideMetricFactory::instance(); - const RideMetric *rm = factory.rideMetric("skiba_xpower"); - - QStringList metrics; - metrics.append("skiba_bike_score"); - metrics.append("skiba_xpower"); - QHash results = rm->computeMetrics( - this->main,&*rideFile,this->main->zones(),this->main->hrZones(),metrics); - bikescore = results["skiba_bike_score"]->value(true); - xpower = results["skiba_xpower"]->value(true); -} - -//---------------------------------------------------------------------- -// 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 - QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); - QString fileName = workoutDir.toString() + "/" + trainTool->currentWorkout()->text(0); // filename - - // Get users CP for relative watts calculations - QDate today = QDate::currentDate(); - - ergFile = new ErgFile(fileName, mode, FTP); - if (ergFile->isValid()) { - - status |= RT_WORKOUT; - - // success! we have a load file - // setup the course profile in the - // display! - main->notifyErgFileSelected(ergFile); - ergPlot->setData(ergFile); - ergPlot->setVisible(true); - ergPlot->replot(); - } - } - - // set the device to the right mode - if (mode == ERG || mode == MRC) { - 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"); - } 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"); - } -} - -void RealtimeWindow::loadUpdate() -{ - long load; - double gradient; - // the period between loadUpdate calls is not constant, and not exactly LOADRATE, - // therefore, use a QTime timer to measure the load period - load_msecs += load_period.restart(); - - if (deviceController == NULL) return; - - if (status&RT_MODE_ERGO) { - load = ergFile->wattsAt(load_msecs, displayWorkoutLap); - - // we got to the end! - if (load == -100) { - Stop(DEVICE_OK); - } else { - displayLoad = load; - deviceController->setLoad(displayLoad); - main->notifySetNow(load_msecs); - ergPlot->setNow(load_msecs); - } - } else { - gradient = ergFile->gradientAt(displayWorkoutDistance*1000, displayWorkoutLap); - - // we got to the end! - if (gradient == -100) { - Stop(DEVICE_OK); - } else { - displayGradient = gradient; - deviceController->setGradient(displayGradient); - main->notifySetNow(displayWorkoutDistance * 1000); - 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 (deviceController == NULL) return; - - 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 (deviceController == NULL) return; - - 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); -} diff --git a/src/RealtimeWindow.h b/src/RealtimeWindow.h deleted file mode 100644 index a22960cc1..000000000 --- a/src/RealtimeWindow.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net) - * - * 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 - */ - -#ifndef _GC_RealtimeWindow_h -#define _GC_RealtimeWindow_h 1 -#include "GoldenCheetah.h" - -#include -#include -#include "MainWindow.h" -#include "DeviceConfiguration.h" -#include "DeviceTypes.h" -#include "ErgFile.h" -#include "ErgFilePlot.h" -#include "GoldenClient.h" - -class TrainTool; -class TrainTabs; - -// Status settings -#define RT_MODE_ERGO 0x0001 // load generation modes -#define RT_MODE_SPIN 0x0002 // spinscan like modes -#define RT_RUNNING 0x0100 // is running now -#define RT_PAUSED 0x0200 // is paused -#define RT_RECORDING 0x0400 // is recording to disk -#define RT_WORKOUT 0x0800 // is running a workout -#define RT_STREAMING 0x1000 // is streaming to a remote peer - -#define REFRESHRATE 200 // screen refresh in milliseconds -#define STREAMRATE 200 // rate at which we stream updates to remote peer -#define SAMPLERATE 1000 // disk update in milliseconds -#define LOADRATE 1000 // rate at which load is adjusted -#define METRICSRATE 1000 // rate the metrics are updated - - -class RealtimeController; -class RealtimePlot; -class RealtimeData; - -class RealtimeWindow : public GcWindow -{ - Q_OBJECT - G_OBJECT - - - public: - - RealtimeController *deviceController; // read from - GoldenClient *streamController; // send out to - - RealtimeWindow(MainWindow *, TrainTool *, const QDir &); - - void updateData(RealtimeData &); // to update telemetry by push devices - void newLap(); // start new Lap! - void nextDisplayMode(); // show next display mode - void setDeviceController(); // based upon selected device - void setStreamController(); // based upon selected device - - public slots: - - void Start(); // when start button is pressed - void Pause(); // when Paude is pressed - void Stop(int status=0); // when stop button is pressed - - void FFwd(); // jump forward when in a workout - void Rewind(); // jump backwards when in a workout - void FFwdLap(); // jump forward to next Lap marker - void Higher(); // set load/gradient higher - void Lower(); // set load/gradient higher - - void SelectWorkout(); - - // Timed actions - void guiUpdate(); // refreshes the telemetry - void diskUpdate(); // writes to CSV file - void streamUpdate(); // writes to remote Peer - void loadUpdate(); // sets Load on CT like devices - void metricsUpdate(); // calculates the metrics - - // Handle config updates - void configUpdate(); // called when config changes - - // When no config has been setup - void warnnoConfig(); - - protected: - - - // passed from MainWindow - QDir home; - MainWindow *main; - TrainTool *trainTool; - int FTP; // current FTP - - QList Devices; - bool useMetricUnits; - - // updated with a RealtimeData object either from - // update() - from a push device (quarqd ANT+) - // Device->getRealtimeData() - from a pull device (Computrainer) - double displayPower, displayHeartRate, displayCadence, - displayLoad, displayGradient, displaySpeed; - double displayDistance, displayWorkoutDistance; - int displayLap; // user increment for Lap - int displayWorkoutLap; // which Lap in the workout are we at? - double kjoules; - double bikescore; - double xpower; - - // for non-zero average calcs - int pwrcount, cadcount, hrcount, spdcount, lodcount, grdcount; // for NZ average calc - int status; - int displaymode; - - QFile *recordFile; // where we record! - ErgFile *ergFile; // workout file - boost::shared_ptr rideFile; // keeps track of the workout to figure out BikeScore - - long total_msecs, - lap_msecs, - load_msecs; - QTime load_period; - - uint session_elapsed_msec, lap_elapsed_msec; - QTime session_time, lap_time; - - // GUI WIDGETS - // layout - RealtimePlot *rtPlot; - ErgFilePlot *ergPlot; - - QVBoxLayout *main_layout; - - // labels - QLabel *powerLabel, - *heartrateLabel, - *speedLabel, - *cadenceLabel, - *loadLabel, - *lapLabel, - *laptimeLabel, - *timeLabel, - *distanceLabel, - *kjouleLabel, - *bikescoreLabel, - *xpowerLabel; - - double avgPower, avgHeartRate, avgSpeed, avgCadence, avgLoad, avgGradient; - QLabel *avgpowerLabel, - *avgheartrateLabel, - *avgspeedLabel, - *avgcadenceLabel; - - QHBoxLayout *button_layout, - *option_layout; - QGridLayout *timer_layout; - QVBoxLayout *controls_layout; - - QGridLayout *gridLayout; - - // the LCDs - QLCDNumber *powerLCD, - *heartrateLCD, - *speedLCD, - *cadenceLCD, - *loadLCD, - *lapLCD, - *laptimeLCD, - *timeLCD, - *distanceLCD, - *kjouleLCD, - *bikescoreLCD, - *xpowerLCD; - - QLCDNumber *avgpowerLCD, - *avgheartrateLCD, - *avgspeedLCD, - *avgcadenceLCD; - - QTimer *gui_timer, // refresh the gui - *stream_timer, // send telemetry to server - *load_timer, // change the load on the device - *disk_timer, // write to .CSV file - *metrics_timer; // computational intense metrics - -}; - -#endif // _GC_RealtimeWindow_h - diff --git a/src/SimpleNetworkController.cpp b/src/SimpleNetworkController.cpp index 32173088e..d644142c9 100644 --- a/src/SimpleNetworkController.cpp +++ b/src/SimpleNetworkController.cpp @@ -22,7 +22,7 @@ #include "SimpleNetworkController.h" #include "RealtimeData.h" -SimpleNetworkController::SimpleNetworkController(RealtimeWindow *parent, +SimpleNetworkController::SimpleNetworkController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent), parent(parent), client(parent, dc), state(DISCONNECTED) { diff --git a/src/SimpleNetworkController.h b/src/SimpleNetworkController.h index 45bb078ee..307dcadf5 100644 --- a/src/SimpleNetworkController.h +++ b/src/SimpleNetworkController.h @@ -44,11 +44,11 @@ class SimpleNetworkController : public RealtimeController { public: - RealtimeWindow *parent; + TrainTool *parent; // hostname and port are the hostname/port of the server to which // this SimpleNetworkControlller should connect. - SimpleNetworkController(RealtimeWindow *parent, + SimpleNetworkController(TrainTool *parent, DeviceConfiguration *dc); ~SimpleNetworkController() { } diff --git a/src/TrainTabs.cpp b/src/TrainTabs.cpp deleted file mode 100644 index 75c815cba..000000000 --- a/src/TrainTabs.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 "TrainTabs.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" - -TrainTabs::TrainTabs(MainWindow *parent, TrainTool *trainTool, const QDir &home) : -trainTool(trainTool), home(home), main(parent) -{ - setContentsMargins(0,0,0,0); - realtimeWindow = new RealtimeWindow(parent, trainTool, home); - addTab(realtimeWindow, tr("Solo Ride")); -}; diff --git a/src/TrainTabs.h b/src/TrainTabs.h deleted file mode 100644 index 52ad4950a..000000000 --- a/src/TrainTabs.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2000 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 - */ - -#ifndef _GC_TrainTabs_h -#define _GC_TrainTabs_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" -#include "RaceWindow.h" -#include "MultiWindow.h" - -#ifdef GC_HAVE_PHONON -#include "VideoWindow.h" -#endif - -class TrainTabs : public QTabWidget -{ - Q_OBJECT - G_OBJECT - - - public: - TrainTabs(MainWindow *parent, TrainTool *trainTool, const QDir &home); - - private: - RealtimeWindow *realtimeWindow; - - TrainTool *trainTool; - const QDir home; - const MainWindow *main; -}; - -#endif // _GC_TrainTabs_h diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index f5c559306..b581f4a97 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -18,7 +18,6 @@ #include "TrainTool.h" #include "MainWindow.h" -#include "ViewSelection.h" #include "Settings.h" #include "Units.h" #include "DeviceTypes.h" @@ -28,6 +27,13 @@ #include #include +// Three current realtime device types supported are: +#include "RealtimeController.h" +#include "ComputrainerController.h" +#include "ANTplusController.h" +#include "ANTlocalController.h" +#include "NullController.h" + TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), home(home), main(parent) { @@ -73,8 +79,6 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho allWorkouts->setText(0, tr("Workout Library")); workoutTree->expandItem(allWorkouts); - configChanged(); // will reset the workout tree - buttonPanel = new QFrame; buttonPanel->setLineWidth(1); buttonPanel->setFrameStyle(QFrame::Box | QFrame::Raised); @@ -114,14 +118,11 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho trainSplitter->addWidget(workoutTree); trainSplitter->setCollapsible(3, true); - connect(serverTree,SIGNAL(itemSelectionChanged()), - this, SLOT(serverTreeWidgetSelectionChanged())); - connect(deviceTree,SIGNAL(itemSelectionChanged()), - this, SLOT(deviceTreeWidgetSelectionChanged())); - connect(workoutTree,SIGNAL(itemSelectionChanged()), - this, SLOT(workoutTreeWidgetSelectionChanged())); - connect(main, SIGNAL(configChanged()), - this, SLOT(configChanged())); + // handle config changes + connect(serverTree,SIGNAL(itemSelectionChanged()), this, SLOT(serverTreeWidgetSelectionChanged())); + connect(deviceTree,SIGNAL(itemSelectionChanged()), this, SLOT(deviceTreeWidgetSelectionChanged())); + connect(workoutTree,SIGNAL(itemSelectionChanged()), this, SLOT(workoutTreeWidgetSelectionChanged())); + connect(main, SIGNAL(configChanged()), this, SLOT(configChanged())); // connect train tool buttons! connect(startButton, SIGNAL(clicked()), this, SLOT(Start())); @@ -135,6 +136,52 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho connect(&*watcher,SIGNAL(directoryChanged(QString)),this,SLOT(configChanged())); connect(&*watcher,SIGNAL(fileChanged(QString)),this,SLOT(configChanged())); + + // set home + main = parent; + deviceController = NULL; + streamController = NULL; + ergFile = NULL; + + // metric or imperial? + QVariant unit = appsettings->value(this, GC_UNIT); + useMetricUnits = (unit.toString() == "Metric"); + + // 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); + metrics_timer = new QTimer(this); + + session_time = QTime(); + session_elapsed_msec = 0; + lap_time = QTime(); + lap_elapsed_msec = 0; + + recordFile = NULL; + status = 0; + 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; + + rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); + + 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())); + connect(metrics_timer, SIGNAL(timeout()), this, SLOT(metricsUpdate())); + + configChanged(); // will reset the workout tree + } void @@ -149,7 +196,7 @@ TrainTool::configChanged() for (int i=0; i Devices; + Devices.clear(); Devices = all.getList(); for (int i=0; isetText(0, name); } + + // Athlete + FTP=285; // default to 285 if zones are not set + int range = main->zones()->whichRange(QDate::currentDate()); + if (range != -1) FTP = main->zones()->getCP(range); + + // metric or imperial changed? + QVariant unit = appsettings->value(this, GC_UNIT); + useMetricUnits = (unit.toString() == "Metric"); } /*---------------------------------------------------------------------- * Buttons! *----------------------------------------------------------------------*/ -void -TrainTool::Start() -{ - start(); -} - -void -TrainTool::Stop() -{ - stop(); -} - -void -TrainTool::Pause() -{ - pause(); -} void TrainTool::setStartText(QString string) @@ -270,7 +309,60 @@ TrainTool::workoutTreeWidgetSelectionChanged() else workout = which; } - workoutSelected(); + + int mode; + + // wip away the current selected workout + if (ergFile) { + delete ergFile; + ergFile = NULL; + } + + // which one is selected? + if (currentWorkout() == NULL || currentWorkout()->type() != WORKOUT_TYPE) return; + + // is it the auto mode? + int index = workoutItems()->indexOfChild((QTreeWidgetItem *)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 + QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); + QString fileName = workoutDir.toString() + "/" + currentWorkout()->text(0); // filename + + // Get users CP for relative watts calculations + QDate today = QDate::currentDate(); + + ergFile = new ErgFile(fileName, mode, FTP); + if (ergFile->isValid()) { + + status |= RT_WORKOUT; + + // success! we have a load file + // setup the course profile in the + // display! + main->notifyErgFileSelected(ergFile); + } + } + + // set the device to the right mode + if (mode == ERG || mode == MRC) { + status |= RT_MODE_ERGO; + status &= ~RT_MODE_SPIN; + if (deviceController != NULL) deviceController->setMode(RT_MODE_ERGO); + } else { // SLOPE MODE + status |= RT_MODE_SPIN; + status &= ~RT_MODE_ERGO; + if (deviceController != NULL) deviceController->setMode(RT_MODE_SPIN); + } } QStringList @@ -283,3 +375,522 @@ TrainTool::listWorkoutFiles(const QDir &dir) const return dir.entryList(filters, QDir::Files, QDir::Name); } + +/*-------------------------------------------------------------------------------- + * Was realtime window, now local and manages controller and chart updates etc + *------------------------------------------------------------------------------*/ + +void TrainTool::setDeviceController() +{ + int deviceno = selectedDeviceNumber(); + + if (deviceno == -1) // not selected, maybe they are spectating + return; + + // 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_NULL) { + deviceController = new NullController(this, &temp); + } else if (Devices.at(deviceno).type == DEV_ANTLOCAL) { + deviceController = new ANTlocalController(this, &temp); + } + } +} + +// open a connection to the GoldenServer via a GoldenClient +void TrainTool::setStreamController() +{ + int deviceno = selectedServerNumber(); + + if (deviceno == -1) return; + + // zap the current one + if (streamController != NULL) { + delete streamController; + streamController = NULL; + } + + if (Devices.count() > 0) { + DeviceConfiguration config = Devices.at(deviceno); + streamController = new GoldenClient; + + // connect + QStringList speclist = config.portSpec.split(":", QString::SkipEmptyParts); + bool rc = streamController->connect(speclist[0], // host + speclist[1].toInt(), // port + "9cf638294030cea7b1590a4ca32e7f58", // raceid + appsettings->cvalue(main->cyclist, GC_NICKNAME).toString(), // name + FTP, // CP60 + appsettings->cvalue(main->cyclist, GC_WEIGHT).toDouble()); // weight + + // no connection + if (rc == false) { + streamController->closeAndExit(); + streamController = NULL; + status &= ~RT_STREAMING; + QMessageBox msgBox; + msgBox.setText(QString(tr("Cannot Connect to Server %1 on port %2").arg(speclist[0]).arg(speclist[1]))); + msgBox.setIcon(QMessageBox::Critical); + msgBox.exec(); + } + } +} + +void TrainTool::Start() // when start button is pressed +{ + if (status&RT_RUNNING) { + newLap(); + } else { + + // open the controller if it is selected + setDeviceController(); + if (deviceController == NULL) return; + else deviceController->start(); // start device + + // we're away! + status |=RT_RUNNING; + + // should we be streaming too? + setStreamController(); + if (streamController != NULL) status |= RT_STREAMING; + + setStartText(tr("Lap/Interval")); + + load_period.restart(); + session_time.start(); + session_elapsed_msec = 0; + lap_time.start(); + lap_elapsed_msec = 0; + + if (status & RT_WORKOUT) { + load_timer->start(LOADRATE); // start recording + } + + if (recordSelector->isChecked()) { + status |= RT_RECORDING; + } + + if (status & RT_RECORDING) { + QDateTime now = QDateTime::currentDateTime(); + + // setup file + QString filename = now.toString(QString("yyyy_MM_dd_hh_mm_ss")) + QString(".csv"); + + 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 + } + } + + // create a new rideFile + rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); + + + // stream + if (status & RT_STREAMING) { + stream_timer->start(STREAMRATE); + } + + gui_timer->start(REFRESHRATE); // start recording + metrics_timer->start(METRICSRATE); + + } +} + +void TrainTool::Pause() // pause capture to recalibrate +{ + if (deviceController == NULL) return; + + // we're not running fool! + if ((status&RT_RUNNING) == 0) return; + + if (status&RT_PAUSED) { + session_time.start(); + lap_time.start(); + status &=~RT_PAUSED; + deviceController->restart(); + setPauseText(tr("Pause")); + gui_timer->start(REFRESHRATE); + metrics_timer->start(METRICSRATE); + if (status & RT_STREAMING) stream_timer->start(STREAMRATE); + if (status & RT_RECORDING) disk_timer->start(SAMPLERATE); + load_period.restart(); + if (status & RT_WORKOUT) load_timer->start(LOADRATE); + } else { + session_elapsed_msec += session_time.elapsed(); + lap_elapsed_msec += lap_time.elapsed(); + deviceController->pause(); + setPauseText(tr("Un-Pause")); + status |=RT_PAUSED; + gui_timer->stop(); + metrics_timer->stop(); + if (status & RT_STREAMING) stream_timer->stop(); + if (status & RT_RECORDING) disk_timer->stop(); + if (status & RT_WORKOUT) load_timer->stop(); + load_msecs += load_period.restart(); + } +} + +void TrainTool::Stop(int deviceStatus) // when stop button is pressed +{ + if (deviceController == NULL) return; + + if ((status&RT_RUNNING) == 0) return; + + status &= ~RT_RUNNING; + setStartText(tr("Start")); + + // wipe connection + deviceController->stop(); + delete deviceController; + deviceController = NULL; + + gui_timer->stop(); + metrics_timer->stop(); + + QDateTime now = QDateTime::currentDateTime(); + + if (status & RT_RECORDING) { + disk_timer->stop(); + + // close and reset File + recordFile->close(); + + if(deviceStatus == DEVICE_ERROR) + { + recordFile->remove(); + } + else { + // 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->closeAndExit(); + delete streamController; + streamController = NULL; + } + + if (status & RT_WORKOUT) { + load_timer->stop(); + load_msecs = 0; + main->notifySetNow(load_msecs); + } + + // Re-enable gui elements + //recordSelector->setEnabled(true); + + // reset counters etc + pwrcount = 0; + cadcount = 0; + hrcount = 0; + spdcount = 0; + lodcount = 0; + displayWorkoutLap = displayLap =0; + session_elapsed_msec = 0; + session_time.restart(); + lap_elapsed_msec = 0; + lap_time.restart(); + displayWorkoutDistance = displayDistance = 0; + guiUpdate(); + + return; +} + + +// Called by push devices (e.g. ANT+) +void TrainTool::updateData(RealtimeData &rtData) +{ + displayPower = rtData.getWatts(); + displayCadence = rtData.getCadence(); + displayHeartRate = rtData.getHr(); + displaySpeed = rtData.getSpeed(); + displayLoad = rtData.getLoad(); + // Gradient not supported + return; +} + +//---------------------------------------------------------------------- +// SCREEN UPDATE FUNCTIONS +//---------------------------------------------------------------------- + +void TrainTool::guiUpdate() // refreshes the telemetry +{ + RealtimeData rtData; + + if (deviceController == NULL) return; + + // get latest telemetry from device (if it is a pull device e.g. Computrainer // + if (status&RT_RUNNING && deviceController->doesPull() == true) { + + deviceController->getRealtimeData(rtData); + + // 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 + rtData.setDistance(displayDistance); + + // time + total_msecs = session_elapsed_msec + session_time.elapsed(); + lap_msecs = lap_elapsed_msec + lap_time.elapsed(); + rtData.setMsecs(total_msecs); + rtData.setLapMsecs(lap_msecs); + + // metrics + rtData.setJoules(kjoules); + rtData.setBikeScore(bikescore); + rtData.setXPower(xpower); + + // local stuff ... + displayPower = rtData.getWatts(); + displayCadence = rtData.getCadence(); + displayHeartRate = rtData.getHr(); + displaySpeed = rtData.getSpeed(); + displayLoad = rtData.getLoad(); + + // go update the displays... + main->notifyTelemetryUpdate(rtData); // signal everyone to update telemetry + } +} + +// can be called from the controller - when user presses "Lap" button +void TrainTool::newLap() +{ + displayLap++; + + pwrcount = 0; + cadcount = 0; + hrcount = 0; + spdcount = 0; + + lap_time.restart(); + lap_elapsed_msec = 0; + +} + +// can be called from the controller +void TrainTool::nextDisplayMode() +{ +} + +void TrainTool::warnnoConfig() +{ + QMessageBox::warning(this, tr("No Devices Configured"), "Please configure a device in Preferences."); +} + +//---------------------------------------------------------------------- +// STREAMING FUNCTION +//---------------------------------------------------------------------- +#if 0 +TrainTool::SelectStream(int index) +{ + + if (index > 0) { + status |= RT_STREAMING; + setStreamController(); + } else { + status &= ~RT_STREAMING; + } +} +#endif + +void +TrainTool::streamUpdate() +{ + // send over the wire... + if (streamController) { + + // send my data + streamController->sendTelemetry(displayPower, + displayCadence, + displayDistance, + displayHeartRate, + displaySpeed); + + // get standings for everyone else + RaceStatus current = streamController->getStandings(); + + // send out to all the widgets... + notifyRaceStandings(current); + + // has the race finished? + if (current.race_finished == true) { + Stop(0); // all over dude + QMessageBox msgBox; + msgBox.setText(tr("Race Over!")); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); + } + } +} + +//---------------------------------------------------------------------- +// DISK UPDATE FUNCTIONS +//---------------------------------------------------------------------- +void TrainTool::diskUpdate() +{ + double Minutes; + + long Torq = 0, Altitude = 0; + QTextStream recordFileStream(recordFile); + + // convert from milliseconds to minutes + total_msecs = session_elapsed_msec + session_time.elapsed(); + 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"; + + rideFile->appendPoint(total_msecs/1000,displayCadence,displayHeartRate,displayDistance,displaySpeed,0, + displayPower,Altitude,0,0,0,displayLap + displayWorkoutLap); +} + +void TrainTool::metricsUpdate() +{ + // calculate bike score, xpower + const RideMetricFactory &factory = RideMetricFactory::instance(); + const RideMetric *rm = factory.rideMetric("skiba_xpower"); + + QStringList metrics; + metrics.append("skiba_bike_score"); + metrics.append("skiba_xpower"); + QHash results = rm->computeMetrics( + this->main,&*rideFile,this->main->zones(),this->main->hrZones(),metrics); + bikescore = results["skiba_bike_score"]->value(true); + xpower = results["skiba_xpower"]->value(true); +} + +//---------------------------------------------------------------------- +// WORKOUT MODE +//---------------------------------------------------------------------- + +void TrainTool::loadUpdate() +{ + long load; + double gradient; + // the period between loadUpdate calls is not constant, and not exactly LOADRATE, + // therefore, use a QTime timer to measure the load period + load_msecs += load_period.restart(); + + if (deviceController == NULL) return; + + if (status&RT_MODE_ERGO) { + load = ergFile->wattsAt(load_msecs, displayWorkoutLap); + + // we got to the end! + if (load == -100) { + Stop(DEVICE_OK); + } else { + displayLoad = load; + deviceController->setLoad(displayLoad); + main->notifySetNow(load_msecs); + } + } else { + gradient = ergFile->gradientAt(displayWorkoutDistance*1000, displayWorkoutLap); + + // we got to the end! + if (gradient == -100) { + Stop(DEVICE_OK); + } else { + displayGradient = gradient; + deviceController->setGradient(displayGradient); + main->notifySetNow(displayWorkoutDistance * 1000); + } + } +} + +void TrainTool::FFwd() +{ + if (status&RT_MODE_ERGO) load_msecs += 10000; // jump forward 10 seconds + else displayWorkoutDistance += 1; // jump forward a kilometer in the workout +} + +void TrainTool::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 TrainTool::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 TrainTool::Higher() +{ + if (deviceController == NULL) return; + + 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 TrainTool::Lower() +{ + if (deviceController == NULL) return; + + 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); +} diff --git a/src/TrainTool.h b/src/TrainTool.h index e5952a9c0..0040d8336 100644 --- a/src/TrainTool.h +++ b/src/TrainTool.h @@ -21,27 +21,64 @@ #include "GoldenCheetah.h" #include "MainWindow.h" -#include "ViewSelection.h" #include "GoldenClient.h" +#include "RealtimeData.h" +#include "RealtimePlot.h" +#include "DeviceConfiguration.h" +#include "DeviceTypes.h" +#include "ErgFile.h" +#include "ErgFilePlot.h" + +// standard stuff #include #include +#include "math.h" // for round() +#include "Units.h" // for MILES_PER_KM +#include +// Status settings +#define RT_MODE_ERGO 0x0001 // load generation modes +#define RT_MODE_SPIN 0x0002 // spinscan like modes +#define RT_RUNNING 0x0100 // is running now +#define RT_PAUSED 0x0200 // is paused +#define RT_RECORDING 0x0400 // is recording to disk +#define RT_WORKOUT 0x0800 // is running a workout +#define RT_STREAMING 0x1000 // is streaming to a remote peer + +// msecs constants for timers +#define REFRESHRATE 200 // screen refresh in milliseconds +#define STREAMRATE 200 // rate at which we stream updates to remote peer +#define SAMPLERATE 1000 // disk update in milliseconds +#define LOADRATE 1000 // rate at which load is adjusted +#define METRICSRATE 1000 // rate the metrics are updated + +// device treeview node types #define HEAD_TYPE 6666 #define SERVER_TYPE 5555 #define WORKOUT_TYPE 4444 #define DEVICE_TYPE 0 +class RealtimeController; +class ComputrainerController; +class ANTplusController; +class ANTlocalController; +class NullController; +class RealtimePlot; +class RealtimeData; + class TrainTool : public QWidget { Q_OBJECT G_OBJECT - public: TrainTool(MainWindow *parent, const QDir &home); QStringList listWorkoutFiles(const QDir &) const; + RealtimeController *deviceController; // read from + GoldenClient *streamController; // send out to + const QTreeWidgetItem *currentWorkout() { return workout; } const QTreeWidgetItem *workoutItems() { return allWorkouts; } const QTreeWidgetItem *currentServer() { return server; } @@ -57,13 +94,21 @@ class TrainTool : public QWidget // notify widgets of race update void notifyRaceStandings(RaceStatus x) { raceStandings(x); } + // was realtimewindow,merged into tool + // update charts/dials and manage controller + void updateData(RealtimeData &); // to update telemetry by push devices + void newLap(); // start new Lap! + void nextDisplayMode(); // show next display mode + void setDeviceController(); // based upon selected device + void setStreamController(); // based upon selected device + // this QTabWidget *trainTabs; + signals: void deviceSelected(); void serverSelected(); - void workoutSelected(); void raceStandings(RaceStatus); void start(); void pause(); @@ -74,9 +119,28 @@ class TrainTool : public QWidget void deviceTreeWidgetSelectionChanged(); void workoutTreeWidgetSelectionChanged(); void configChanged(); - void Start(); - void Pause(); - void Stop(); + + public slots: + + void Start(); // when start button is pressed + void Pause(); // when Paude is pressed + void Stop(int status=0); // when controller wants to stop + + void FFwd(); // jump forward when in a workout + void Rewind(); // jump backwards when in a workout + void FFwdLap(); // jump forward to next Lap marker + void Higher(); // set load/gradient higher + void Lower(); // set load/gradient higher + + // Timed actions + void guiUpdate(); // refreshes the telemetry + void diskUpdate(); // writes to CSV file + void streamUpdate(); // writes to remote Peer + void loadUpdate(); // sets Load on CT like devices + void metricsUpdate(); // calculates the metrics + + // When no config has been setup + void warnnoConfig(); private: @@ -100,6 +164,46 @@ class TrainTool : public QWidget *pauseButton, *stopButton; + int FTP; // current FTP + + QList Devices; + bool useMetricUnits; + + // updated with a RealtimeData object either from + // update() - from a push device (quarqd ANT+) + // Device->getRealtimeData() - from a pull device (Computrainer) + double displayPower, displayHeartRate, displayCadence, + displayLoad, displayGradient, displaySpeed; + double displayDistance, displayWorkoutDistance; + int displayLap; // user increment for Lap + int displayWorkoutLap; // which Lap in the workout are we at? + double kjoules; + double bikescore; + double xpower; + + // for non-zero average calcs + int pwrcount, cadcount, hrcount, spdcount, lodcount, grdcount; // for NZ average calc + int status; + int displaymode; + + QFile *recordFile; // where we record! + ErgFile *ergFile; // workout file + boost::shared_ptr rideFile; // keeps track of the workout to figure out BikeScore + + long total_msecs, + lap_msecs, + load_msecs; + QTime load_period; + + uint session_elapsed_msec, lap_elapsed_msec; + QTime session_time, lap_time; + + QTimer *gui_timer, // refresh the gui + *stream_timer, // send telemetry to server + *load_timer, // change the load on the device + *disk_timer, // write to .CSV file + *metrics_timer; // computational intense metrics + public: // everyone else wants this QCheckBox *recordSelector; diff --git a/src/TrainWindow.cpp b/src/TrainWindow.cpp deleted file mode 100644 index 6d33b51ad..000000000 --- a/src/TrainWindow.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 "TrainWindow.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "ViewSelection.h" -#include "RaceDispatcher.h" -#include "MainWindow.h" -#include "Settings.h" -#include "Units.h" -#include -#include - -TrainWindow::TrainWindow(MainWindow *parent, const QDir &home) : GcWindow(parent), home(home), main(parent) -{ - QVBoxLayout *mainLayout = new QVBoxLayout; - setLayout(mainLayout); - - // RaceDispatcher first -- could be connected by lots of different widgets - dispatcher = new RaceDispatcher(this); - - // LeftSide - trainTool = new TrainTool(parent, home); - setControls(trainTool); - - // Right side - trainTabs = new TrainTabs(parent, trainTool, home); - mainLayout->addWidget(trainTabs); -} diff --git a/src/TrainWindow.h b/src/TrainWindow.h deleted file mode 100644 index 156e45cec..000000000 --- a/src/TrainWindow.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2000 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 - */ - -#ifndef _GC_TrainWindow_h -#define _GC_TrainWindow_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "RaceDispatcher.h" - -#include "RealtimeWindow.h" - -class TrainWindow : public GcWindow -{ - Q_OBJECT - G_OBJECT - - public: - TrainWindow(MainWindow *parent, const QDir &home); - - public slots: - - private: - const QDir home; - const MainWindow *main; - - TrainTool *trainTool; - TrainTabs *trainTabs; - RaceDispatcher *dispatcher; -}; - -#endif // _GC_TrainWindow_h diff --git a/src/VideoWindow.cpp b/src/VideoWindow.cpp index 15a174b9f..a65a91efe 100644 --- a/src/VideoWindow.cpp +++ b/src/VideoWindow.cpp @@ -46,7 +46,7 @@ GcWindow(parent), home(home), main(parent) /* Create a new item */ // XXX need to add controls - not everyone is going to want to play a video from // my desktop!!! - m = libvlc_media_new_path(inst, "/home/markl/Desktop/My Documents/fightclub.mp4"); + m = libvlc_media_new_path(inst, "/home/markl/Videos/fightclub.mp4"); //vlc_exceptions(&exceptions); /* Create a media player playing environement */ diff --git a/src/ViewSelection.cpp b/src/ViewSelection.cpp deleted file mode 100644 index 867bb264e..000000000 --- a/src/ViewSelection.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 "ViewSelection.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "MainWindow.h" -#include "Settings.h" -#include "Units.h" -#include -#include -#include -#include - -ViewSelection::ViewSelection(const MainWindow *parent, const int homeView) : -QComboBox((QWidget *)parent), -main(parent), -homeView(homeView) -{ - // don't make a fat ugly box... - setFixedHeight(((double)(QFontMetrics(font()).height()) * 1.6)); - - // set drop-down list - addItem(tr("Ride Analysis View"), VIEW_ANALYSIS); - addItem(tr("Train and Racing View"), VIEW_TRAIN); - - // Default to home view - if (homeView == VIEW_ANALYSIS) setCurrentIndex(0); - if (homeView == VIEW_TRAIN) setCurrentIndex(1); - - // set to this view - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(changeView(int))); -} - -void -ViewSelection::changeView(int index) -{ - int view = itemData(index).toInt(); - - if (view != homeView) { - ((MainWindow *)main)->selectView(view); - } - - // now reset back to the homeView! - if (homeView == VIEW_ANALYSIS) setCurrentIndex(0); - if (homeView == VIEW_TRAIN) setCurrentIndex(1); -} diff --git a/src/ViewSelection.h b/src/ViewSelection.h deleted file mode 100644 index c85010044..000000000 --- a/src/ViewSelection.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2000 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 - */ - -#ifndef _GC_ViewSelection_h -#define _GC_ViewSelection_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" - -#define VIEW_ANALYSIS 1 -#define VIEW_TRAIN 2 - -class ViewSelection : public QComboBox -{ - Q_OBJECT - G_OBJECT - - - public: - ViewSelection(const MainWindow *parent, const int homeView); - - public slots: - void changeView(int); - - private: - const MainWindow *main; - const int homeView; -}; - -#endif // _GC_ViewSelection_h diff --git a/src/WorkoutPlotWindow.cpp b/src/WorkoutPlotWindow.cpp index 353490a51..6f9097b9a 100644 --- a/src/WorkoutPlotWindow.cpp +++ b/src/WorkoutPlotWindow.cpp @@ -27,6 +27,7 @@ WorkoutPlotWindow::WorkoutPlotWindow(MainWindow *mainWindow) : setControls(NULL); QVBoxLayout *layout = new QVBoxLayout(this); + layout->setSpacing(0); ergPlot = new ErgFilePlot(0); layout->addWidget(ergPlot); diff --git a/src/application.qrc b/src/application.qrc index 1cd5c56c0..587c5cc9e 100644 --- a/src/application.qrc +++ b/src/application.qrc @@ -35,6 +35,7 @@ xml/charts.xml xml/metadata.xml xml/measures.xml + xml/train-layout.xml html/ltm-summary.html images/toolbar/close-icon.png images/toolbar/save.png diff --git a/src/src.pro b/src/src.pro index d6421be4f..e26967b0a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -244,7 +244,6 @@ HEADERS += \ RaceDispatcher.h \ RealtimeData.h \ RealtimePlotWindow.h \ - RealtimeWindow.h \ RealtimeController.h \ ComputrainerController.h \ RealtimePlot.h \ @@ -283,13 +282,10 @@ HEADERS += \ TxtRideFile.h \ TimeUtils.h \ ToolsDialog.h \ - TrainTabs.h \ TrainTool.h \ - TrainWindow.h \ TreeMapWindow.h \ TreeMapPlot.h \ Units.h \ - ViewSelection.h \ WeeklySummaryWindow.h \ WeeklyViewItemDelegate.h \ WithingsDownload.h \ @@ -406,7 +402,6 @@ SOURCES += \ RealtimeData.cpp \ RealtimeController.cpp \ ComputrainerController.cpp \ - RealtimeWindow.cpp \ RealtimePlot.cpp \ RealtimePlotWindow.cpp \ RideCalendar.cpp \ @@ -445,13 +440,10 @@ SOURCES += \ TimeInZone.cpp \ TimeUtils.cpp \ ToolsDialog.cpp \ - TrainTabs.cpp \ TrainTool.cpp \ - TrainWindow.cpp \ TreeMapWindow.cpp \ TreeMapPlot.cpp \ TRIMPPoints.cpp \ - ViewSelection.cpp \ WattsPerKilogram.cpp \ WithingsDownload.cpp \ WeeklySummaryWindow.cpp \ diff --git a/src/xml/train-layout.xml b/src/xml/train-layout.xml new file mode 100644 index 000000000..55dba17dc --- /dev/null +++ b/src/xml/train-layout.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +