Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F91053858
mainwindow.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Thu, Nov 7, 09:18
Size
13 KB
Mime Type
text/x-c
Expires
Sat, Nov 9, 09:18 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
22187064
Attached To
R2671 HHRI-software
mainwindow.cpp
View Options
/*
* Copyright (C) 2017 EPFL-LSRO (Laboratoire de Systemes Robotiques).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QDebug>
#include <QInputDialog>
#include <QFileDialog>
#include <QList>
#include <QDateTime>
#include <limits>
#define GRAPH_UPDATE_PERIOD 50 ///< Plot window refresh period [ms].
#define LOG_CHECKBOX_BASE_LABEL QString("Log to CSV file")
#define SETTING_WINDOW_SIZE "window_size"
#define SETTING_LOGS_DIR "logs_dir"
/**
* @brief Constructor
* Setups the GUI, and try to connect to the HRI board.
* @param parent parent of this widget.
*/
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
//
ui->setupUi(this);
variablesListLayout = new QGridLayout();
variablesListLayout->setAlignment(Qt::AlignTop);
delete ui->scrollWidget->layout();
ui->scrollWidget->setLayout(variablesListLayout);
chart = new QtCharts::QChart();
ui->graphicsView->setChart(chart);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
graphUpdateTimer.setInterval(GRAPH_UPDATE_PERIOD);
graphUpdateTimer.setSingleShot(false);
connect(&graphUpdateTimer, SIGNAL(timeout()), this, SLOT(updateGraph()));
//
syncVars = nullptr;
// Recover the previous logs save location, or define if this is the first
// time the app is started.
if(settings.contains(SETTING_LOGS_DIR))
logsDirectory = settings.value(SETTING_LOGS_DIR).toString();
else
{
logsDirectory = qApp->applicationDirPath();
#if defined(Q_OS_MACX) || defined(Q_OS_MAC64)
// For MacOSX, go to out of the app bundle.
logsDirectory.remove(QRegExp("/[^/]*.app/Contents/MacOS/?$"));
#endif
}
ui->logToFileCheckbox->setText(LOG_CHECKBOX_BASE_LABEL +
" (in " + logsDirectory + ")");
// Resize the window to the previous size.
if(settings.contains(SETTING_WINDOW_SIZE))
resize(settings.value(SETTING_WINDOW_SIZE).toSize());
// Ask for the COM port.
QString comPortName;
QStringList ports = HriBoard::getComPorts();
if(ports.size() == 0)
{
QMessageBox::critical(this, qApp->applicationName(), "No COM port.");
exit(0);
}
else if(ports.size() == 1)
comPortName = ports.first();
else
{
bool ok;
QString portChoice = QInputDialog::getItem(this,
qApp->applicationName(),
"Serial port:", ports, 0,
false, &ok);
int portIndex = ports.indexOf(portChoice);
if(portIndex == -1 || !ok)
exit(0);
comPortName = ports[portIndex];
}
//
connect(ui->clearButton, SIGNAL(clicked(bool)), this, SLOT(clearPlot()));
connect(ui->logToFileCheckbox, SIGNAL(toggled(bool)),
this, SLOT(onLogToFileCheckboxToggled()));
connect(ui->setLogLocationButton, SIGNAL(clicked(bool)),
this, SLOT(setLogfilesDirectory()));
// Establish the link with the HRI board.
try
{
hriBoard.openLink(comPortName);
}
catch(std::runtime_error)
{
QMessageBox::critical(nullptr, qApp->applicationName(),
"Could not open the serial port " + comPortName);
exit(0);
}
connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList<SyncVarBase*>&)),
this, SLOT(onVarsListReceived(const QList<SyncVarBase*>&)));
connect(&hriBoard, SIGNAL(syncVarUpdated(SyncVarBase*)),
this, SLOT(onVarUpdated(SyncVarBase*)));
}
/**
* @brief Destructor.
*/
MainWindow::~MainWindow()
{
// Store the settings.
settings.setValue(SETTING_WINDOW_SIZE, size());
settings.setValue(SETTING_LOGS_DIR, logsDirectory);
//
delete ui;
}
/**
* @brief Displays the SyncVars list, and starts the streaming.
* @param syncVars SyncVars list.
* @remark This slot function is called automatically by the HriBoard object, as
* soon as the list is received from the board.
*/
void MainWindow::onVarsListReceived(const QList<SyncVarBase *> &syncVars)
{
this->syncVars = &syncVars;
QLayoutItem *li;
while((li = variablesListLayout->takeAt(0)) != nullptr)
li->widget()->deleteLater();
syncVarsWidgets.clear();
for(int i=0; i<syncVars.size(); i++)
{
SyncVarWidgetsLine varsWidgets;
// Name label.
varsWidgets.name = new QLabel(syncVars[i]->getName());
variablesListLayout->addWidget(varsWidgets.name, i, 0);
// Get button.
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.getButton = new QPushButton("Get");
variablesListLayout->addWidget(varsWidgets.getButton, i, 1);
connect(varsWidgets.getButton, SIGNAL(clicked(bool)),
this, SLOT(onGetButtonPressed()));
}
// Value field.
varsWidgets.valueSpinBox = new QDoubleSpinBox();
varsWidgets.valueSpinBox->setDecimals(4);
varsWidgets.valueSpinBox->setMinimum(std::numeric_limits<double>::lowest());
varsWidgets.valueSpinBox->setMaximum(std::numeric_limits<double>::max());
variablesListLayout->addWidget(varsWidgets.valueSpinBox, i, 2);
if(syncVars[i]->getAccess() == READONLY)
{
varsWidgets.valueSpinBox->setReadOnly(true);
varsWidgets.valueSpinBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
}
else
{
connect(varsWidgets.valueSpinBox, SIGNAL(editingFinished()),
this, SLOT(onSetButtonPressed()));
}
// Set button.
if(syncVars[i]->getAccess() != READONLY)
{
varsWidgets.setButton = new QPushButton("Set");
variablesListLayout->addWidget(varsWidgets.setButton, i, 3);
connect(varsWidgets.setButton, SIGNAL(clicked(bool)),
this, SLOT(onSetButtonPressed()));
}
// Stream checkbox.
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.streamCheckbox = new QCheckBox("Stream");
variablesListLayout->addWidget(varsWidgets.streamCheckbox, i, 4);
connect(varsWidgets.streamCheckbox, SIGNAL(toggled(bool)),
this, SLOT(onStreamCheckboxToggled()));
}
if(syncVars[i]->getAccess() != WRITEONLY)
{
varsWidgets.scaleSpinbox = new QDoubleSpinBox();
variablesListLayout->addWidget(varsWidgets.scaleSpinbox, i, 5);
varsWidgets.scaleSpinbox->setPrefix("x");
varsWidgets.scaleSpinbox->setValue(1.0);
varsWidgets.scaleSpinbox->setRange(-1000000.0, 1000000.0);
}
syncVarsWidgets.append(varsWidgets);
}
//
clearPlot();
}
/**
* @brief Updates the "value" field of a SyncVar.
* @param var the SyncVar corresponding to the "value" field to update.
*/
void MainWindow::onVarUpdated(SyncVarBase *var)
{
int varIndex = syncVars->indexOf(var);
if(varIndex >= 0 && varIndex < syncVarsWidgets.size())
syncVarsWidgets[varIndex].valueSpinBox->setValue(var->toDouble());
}
/**
* @brief Gets the value of a SyncVar whose the "Get" button was pressed.
* @warning This function should only be called by the "Get" button signal.
*/
void MainWindow::onGetButtonPressed()
{
int varIndex = getVarIndexFromWidget((QWidget*)sender());
hriBoard.readRemoteVar(syncVars->at(varIndex));
}
/**
* @brief Sets the value of a SyncVar whose the "Set" button was pressed.
* @warning This function should only be called by the "Set" button signal.
*/
void MainWindow::onSetButtonPressed()
{
int varIndex = getVarIndexFromWidget((QWidget*)sender());
SyncVarBase *sv = syncVars->at(varIndex);
sv->fromDouble(syncVarsWidgets[varIndex].valueSpinBox->value());
hriBoard.writeRemoteVar(sv);
}
/**
* @brief Updates the list of SyncVars to stream from the checkboxes' states.
*/
void MainWindow::onStreamCheckboxToggled()
{
// Setup the streaming.
QList<SyncVarBase*> varsToStream;
for(int i=0; i<syncVarsWidgets.size(); i++)
{
if(syncVarsWidgets[i].streamCheckbox->isChecked())
varsToStream.append(syncVars->at(i));
}
hriBoard.setStreamedVars(varsToStream, &streamedVarsValuesBuffer);
// Setup the graph.
streamedVarsValuesBuffer.clear();
chart->removeAllSeries();
linesSeries.clear();
for(SyncVarBase *sv : varsToStream)
{
QtCharts::QLineSeries *series = new QtCharts::QLineSeries();
series->setName(sv->getName());
chart->addSeries(series);
linesSeries.append(series);
}
chart->createDefaultAxes();
chart->axisX()->setTitleText("Time [s]");
// Setup the update timer.
if(varsToStream.isEmpty())
graphUpdateTimer.stop();
else
graphUpdateTimer.start();
}
/**
* @brief Clears the plot window.
*/
void MainWindow::clearPlot()
{
for(QtCharts::QLineSeries *ls : linesSeries)
ls->clear();
}
/**
* @brief Starts or stops the streamed variables logging to file.
*/
void MainWindow::onLogToFileCheckboxToggled()
{
if(ui->logToFileCheckbox->isChecked())
{
if(!hriBoard.startLoggingToFile(logsDirectory))
{
// If the logging cannot start, uncheck the box.
QSignalBlocker signalBlocker(ui->logToFileCheckbox);
ui->logToFileCheckbox->setChecked(false);
}
}
else
hriBoard.stopLoggingToFile();
}
/**
* @brief Sets the directory to save the logfiles.
*/
void MainWindow::setLogfilesDirectory()
{
QString newDir = QFileDialog::getExistingDirectory(this,
"Select the directory to save the logfiles", logsDirectory);
if(!newDir.isEmpty())
{
logsDirectory = newDir;
ui->logToFileCheckbox->setText(LOG_CHECKBOX_BASE_LABEL +
" (in " + logsDirectory + ")");
}
}
/**
* @brief Refreshes the plot frame.
*/
void MainWindow::updateGraph()
{
// Get the variables scales.
const QList<SyncVarBase*> &streamedVars = hriBoard.getStreamedVars();
QList<double> plotScales;
for(SyncVarBase* sv : streamedVars)
{
int varIndex = syncVars->indexOf(sv);
if(varIndex != -1)
plotScales.append(syncVarsWidgets[varIndex].scaleSpinbox->value());
}
// Add the new points to the graph series.
int decimationCounter = 0;
while(!streamedVarsValuesBuffer.isEmpty())
{
if(decimationCounter <= 0)
{
decimationCounter = ui->plotDecimSpinbox->value();
QList<double> &values = streamedVarsValuesBuffer.first();
for(int i=0; i<linesSeries.size(); i++)
linesSeries[i]->append(values[0], values[i+1] * plotScales[i]);
}
else
decimationCounter--;
streamedVarsValuesBuffer.removeFirst();
}
// Remove the oldest points, to limit the number of points displayed.
for(QtCharts::QLineSeries *ls : linesSeries)
{
if(ls->count() > ui->nPointsSpinbox->value())
ls->removePoints(0, ls->count() - ui->nPointsSpinbox->value());
}
// Compute the display range.
if(linesSeries.size() > 0 && linesSeries.first()->count() > 0)
{
// Compute the horizontal (time) range.
double firstTime = linesSeries.first()->points().first().x();
double lastTime = linesSeries.first()->points().last().x();
chart->axisX()->setRange(firstTime, lastTime);
// Compute the vertical range.
double min = std::numeric_limits<double>::max();
double max = std::numeric_limits<double>::lowest();
for(QtCharts::QLineSeries *ls : linesSeries)
{
QVector<QPointF> points = ls->pointsVector();
for(QPointF p : points)
{
if(p.y() < min)
min = p.y();
if(p.y() > max)
max = p.y();
}
}
if(min == max)
{
min -= 1.0;
max += 1.0;
}
else
{
min -= (max-min) * 0.05; // Add a 5% margin at the top and the
max += (max-min) * 0.05; // bottom.
}
chart->axisY()->setRange(min, max);
}
}
/**
* @brief Gets the variable index from a widget of the SyncVars list.
* @param widget the widget belonging to the variables list.
* @return The index of the SyncVar corresponding to the given widget.
*/
int MainWindow::getVarIndexFromWidget(QWidget *widget)
{
int index = variablesListLayout->indexOf(widget);
int row, column, rowSpan, columnSpan;
variablesListLayout->getItemPosition(index, &row, &column,
&rowSpan, &columnSpan);
return row;
}
Event Timeline
Log In to Comment