diff --git a/CPP/HriBoardLib/hriboard.cpp b/CPP/HriBoardLib/hriboard.cpp index be9e1b1..66db9e0 100644 --- a/CPP/HriBoardLib/hriboard.cpp +++ b/CPP/HriBoardLib/hriboard.cpp @@ -1,436 +1,436 @@ /* * 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 "hriboard.h" #include #include #include #include #include #include const int SYNCVAR_LIST_ITEM_SIZE = SYNCVAR_NAME_SIZE + 3; /** * @brief Constructor. */ HriBoard::HriBoard() { } /** * @brief Establish the link with the board. * Establish the link with the board, then stops the streaming and requests the * variables list. * @param comPortName serial port name, in the format "COM1" on Windows, or * "/dev/ttyO1" on UNIX. * @throws A runtime_error is thrown if the serial port could not be opened. */ void HriBoard::openLink(QString comPortName) { streamID = 0; // Setup the serial port. serial.setPortName(comPortName); serial.setBaudRate(UART_BAUDRATE); serial.setDataBits(QSerialPort::Data8); serial.setFlowControl(QSerialPort::NoFlowControl); serial.setParity(QSerialPort::NoParity); connect(&serial, SIGNAL(readyRead()), this, SLOT(onReceivedData())); qDebug() << "Opening the serial COM port..."; if(serial.open(QIODevice::ReadWrite)) qDebug() << "COM port opened successfully."; else throw std::runtime_error("Can't open the COM port."); // Stop the streaming, in case it was enabled. QByteArray ba; ba.append((char)0); sendPacket(PC_MESSAGE_SET_STREAMED_VAR, ba); // Request the variables list. sendPacket(PC_MESSAGE_GET_VARS_LIST); } /** * @brief Sets the SyncVars to stream. * @param varsToStream list of the SyncVars to stream. * @param streamedVarsValues pointer to array where the streaming values will be * appended. This parameter can be nullptr to not use this feature. */ void HriBoard::setStreamedVars(QList varsToStream, QLinkedList > *streamedVarsValues) { this->streamedVarsValues = streamedVarsValues; streamedVarsValues->clear(); // Copy the list of variables to stream, and compute the size of a streaming // packet. streamedVars.clear(); streamPacketSize = sizeof(quint8) + sizeof(quint32); // Stream ID + timestamp. for(SyncVarBase* sv : varsToStream) { streamedVars.append(syncVars[sv->getIndex()]); streamPacketSize += sv->getSize(); } // streamID++; QByteArray ba; ba.append((quint8)varsToStream.size()); ba.append(streamID); for(SyncVarBase* sv : varsToStream) { int varIndex = sv->getIndex(); ba.append((quint8)varIndex); } sendPacket(PC_MESSAGE_SET_STREAMED_VAR, ba); } /** * @brief Updates a SyncVar on the board with the value of the local one. * @param var the SyncVar to synchronize. */ void HriBoard::writeRemoteVar(SyncVarBase *var) { QByteArray ba; ba.append(var->getIndex()); ba.append(var->getData()); sendPacket(PC_MESSAGE_SET_VAR, ba); } /** * @brief Updates a local SyncVar with the value of the SyncVar on the board. * @param var the SyncVar to synchronize. */ void HriBoard::readRemoteVar(SyncVarBase *var) { QByteArray ba; ba.append(var->getIndex()); sendPacket(PC_MESSAGE_GET_VAR, ba); var->setOutOfDate(); } /** * @brief Makes a list of all the candidate serial ports. * @return a list of all the serial port that use the right USB-to-UART chip. */ QStringList HriBoard::getComPorts() { QList ports = QSerialPortInfo::availablePorts(); // Remove from the list all the serial COM ports that are not the CP210x // USB-to-serial chip. for(int i=ports.size()-1; i >= 0; i--) { if(!ports[i].description().contains("CP210")) ports.removeAt(i); } // When there are cu/tty pairs, remove the tty port. for(int i=ports.size()-1; i >= 0; i--) { if(ports[i].portName().startsWith("tty.")) { // Try to find the corresponding "cu" port. QString cuPortName = "cu." + ports[i].portName().remove(QRegExp("^tty.")); for(int j=0; japplicationName(), "Could not create the logfile."); return false; } logStream.setDevice(&logFile); // Write the file header. logStream << "timestamp [s];"; logStream.setRealNumberPrecision(10); for(int i=0; igetName(); if(i < syncVars.size()-1) logStream << ";"; } logStream << endl; return true; } /** * @brief Stop logging the streamed variables to a file. * @remark This function does nothing if the logging was not enabled. */ void HriBoard::stopLoggingToFile() { if(logFile.isOpen()) { logFile.close(); QFileInfo fileInfo(logFile.fileName()); QMessageBox::information(nullptr, qApp->applicationName(), "Logfile saved as " + fileInfo.absoluteFilePath()); } } /** * @brief Return the list of the streamed variables. * @return A const reference to the list of the streamed variables. */ const QList &HriBoard::getStreamedVars() { return streamedVars; } /** * @brief Interprets the received bytes, and reacts accordingly. */ void HriBoard::onReceivedData() { QByteArray rxData = serial.readAll(); //qDebug() << "RX:" << rxData; for(int i=0; i 0) rxDataBytesBuffer[dataBytesReady-1] = (firstHalfByte<<4) + (rxByte & 0xf); switch(rxCurrentMessageType) { case STM_MESSAGE_START_INFO: if(dataBytesReady == 0) { // Request variables list. sendPacket(PC_MESSAGE_GET_VARS_LIST); } break; case STM_MESSAGE_VAR: if(dataBytesReady >= 1) { quint8 varIndex = rxDataBytesBuffer[0]; if(dataBytesReady == 1 + syncVars[varIndex]->getSize()) { QByteArray ba((char*)&rxDataBytesBuffer[1], syncVars[varIndex]->getSize()); syncVars[varIndex]->setData(ba); emit syncVarUpdated(syncVars[varIndex]); } } break; case STM_MESSAGE_VARS_LIST: if(dataBytesReady >= 1) { quint8 nVars = rxDataBytesBuffer[0]; if(dataBytesReady == 1 + nVars * SYNCVAR_LIST_ITEM_SIZE) { // Clear the SyncVar array. for(SyncVarBase *sv : syncVars) delete sv; syncVars.clear(); // quint8* p = &rxDataBytesBuffer[1]; for(int i=0; i values; // Decode the timestamp. quint32 timestamp; memcpy(×tamp, &rxDataBytesBuffer[1], sizeof(timestamp)); double time = ((double)timestamp) / 1000000.0; values.append(time); // Decode the variables values. quint8 const* p = &rxDataBytesBuffer[5]; for(int i=0; igetSize()); streamedVars[i]->setData(value); p += streamedVars[i]->getSize(); values.append(streamedVars[i]->toDouble()); } if(streamedVarsValues != nullptr) streamedVarsValues->append(values); emit streamedSyncVarsUpdated(time, streamedVars); // Log to file, if enabled. if(logFile.isOpen()) { logStream << time << ";"; for(int i=0; iisUpToDate()) logStream << syncVars[i]->toDouble(); else logStream << 0; if(i < syncVars.size()-1) logStream << ";"; } logStream << endl; } } } break; case STM_MESSAGE_DEBUG_TEXT: if(dataBytesReady > 0 && rxDataBytesBuffer[dataBytesReady-1] == '\0') { qDebug() << QString((const char*)rxDataBytesBuffer); rxCurrentMessageType = 0; } break; default: // Ignore. break; } } } } /** * @brief Sends a communication packet to the board. * @param messageType the type of the message. * @param dataBytes the data that will be part of the packet, which is * type-specific. If omitted, there will be no data in the message, so it will * contain only the type. */ void HriBoard::sendPacket(comm_PcMessage messageType, QByteArray dataBytes) { QByteArray txBuffer; txBuffer.append((1<<7) + (uint8_t)messageType); for(int i=0; i> 4); // MSB. txBuffer.append(((quint8)dataBytes[i]) & 0xf); // LSB. } serial.write(txBuffer); serial.waitForBytesWritten(-1); } diff --git a/CPP/HriBoardLib/hriboard.h b/CPP/HriBoardLib/hriboard.h index b569c17..0810a17 100644 --- a/CPP/HriBoardLib/hriboard.h +++ b/CPP/HriBoardLib/hriboard.h @@ -1,169 +1,169 @@ /* * 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 HRIBOARD_H #define HRIBOARD_H #include #include #include #include #include #include #include #include "syncvar.h" /** @defgroup HriBoardLib HRI board * @brief Set of classes to easily interface with the HRI board. * * @addtogroup HriBoardLib * @{ */ /** * @brief Class to interface with a HRI board. * * After creating an instance of this class, connect your custom slot functions * to the signals * * Then, call openLink() to establish the communication link with the board. * If successfull, this object will automatically request the synchronized * variables list, and call the given slot function. You can then inspect this * link and find useful variables to stream or modify. To operate on these * SyncVars, you can either directly interact with the SyncVarBase (generic) * pointers of the list, or create typed pointer by doing dynamic_cast or * calling getVarHandle(). * * To set the value of a SyncVar on the board, call writeRemoteVar(). * To get the value of a SyncVar from the board, call readRemoteVar(), and * wait until SyncVar::isUpToDate() becomes true. * To continuously receive the value of several variables, setup the streaming * with setStreamedVars(). The given queue object will then be filled * continuously, as the values are received from the board. */ class HriBoard : public QObject { Q_OBJECT public: HriBoard(); void openLink(QString comPortName); void setStreamedVars(QList varsToStream, QLinkedList> *streamedVarsValues); void writeRemoteVar(SyncVarBase *var); /** * @brief Updates a SyncVar on the board with the value of the local one. * @param var the SyncVar to synchronize. * @param value the new value that both the local and remote SyncVar should * have. */ template void writeRemoteVar(SyncVarBase *var, T value) { SyncVar *svt = dynamic_cast*>(var); if(svt == nullptr) throw std::runtime_error("writeRemoteVar(): value and SyncVar type do not match."); else { svt->setLocalValue(value); writeRemoteVar(svt); } } void readRemoteVar(SyncVarBase* var); static QStringList getComPorts(); /** * @brief Gets a typed handle to a SyncVar, from its name. * @param name name of the SyncVar. * return The typed pointer to the SyncVar, or nullptr if the given type * did not match the SyncVar's one, or if a variable with the given name * was not found. */ template SyncVar* getVarHandle(QString name) { for(SyncVarBase *sv : syncVars) { if(sv->getName() == name) return dynamic_cast*>(sv); } return nullptr; // Variable not found. } - bool startLoggingToFile(); + bool startLoggingToFile(QString directory); void stopLoggingToFile(); const QList &getStreamedVars(); public slots: void onReceivedData(); signals: /** * @brief Signal emitted when the syncVars list has been received. * @param syncVars list of all the SyncVars of the board. */ void syncVarsListReceived(const QList &syncVars); /** * @brief Signal emitted when a single variable value was received. * @param var pointer to the SyncVar that was just updated. */ void syncVarUpdated(SyncVarBase* var); /** * @brief Signal emitted when a streaming packet was received. * @param time board timestamp of the streamed variables values snapshot. * @param streamedVars list of the streamed variables, that have just been * updated. */ void streamedSyncVarsUpdated(double time, const QList& streamedVars); protected: void sendPacket(comm_PcMessage messageType, QByteArray dataBytes = QByteArray()); private: QSerialPort serial; ///< Serial port to communicate with the board. QList syncVars; ///< SyncVars list. QList streamedVars; ///< List of pointers to the SyncVars streamed by the board. QByteArray rxBuffer; ///< Byte buffer used for receiving bytes from the board. int rxCurrentMessageType; ///< Type of the board message being interpreted. int rxBytesCount; ///< Number of bytes of the board message being interpreted. quint8 firstHalfByte; ///< First byte of a data byte. quint8 rxDataBytesBuffer[1024]; ///< Temporary buffer to store the data bytes (made of two received bytes) of the message being interpreted. int streamID; ///< Identifier of the current streaming configuration, to check if the received streaming packet correspond to the request. int streamPacketSize; ///< Expected size of a streaming packet [byte]. QFile logFile; ///< CSV file to store the states of the streamed variables. QTextStream logStream; ///< Text stream interface for the logFile. QLinkedList> *streamedVarsValues; ///< Pointer to a user array, to be appended with the values of the streamed variables. }; /** * @} */ #endif diff --git a/CPP/HriPcController/mainwindow.cpp b/CPP/HriPcController/mainwindow.cpp index 9af4a4b..fb31a96 100644 --- a/CPP/HriPcController/mainwindow.cpp +++ b/CPP/HriPcController/mainwindow.cpp @@ -1,402 +1,452 @@ /* * 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_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. - hriBoard.openLink(comPortName); + 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 window size. + // 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 &syncVars) { this->syncVars = &syncVars; 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); 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 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(); } /** * @brief Starts or stops the streamed variables logging to file. */ void MainWindow::onLogToFileCheckboxToggled() { if(ui->logToFileCheckbox->isChecked()) { - if(!hriBoard.startLoggingToFile()) + 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 &streamedVars = hriBoard.getStreamedVars(); QList 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 &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 10b06ab..81c7ae7 100644 --- a/CPP/HriPcController/mainwindow.h +++ b/CPP/HriPcController/mainwindow.h @@ -1,98 +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; 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 diff --git a/CPP/HriPcController/mainwindow.ui b/CPP/HriPcController/mainwindow.ui index 9358dba..9571e82 100644 --- a/CPP/HriPcController/mainwindow.ui +++ b/CPP/HriPcController/mainwindow.ui @@ -1,141 +1,160 @@ MainWindow 0 0 648 649 0 0 HRI PC Controller + + + 0 + 0 + + Log to CSV file Live graph 10 1000000 1000 Number of points to show: Decimation: 1 100 10 Clear plot - + + + + + 0 + 0 + + + + Change location... + + + + Variables list 0 0 Qt::ScrollBarAlwaysOff QAbstractScrollArea::AdjustIgnored true 0 0 - 612 - 274 + 592 + 208 QtCharts::QChartView QGraphicsView
QtCharts/QChartView
diff --git a/CPP/doc/cpp_interface_documentation.html b/CPP/doc/cpp_interface_documentation.html deleted file mode 100644 index a889182..0000000 --- a/CPP/doc/cpp_interface_documentation.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - -

If the documentation is not loading, click here.

- \ No newline at end of file