Make Training View Configurable

The training view (aka realtime) is now configurable
allowing users to drag and drop appropriate charts
and dials onto the main view.

The controls for this view are static and comprise the
old controls with start/stop buttons, device selections etc.

I have removed deprecated code too, the following are removed
from the repository;
* ViewSelection
* RealtimeWindow
* TrainWindow
* TrainTabs

Fixes #290.
This commit is contained in:
Mark Liversedge
2011-05-10 18:21:08 +01:00
parent e0c7560c74
commit fc741fb9b4
36 changed files with 907 additions and 1563 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -4,7 +4,6 @@
#include "MainWindow.h"
#include "ConfigDialog.h"
#include "RealtimeWindow.h"
#include "Pages.h"
#include "Settings.h"
#include "Zones.h"

View File

@@ -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;

View File

@@ -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;

View File

@@ -38,7 +38,6 @@ enum gcwinid {
PerformanceManager =9,
PfPv =10,
Race =11,
Training =12,
RideEditor =13,
RideSummary =14,
Scatter =15,

View File

@@ -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

View File

@@ -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(); }

View File

@@ -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

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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() { }

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -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 <QDebug>
// 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<RideFile>(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<RideFile>(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<QString,RideMetricPtr> 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);
}

View File

@@ -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 <QtGui>
#include <QTimer>
#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<DeviceConfiguration> 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> 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

View File

@@ -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)
{

View File

@@ -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() { }

View File

@@ -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"));
};

View File

@@ -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 <QDir>
#include <QtGui>
#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

View File

@@ -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 <QtGui>
#include <QRegExp>
// 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<RideFile>(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.count(); i++) delete devices.at(i);
DeviceConfigurations all;
QList<DeviceConfiguration> Devices;
Devices.clear();
Devices = all.getList();
for (int i=0; i<Devices.count(); i++) {
if (Devices.at(i).type == DEV_GSERVER) {
@@ -180,28 +227,20 @@ TrainTool::configChanged()
QTreeWidgetItem *work = new QTreeWidgetItem(allWorkouts, WORKOUT_TYPE);
work->setText(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<RideFile>(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<QString,RideMetricPtr> 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);
}

View File

@@ -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 <QDir>
#include <QtGui>
#include "math.h" // for round()
#include "Units.h" // for MILES_PER_KM
#include <QDebug>
// 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<DeviceConfiguration> 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> 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;

View File

@@ -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 <QApplication>
#include <QtGui>
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);
}

View File

@@ -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 <QDir>
#include <QtGui>
#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

View File

@@ -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 */

View File

@@ -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 <QApplication>
#include <QtGui>
#include <QFont>
#include <QFontMetrics>
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);
}

View File

@@ -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 <QDir>
#include <QtGui>
#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

View File

@@ -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);

View File

@@ -35,6 +35,7 @@
<file>xml/charts.xml</file>
<file>xml/metadata.xml</file>
<file>xml/measures.xml</file>
<file>xml/train-layout.xml</file>
<file>html/ltm-summary.html</file>
<file>images/toolbar/close-icon.png</file>
<file>images/toolbar/save.png</file>

View File

@@ -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 \

93
src/xml/train-layout.xml Normal file
View File

@@ -0,0 +1,93 @@
<layout name="train">
<chart id="25" name="RT Plot" title="Workout" >
<property name="instanceName" type="QString" value="RT Plot" />
<property name="title" type="QString" value="Workout" />
<property name="widthFactor" type="double" value="1.69427" />
<property name="heightFactor" type="double" value="5.24068" />
<property name="resizable" type="bool" value="1" />
</chart>
<chart id="22" name="Dial" title="Time" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Time" />
<property name="widthFactor" type="double" value="2.43816" />
<property name="heightFactor" type="double" value="5.03571" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="0" />
<property name="avgType" type="int" value="4" />
<property name="dataSeries" type="int" value="1" />
<property name="style" type="int" value="42092544" />
</chart>
<chart id="24" name="RT Plot" title="Performance" >
<property name="instanceName" type="QString" value="RT Plot" />
<property name="title" type="QString" value="Performance" />
<property name="widthFactor" type="double" value="1" />
<property name="heightFactor" type="double" value="1.54422" />
<property name="resizable" type="bool" value="1" />
</chart>
<chart id="22" name="Dial" title="Power" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Power" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="1" />
<property name="avgType" type="int" value="32634" />
<property name="dataSeries" type="int" value="4" />
<property name="style" type="int" value="32512" />
</chart>
<chart id="22" name="Dial" title="Cadence" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Cadence" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="1" />
<property name="avgType" type="int" value="0" />
<property name="dataSeries" type="int" value="6" />
<property name="style" type="int" value="0" />
</chart>
<chart id="22" name="Dial" title="Heartrate" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Heartrate" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="1" />
<property name="avgType" type="int" value="27263016" />
<property name="dataSeries" type="int" value="7" />
<property name="style" type="int" value="27263071" />
</chart>
<chart id="22" name="Dial" title="Speed" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Speed" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="1" />
<property name="avgType" type="int" value="507" />
<property name="dataSeries" type="int" value="5" />
<property name="style" type="int" value="616" />
</chart>
<chart id="22" name="Dial" title="Energy" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="Energy" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="1" />
<property name="avgType" type="int" value="0" />
<property name="dataSeries" type="int" value="11" />
<property name="style" type="int" value="68510928" />
</chart>
<chart id="22" name="Dial" title="BikeScore" >
<property name="instanceName" type="QString" value="Dial" />
<property name="title" type="QString" value="BikeScore" />
<property name="widthFactor" type="double" value="6" />
<property name="heightFactor" type="double" value="6" />
<property name="resizable" type="bool" value="1" />
<property name="showInstant" type="bool" value="0" />
<property name="avgType" type="int" value="0" />
<property name="dataSeries" type="int" value="9" />
<property name="style" type="int" value="3072" />
</chart>
</layout>