diff --git a/CPP/HriBoardLib/syncvar.cpp b/CPP/HriBoardLib/syncvar.cpp index f384178..d2769c6 100644 --- a/CPP/HriBoardLib/syncvar.cpp +++ b/CPP/HriBoardLib/syncvar.cpp @@ -1,146 +1,147 @@ /* * 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 "syncvar.h" #include "hriboard.h" #include /** * @brief Constructor. * @param index index in the SyncVars list. * @param name name of the SyncVar. * @param access access right of the SyncVar. * @param data address of the bytes array to hold the value of the variable. * @param size size of the bytes array that holds the value of the variable. */ SyncVarBase::SyncVarBase(int index, QString name, VarAccess access, uint8_t *data, int size) : index(index), name(name), access(access), data(data), size(size) { upToDate = false; } /** * @brief Gets the index in the SyncVars list. * @return the index. */ int SyncVarBase::getIndex() const { return index; } /** * @brief Gets the name of the SyncVar. * @return the name. */ QString SyncVarBase::getName() const { return name; } /** * @brief Gets the size of the SyncVar value. * @return the value size [byte]. */ int SyncVarBase::getSize() const { return size; } /** * @brief Gets the SyncVar access rights. * @return the SyncVar access rights. */ VarAccess SyncVarBase::getAccess() const { return access; } /** * @brief Gets the local value in raw bytes form. * @return a copy of the value data. * @remark this value may not match the actual value of the variable on the * board, because this function does not perform the synchronisation. */ QByteArray SyncVarBase::getData() const { QByteArray ba((char*)data, size); return ba; } /** * @brief Sets the local value with raw bytes. * @param newData the new value data. * @remark this function only sets the local value of the SyncVar, not the value - * of the one on the board (no synchronisation performed). + * of the one on the board (no synchronisation performed). For this you need to + * call HriBoard::writeRemoteVar(). */ void SyncVarBase::setData(QByteArray newData) { if(newData.size() != size) throw std::runtime_error("SyncVar::setData(): size do not match."); memcpy(data, newData.data(), size); upToDate = true; } /** * @brief Gets if the variable is up-to-date. * @return true if the variable value has been set, false if it has not been set * since the beginning, or the call to setOutOfDate(). */ bool SyncVarBase::isUpToDate() const { return upToDate; } /** * @brief Sets the variable as out-of-date. * This function is useful when the user need to know when the variable value * has been updated. */ void SyncVarBase::setOutOfDate() { upToDate = false; } /** * @brief Construct a SyncVar object with the given characteristics. * @param type type of the SyncVar. * @param index index of the SyncVar, as defined by the board. * @param name user-readable name of the SyncVar. * @param access access rights for this variable. * @return A pointer to the constructed SyncVar. */ SyncVarBase *makeSyncVar(VarType type, int index, QString name, VarAccess access) { switch(type) { case BOOL: return new SyncVar(index, name, access, 1); case UINT8: return new SyncVar(index, name, access, 1); case INT8: return new SyncVar(index, name, access, 1); case UINT16: return new SyncVar(index, name, access, 2); case INT16: return new SyncVar(index, name, access, 2); case UINT32: return new SyncVar(index, name, access, 4); case INT32: return new SyncVar(index, name, access, 4); case UINT64: return new SyncVar(index, name, access, 8); case INT64: return new SyncVar(index, name, access, 8); case FLOAT32: return new SyncVar(index, name, access, 4); case FLOAT64: return new SyncVar(index, name, access, 8); default: return nullptr; } } diff --git a/CPP/HriBoardLib/syncvar.h b/CPP/HriBoardLib/syncvar.h index 743b23f..2e1a150 100644 --- a/CPP/HriBoardLib/syncvar.h +++ b/CPP/HriBoardLib/syncvar.h @@ -1,108 +1,213 @@ /* * 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. */ #ifndef SYNCVAR_H #define SYNCVAR_H #include +#include #include #include #include "../../Firmware/src/definitions.h" typedef comm_VarType VarType; typedef comm_VarAccess VarAccess; /** * @addtogroup HriBoardLib * @{ */ /** * @brief Variable that can be synchronized with its HRI board counterpart. */ class SyncVarBase { public: SyncVarBase(int index, QString name, VarAccess access, uint8_t* data, int size); virtual ~SyncVarBase() = default; int getIndex() const; QString getName() const; int getSize() const; VarAccess getAccess() const; QByteArray getData() const; void setData(QByteArray newData); bool isUpToDate() const; void setOutOfDate(); + /** + * @brief Gets the variable value, as a floating-point number. + * @return The variable value, casted to the double type. + */ virtual double toDouble() const = 0; + + /** + * @brief Gets the variable value, as text. + * @return A string with the variable value printed. + */ + virtual QString toString() const = 0; + + /** + * @brief Sets the variable local value from a floating-point number. + * @param value the new value of the variable. It will be casted to the + * actual variable type. + * @remark This does not change the value of the variable on the board. For + * this you need to call HriBoard::writeRemoteVar(). + */ virtual void fromDouble(double value) = 0; + /** + * @brief Sets the variable local value from a string. + * This method tries to convert the given string to a number, then checks + * that it fits into the variable type. If the given number is invalid, or + * out-of-range, it will be ignored and the variable value will not change. + * @param text the new value of the variable. It will interpreted and + * converted to the actual variable type. + * @return true if the text represented a valid number, false otherwise. + * @remark This does not change the value of the variable on the board. For + * this you need to call HriBoard::writeRemoteVar(). + */ + virtual bool fromString(QString text) = 0; + private: int index; ///< Index of the SyncVar in the list. QString name; ///< Name describing the SyncVar. VarAccess access; ///< Access rights of the variable. bool upToDate; ///< Indicates whether the local value is up-to-date or not. uint8_t *const data; const int size; }; template class SyncVar : public SyncVarBase { public: SyncVar(int index, QString name, VarAccess access, int varSize) : SyncVarBase(index, name, access, (uint8_t*)&value, varSize) { } ~SyncVar() = default; void setLocalValue(T value) { this->value = value; } T getLocalValue() { return value; } double toDouble() const override { return (double)value; } + QString toString() const override + { + return QString::number(value); + } + void fromDouble(double value) override { this->value = (T)value; } + bool fromString(QString text) override + { + // If the number is supposed to be integer, check that the string does + // not contain characters for floating-point numbers. + if(std::numeric_limits::is_integer && + text.contains(QRegExp("[.,eE]"))) + { + return false; + } + + // Convert from string to number, and check that the conversion was + // successful, and the number fits the type range. + if(std::numeric_limits::is_integer) + { + if(std::numeric_limits::is_signed) + { + bool ok; + int64_t tempValue = text.toInt(&ok); + + if(!ok) + return false; + + if(tempValue >= std::numeric_limits::lowest() && + tempValue <= std::numeric_limits::max()) + { + value = (T)tempValue; + return true; + } + else + return false; + } + else + { + bool ok; + uint64_t tempValue = text.toInt(&ok); + + if(!ok) + return false; + + if(tempValue >= std::numeric_limits::lowest() && + tempValue <= std::numeric_limits::max()) + { + value = (T)tempValue; + return true; + } + else + return false; + } + } + else + { + bool ok; + double tempValue = text.toDouble(&ok); + + if(!ok) + return false; + + if(tempValue >= std::numeric_limits::lowest() && + tempValue <= std::numeric_limits::max()) + { + value = (T)tempValue; + return true; + } + else + return false; + } + } + protected: T value; }; SyncVarBase* makeSyncVar(VarType type, int index, QString name, VarAccess access); /** * @} */ #endif // SYNCVAR_H diff --git a/CPP/HriPcController/mainwindow.cpp b/CPP/HriPcController/mainwindow.cpp index d02c441..51364c7 100644 --- a/CPP/HriPcController/mainwindow.cpp +++ b/CPP/HriPcController/mainwindow.cpp @@ -1,486 +1,494 @@ /* * 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 #include #include #include #include #include #include #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_LIST_FRAME_HEIGHT "list_frame_height" #define SETTING_PLOT_FRAME_HEIGHT "plot_frame_height" #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); ui->clearButton->setIcon(QIcon(qApp->style()->standardPixmap(QStyle::SP_DialogResetButton))); ui->pausePlotButton->setIcon(QIcon(qApp->style()->standardPixmap(QStyle::SP_MediaPause))); ui->setLogLocationButton->setIcon(qApp->style()->standardPixmap(QStyle::SP_DirOpenIcon)); 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 and the panes to the previous size. if(settings.contains(SETTING_WINDOW_SIZE)) resize(settings.value(SETTING_WINDOW_SIZE).toSize()); if(settings.contains(SETTING_LIST_FRAME_HEIGHT) && settings.contains(SETTING_PLOT_FRAME_HEIGHT)) { int plotGroupWidth = settings.value(SETTING_LIST_FRAME_HEIGHT).toInt(); int plotGroupHeight = settings.value(SETTING_PLOT_FRAME_HEIGHT).toInt(); ui->splitter->setSizes({ plotGroupWidth, plotGroupHeight }); } // 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&)), this, SLOT(onVarsListReceived(const QList&))); 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_LIST_FRAME_HEIGHT, ui->splitter->sizes()[0]); settings.setValue(SETTING_PLOT_FRAME_HEIGHT, ui->splitter->sizes()[1]); 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 &syncVars) { this->syncVars = &syncVars; // Reset the plot frame. clearPlot(); chart->removeAllSeries(); linesSeries.clear(); // Create the widgets to display the SyncVars list. QLayoutItem *li; while((li = variablesListLayout->takeAt(0)) != nullptr) li->widget()->deleteLater(); syncVarsWidgets.clear(); for(int i=0; igetName()); 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::lowest()); - varsWidgets.valueSpinBox->setMaximum(std::numeric_limits::max()); - variablesListLayout->addWidget(varsWidgets.valueSpinBox, i, 2); + varsWidgets.valueLineEdit = new QLineEdit(); + variablesListLayout->addWidget(varsWidgets.valueLineEdit, i, 2); if(syncVars[i]->getAccess() == READONLY) - { - varsWidgets.valueSpinBox->setReadOnly(true); - varsWidgets.valueSpinBox->setButtonSymbols(QAbstractSpinBox::NoButtons); - } + varsWidgets.valueLineEdit->setReadOnly(true); else { - connect(varsWidgets.valueSpinBox, SIGNAL(editingFinished()), + connect(varsWidgets.valueLineEdit, 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); } // Acquire the value of all the readable SyncVars. for(SyncVarBase *sv : syncVars) { if(sv->getAccess() != WRITEONLY) hriBoard.readRemoteVar(sv); } } /** * @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()); + { + QString text = var->toString(); + syncVarsWidgets[varIndex].valueLineEdit->setText(text); + syncVarsWidgets[varIndex].valueLineEdit->setStyleSheet(""); + } } /** * @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() { + // Get the text field widget and the associated SyncVar. int varIndex = getVarIndexFromWidget((QWidget*)sender()); + QLineEdit *lineEdit = syncVarsWidgets[varIndex].valueLineEdit; SyncVarBase *sv = syncVars->at(varIndex); - sv->fromDouble(syncVarsWidgets[varIndex].valueSpinBox->value()); - hriBoard.writeRemoteVar(sv); + + // If the number is correct, send it to the board. Otherwise, color the + // text field background in red. + if(sv->fromString(lineEdit->text())) + { + lineEdit->setStyleSheet(""); + hriBoard.writeRemoteVar(sv); + } + else + lineEdit->setStyleSheet("background-color: #ffaaaa;"); } /** * @brief Updates the list of SyncVars to stream from the checkboxes' states. */ void MainWindow::onStreamCheckboxToggled() { // Setup the streaming. QList varsToStream; for(int i=0; iisChecked()) 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(); ui->pausePlotButton->setChecked(false); } /** * @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() { // Do not refresh the graph in "pause" mode. if(ui->pausePlotButton->isChecked()) return; // Get the variables scales. const QList &streamedVars = hriBoard.getStreamedVars(); QList plotScales; for(SyncVarBase* sv : streamedVars) { int varIndex = syncVars->indexOf(sv); if(varIndex >= 0) plotScales.append(syncVarsWidgets[varIndex].scaleSpinbox->value()); else return; } // Add the new points to the graph series. int decimationCounter = 0; while(!streamedVarsValuesBuffer.isEmpty()) { if(decimationCounter <= 0) { decimationCounter = ui->plotDecimSpinbox->value(); QList &values = streamedVarsValuesBuffer.first(); for(int i=0; iappend(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::max(); double max = std::numeric_limits::lowest(); for(QtCharts::QLineSeries *ls : linesSeries) { QVector 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; } diff --git a/CPP/HriPcController/mainwindow.h b/CPP/HriPcController/mainwindow.h index 81c7ae7..3204e38 100644 --- a/CPP/HriPcController/mainwindow.h +++ b/CPP/HriPcController/mainwindow.h @@ -1,100 +1,100 @@ /* * 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. */ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include "../HriBoardLib/hriboard.h" #include "../HriBoardLib/syncvar.h" namespace Ui { class MainWindow; } /** * @addtogroup HriPcController * @{ */ struct SyncVarWidgetsLine { QLabel *name; QPushButton *getButton, *setButton; - QDoubleSpinBox *valueSpinBox; + QLineEdit *valueLineEdit; QCheckBox *streamCheckbox; QDoubleSpinBox *scaleSpinbox; }; /** * @brief Qt MainWindow with widgets to interact with SyncVars. */ class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); public slots: void onVarsListReceived(const QList &syncVars); void onVarUpdated(SyncVarBase *var); void onGetButtonPressed(); void onSetButtonPressed(); void onStreamCheckboxToggled(); void clearPlot(); void onLogToFileCheckboxToggled(); void setLogfilesDirectory(); void updateGraph(); private: int getVarIndexFromWidget(QWidget* widget); Ui::MainWindow *ui; ///< Graphical user interface from the ui file. HriBoard hriBoard; ///< HRI board interface. const QList *syncVars; QList syncVarsWidgets; QSettings settings; QString logsDirectory; QGridLayout *variablesListLayout; QtCharts::QChart *chart; QList linesSeries; QTimer graphUpdateTimer; QLinkedList> streamedVarsValuesBuffer; }; /** * @} */ #endif