diff --git a/.gitignore b/.gitignore index c2e8b32..b4152ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ -CPP/*.pro.* -CPP/build* +CPP/*/*.pro.* +CPP/build-* +CPP/*/build-* CPP/doc/html/ Firmware/carteHRI.uvguix.* Firmware/doc/html/ -Firmware/lst/ -Firmware/output/ +Firmware/Debug/ +Firmware/Release/ +Firmware/.settings/language.settings.xml MATLAB/logs/* \ No newline at end of file diff --git a/CPP/hriboard.cpp b/CPP/HriBoardLib/hriboard.cpp similarity index 64% rename from CPP/hriboard.cpp rename to CPP/HriBoardLib/hriboard.cpp index bc2562d..f8039bc 100644 --- a/CPP/hriboard.cpp +++ b/CPP/HriBoardLib/hriboard.cpp @@ -1,307 +1,398 @@ #include "hriboard.h" #include #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) +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(SyncVarPointerBase* svpb : varsToStream) + for(SyncVarBase* sv : varsToStream) { - streamedVars.append(&syncVars[svpb->getVar()->getIndex()]); - streamPacketSize += svpb->getVar()->getSize(); + streamedVars.append(syncVars[sv->getIndex()]); + streamPacketSize += sv->getSize(); } // streamID++; QByteArray ba; ba.append((quint8)varsToStream.size()); ba.append(streamID); - for(SyncVarPointerBase* svpb : varsToStream) + for(SyncVarBase* sv : varsToStream) { - int varIndex = svpb->getVar()->getIndex(); + int varIndex = sv->getIndex(); ba.append((quint8)varIndex); } sendPacket(PC_MESSAGE_SET_STREAMED_VAR, ba); } /** - * @brief Associates a SyncVarPointer to an actual SyncVar, from its name. - * @param svp the SyncVarPointer to the SyncVar. - * @param name the name of the SyncVar to associate to the pointer. - * @return true if a variable with the given name was found, false otherwise. - */ -bool HriBoard::associate(SyncVarPointerBase &svp, QString name) -{ - for(SyncVar &sv : syncVars) - { - if(sv.getName() == name && sv.getType() == svp.getType()) - { - svp.associate(this, &sv); - return true; - } - } - - svp.associate(this, nullptr); - return false; -} - -/** - * @brief Updates a SyncVar on the board with the value of the local SyncVar. + * @brief Updates a SyncVar on the board with the value of the local one. * @param var the SyncVar to synchronize. */ -void HriBoard::writeRemoteVar(SyncVar *var) +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(SyncVar *var) +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(); for(int i=ports.size()-1; i >= 0; i--) { // Remove from the list all the serial COM ports that are not the CP210x // USB-to-serial chip. if(!ports[i].description().contains("CP210x")) ports.removeAt(i); } // Make a list of the COM ports names. QStringList portsNames; for(QSerialPortInfo comInfo : ports) portsNames << comInfo.portName(); return portsNames; } +/** + * @brief Start logging the streamed variables to a CSV file. + * @return true if the logfile could be created, false otherwise. + */ +bool HriBoard::startLoggingToFile() +{ + stopLoggingToFile(); + + QDateTime now = QDateTime::currentDateTime(); + logFile.setFileName("logs/log_" + now.toString("yyyy-MM-dd_hh-mm-ss") + + ".csv"); + + if(QDir().mkpath("logs/") && + !logFile.open(QFile::WriteOnly | QFile::Truncate)) + { + QMessageBox::warning(nullptr, qApp->applicationName(), + "Could not create the logfile."); + return false; + } + + logStream.setDevice(&logFile); + + // Write the file header. + logStream << "Timestamp [s];"; + + 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()) + if(dataBytesReady == 1 + syncVars[varIndex]->getSize()) { QByteArray ba((char*)&rxDataBytesBuffer[1], - syncVars[varIndex].getSize()); - syncVars[varIndex].setData(ba); + 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()); } - emit syncVarsUpdated(); + 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 new file mode 100644 index 0000000..317cfaa --- /dev/null +++ b/CPP/HriBoardLib/hriboard.h @@ -0,0 +1,153 @@ +#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(); + 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/HriBoardLib/syncvar.cpp b/CPP/HriBoardLib/syncvar.cpp new file mode 100644 index 0000000..7468110 --- /dev/null +++ b/CPP/HriBoardLib/syncvar.cpp @@ -0,0 +1,130 @@ +#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). + */ +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 new file mode 100644 index 0000000..30c208a --- /dev/null +++ b/CPP/HriBoardLib/syncvar.h @@ -0,0 +1,92 @@ +#ifndef SYNCVAR_H +#define SYNCVAR_H + +#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(); + + virtual double toDouble() const = 0; + virtual void fromDouble(double value) = 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; + } + + void fromDouble(double value) override + { + this->value = (T)value; + } + +protected: + T value; +}; + +SyncVarBase* makeSyncVar(VarType type, int index, QString name, + VarAccess access); + +/** + * @} + */ + +#endif // SYNCVAR_H diff --git a/CPP/HriExampleProgram/HriExampleProgram.pro b/CPP/HriExampleProgram/HriExampleProgram.pro new file mode 100644 index 0000000..82646f5 --- /dev/null +++ b/CPP/HriExampleProgram/HriExampleProgram.pro @@ -0,0 +1,23 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-03-05T16:55:17 +# +#------------------------------------------------- + +QT += core gui serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = HriExampleProgram +TEMPLATE = app + +SOURCES += main.cpp\ + mainwindow.cpp \ + ../HriBoardLib/hriboard.cpp \ + ../HriBoardLib/syncvar.cpp + +HEADERS += mainwindow.h \ + ../HriBoardLib/hriboard.h \ + ../HriBoardLib/syncvar.h + +FORMS += mainwindow.ui diff --git a/CPP/main.cpp b/CPP/HriExampleProgram/main.cpp similarity index 51% copy from CPP/main.cpp copy to CPP/HriExampleProgram/main.cpp index 6a2f831..3303d40 100644 --- a/CPP/main.cpp +++ b/CPP/HriExampleProgram/main.cpp @@ -1,38 +1,36 @@ #include "mainwindow.h" #include -/** @defgroup DemoApp HRI C++ interface demo application - * @brief A simple demo program demonstrating the capabilities of the C++ - * interface of the HRI boards. +/** @defgroup HriExampleProgram Example app to interact with the HRI board + * @brief This interface demonstrates how to interact with the HRI. It shows + * how to visually display the encoder position and the Hall voltage, and how + * to control the LED intensity. * - * This demo program shows a window allows the user to set an arbitrary motor - * torque (on-demand SyncVar read/write), and to display continuously the Hall - * sensor voltage (SyncVar streaming). - * - * @addtogroup DemoApp + * @addtogroup HriExampleProgram * @{ */ /** * @brief Main function. * @param argc number of arguments. * @param argv arguments array. * @return 0 if the program exited normally, the error code otherwise. */ + int main(int argc, char *argv[]) { QApplication a(argc, argv); - a.setApplicationName("HRI_PC_Controller"); + a.setApplicationName("HRI_example_program"); a.setOrganizationDomain("lsro.epfl.ch"); a.setOrganizationName("LSRO"); MainWindow w; w.show(); return a.exec(); } /** * @} */ diff --git a/CPP/HriExampleProgram/mainwindow.cpp b/CPP/HriExampleProgram/mainwindow.cpp new file mode 100644 index 0000000..2cfd4c8 --- /dev/null +++ b/CPP/HriExampleProgram/mainwindow.cpp @@ -0,0 +1,139 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +#define UPDATE_PERIOD 50 ///< Update period of the data display [ms]. + +/** + * @brief Constructor. + * @param parent parent widget. + */ +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // 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]; + } + + // Establish the link with the HRI board. + try + { + hriBoard.openLink(comPortName); + } + catch(std::runtime_error) + { + QMessageBox::critical(this, qApp->applicationName(), + "Could not open the COM port."); + } + + connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList&)), + this, SLOT(onVarsListReceived(const QList&))); + + // Start the user interface update timer. + updateTimer.setInterval(UPDATE_PERIOD); + updateTimer.setSingleShot(false); + connect(&updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplay())); + + // Setup the action when the LED intensity is changed on the GUI. + connect(ui->ledSlider, SIGNAL(valueChanged(int)), + this, SLOT(setLedIntensity())); +} + +/** + * @brief Destructor. + */ +MainWindow::~MainWindow() +{ + delete ui; +} + +/** + * @brief Gets the SyncVars handles from the list, and setup the data streaming. + * @param syncVars syncVars list. + */ +void MainWindow::onVarsListReceived(const QList &syncVars) +{ + // List all the variables. + for(SyncVarBase *sv : syncVars) + qDebug() << sv->getName(); + + // Get a convenient handle to the desired SyncVars. + encoderPosition = hriBoard.getVarHandle("actual_position [deg]"); + hallVoltage = hriBoard.getVarHandle("hall_voltage [V]"); + ledIntensity = hriBoard.getVarHandle("led_0 [0.0-1.0]"); + + // If at least one SyncVar could not be found on the board's list, abort. + if(encoderPosition == nullptr || ledIntensity == nullptr) + { + QMessageBox::critical(this, qApp->applicationName(), + "A SyncVar could not be found on the board."); + qApp->exit(); + } + + // Setup the streaming to get the continuously get the encoder position. + QList varsToStream; + varsToStream.append(encoderPosition); + varsToStream.append(hallVoltage); + hriBoard.setStreamedVars(varsToStream, &streamedVarsValuesBuffer); +} + +/** + * @brief Updates the user interface widgets, with the latest variables values. + */ +void MainWindow::updateDisplay() +{ + if(!streamedVarsValuesBuffer.isEmpty()) + { + // Get only the latest set of values and discard the rest, since we are + // not interested in the older ones (for this simple example). + // An other solution would be to read directly the SynVars (e.g. + // encoderPosition->getLocalValue(). + QList valuesSet = streamedVarsValuesBuffer.last(); + streamedVarsValuesBuffer.clear(); + + // The first value is the timestamp, and the following are the values of + // the streamed variables. + ui->encoderProgressbar->setValue((int)valuesSet[1]); + ui->hallVoltageLabel->setText(QString::number(valuesSet[2]) + " V"); + } +} + +/** + * @brief Set the intensity of the first LED (LED 0), from the slider position. + */ +void MainWindow::setLedIntensity() +{ + float intensityValue = ((double)ui->ledSlider->value()) / 100.0; + + hriBoard.writeRemoteVar(ledIntensity, intensityValue); +} diff --git a/CPP/HriExampleProgram/mainwindow.h b/CPP/HriExampleProgram/mainwindow.h new file mode 100644 index 0000000..4f8c281 --- /dev/null +++ b/CPP/HriExampleProgram/mainwindow.h @@ -0,0 +1,44 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include + +#include "../HriBoardLib/hriboard.h" + +namespace Ui { +class MainWindow; +} + +/** + * @addtogroup HriExampleProgram + * @{ + */ + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +public slots: + void onVarsListReceived(const QList &syncVars); + void updateDisplay(); + void setLedIntensity(); + +private: + Ui::MainWindow *ui; ///< Graphical user interface handle. + + HriBoard hriBoard; ///< HRI board interface. + QLinkedList> streamedVarsValuesBuffer; ///< Queue to receive the all the values of the streamed variables. + SyncVar *encoderPosition, *hallVoltage, *ledIntensity; ///< Handle for the SyncVars used in this example. + QTimer updateTimer; ///< Timer to update periodically the data display. +}; + +/** + * @} + */ + +#endif diff --git a/CPP/mainwindow.ui b/CPP/HriExampleProgram/mainwindow.ui similarity index 62% rename from CPP/mainwindow.ui rename to CPP/HriExampleProgram/mainwindow.ui index 433935a..8dde667 100644 --- a/CPP/mainwindow.ui +++ b/CPP/HriExampleProgram/mainwindow.ui @@ -1,89 +1,94 @@ MainWindow 0 0 - 408 - 236 + 334 + 127 - HRI GUI + HRI example program - - + + - Hall voltage: + LED 0 intensity: - - + + + + 100 + + + Qt::Horizontal + + + + + - Offset torque [N.m]: + Encoder position: - + - 0 + -40 - 100 + 40 0 - - false - - - false - - %v deg + %v° Qt::Vertical 20 40 - - - - 3 - - - -0.050000000000000 + + + + Hall voltage: - - 0.050000000000000 + + + + + + 0 V - - 0.001000000000000 + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/CPP/HRI_PC_Controller.pro b/CPP/HriPcController/HriPcController.pro similarity index 59% rename from CPP/HRI_PC_Controller.pro rename to CPP/HriPcController/HriPcController.pro index 3ea735b..f75207b 100644 --- a/CPP/HRI_PC_Controller.pro +++ b/CPP/HriPcController/HriPcController.pro @@ -1,27 +1,27 @@ #------------------------------------------------- # # Project created by QtCreator 2015-02-18T17:52:49 # #------------------------------------------------- -QT += core gui serialport printsupport +QT += core gui serialport charts CONFIG += c++11 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -TARGET = HRI_PC_Controller +TARGET = HriPcController TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ - hriboard.cpp \ - syncvar.cpp + ../HriBoardLib/hriboard.cpp \ + ../HriBoardLib/syncvar.cpp HEADERS += mainwindow.h \ - ../Firmware/src/definitions.h \ - hriboard.h \ - syncvar.h + ../../Firmware/src/definitions.h \ + ../HriBoardLib/hriboard.h \ + ../HriBoardLib/syncvar.h FORMS += mainwindow.ui diff --git a/CPP/main.cpp b/CPP/HriPcController/main.cpp similarity index 56% rename from CPP/main.cpp rename to CPP/HriPcController/main.cpp index 6a2f831..1f04086 100644 --- a/CPP/main.cpp +++ b/CPP/HriPcController/main.cpp @@ -1,38 +1,34 @@ #include "mainwindow.h" #include -/** @defgroup DemoApp HRI C++ interface demo application - * @brief A simple demo program demonstrating the capabilities of the C++ - * interface of the HRI boards. +/** @defgroup HriPcController C++ control interface for the HRI board + * @brief This interface can interact with a HRI board, by listing, reading, + * writing, or plotting its shared variables. * - * This demo program shows a window allows the user to set an arbitrary motor - * torque (on-demand SyncVar read/write), and to display continuously the Hall - * sensor voltage (SyncVar streaming). - * - * @addtogroup DemoApp + * @addtogroup HriPcController * @{ */ /** * @brief Main function. * @param argc number of arguments. * @param argv arguments array. * @return 0 if the program exited normally, the error code otherwise. */ int main(int argc, char *argv[]) { QApplication a(argc, argv); a.setApplicationName("HRI_PC_Controller"); a.setOrganizationDomain("lsro.epfl.ch"); a.setOrganizationName("LSRO"); MainWindow w; w.show(); return a.exec(); } /** * @} */ diff --git a/CPP/HriPcController/mainwindow.cpp b/CPP/HriPcController/mainwindow.cpp new file mode 100644 index 0000000..88b20ec --- /dev/null +++ b/CPP/HriPcController/mainwindow.cpp @@ -0,0 +1,356 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include + +#define GRAPH_UPDATE_PERIOD 50 ///< Plot window refresh period [ms]. + +/** + * @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); + + 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())); + + // + variablesListLayout = nullptr; + syncVars = nullptr; + + // 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->logToFileCheckbox, SIGNAL(toggled(bool)), + this, SLOT(onLogToFileCheckboxToggled())); + + // Establish the link with the HRI board. + hriBoard.openLink(comPortName); + connect(&hriBoard, SIGNAL(syncVarsListReceived(const QList&)), + this, SLOT(onVarsListReceived(const QList&))); + connect(&hriBoard, SIGNAL(syncVarUpdated(SyncVarBase*)), + this, SLOT(onVarUpdated(SyncVarBase*))); +} + +/** + * @brief Destructor. + */ +MainWindow::~MainWindow() +{ + 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; + + // Populate the SyncVars list in the GUI. + QGridLayout* newLayout = new QGridLayout; + newLayout->setAlignment(Qt::AlignTop); + delete ui->scrollWidget->layout(); + ui->scrollWidget->setLayout(newLayout); + + if(variablesListLayout != nullptr) + variablesListLayout->deleteLater(); + + variablesListLayout = newLayout; + + 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->setReadOnly(syncVars[i]->getAccess() == READONLY); + variablesListLayout->addWidget(varsWidgets.valueSpinBox, i, 2); + + if(syncVars[i]->getAccess() != READONLY) + 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); + } +} + +/** + * @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 Starts or stops the streamed variables logging to file. + */ +void MainWindow::onLogToFileCheckboxToggled() +{ + if(ui->logToFileCheckbox->isChecked()) + { + if(!hriBoard.startLoggingToFile()) + { + QSignalBlocker signalBlocker(ui->logToFileCheckbox); + ui->logToFileCheckbox->setChecked(false); + } + } + else + hriBoard.stopLoggingToFile(); +} + +/** + * @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); + 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 new file mode 100644 index 0000000..28fe61e --- /dev/null +++ b/CPP/HriPcController/mainwindow.h @@ -0,0 +1,79 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#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 onLogToFileCheckboxToggled(); + + 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; + + QGridLayout *variablesListLayout; + QtCharts::QChart *chart; + QList linesSeries; + QTimer graphUpdateTimer; + QLinkedList> streamedVarsValuesBuffer; +}; + +/** + * @} + */ + +#endif diff --git a/CPP/HriPcController/mainwindow.ui b/CPP/HriPcController/mainwindow.ui new file mode 100644 index 0000000..f184873 --- /dev/null +++ b/CPP/HriPcController/mainwindow.ui @@ -0,0 +1,134 @@ + + + MainWindow + + + + 0 + 0 + 453 + 543 + + + + + 0 + 0 + + + + HRI PC Controller + + + + + + + Log to CSV file + + + + + + + Live graph + + + + + + Number of points to show: + + + + + + + Decimation: + + + + + + + 10 + + + 1000000 + + + 1000 + + + + + + + 1 + + + 100 + + + 10 + + + + + + + + + + + + + Variables list + + + + + + + 0 + 0 + + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 417 + 201 + + + + + + + + + + + + + + + QtCharts::QChartView + QGraphicsView +
QtCharts/QChartView
+
+
+ + +
diff --git a/CPP/doc/cpp_documentation.html b/CPP/doc/cpp_documentation.html new file mode 100644 index 0000000..a889182 --- /dev/null +++ b/CPP/doc/cpp_documentation.html @@ -0,0 +1,7 @@ + + + + + +

If the documentation is not loading, click here.

+ \ No newline at end of file diff --git a/CPP/doc/main.dox b/CPP/doc/main.dox index 57b1833..37982b7 100644 --- a/CPP/doc/main.dox +++ b/CPP/doc/main.dox @@ -1,22 +1,24 @@ /** * @mainpage HRI C++ interface documentation * * @section intro_sec Introduction * This C++ interface is designed to control a HRI board through the serial * port. Unlike the MATLAB interface, it runs much faster, and allows complex * programs to interact with the HRI board. * It is using the Qt 5 library, which allows easy and clean code, while * while enabling high-performance graphics. * * The given interface essentially allows to interact with the board using the * synchronised variables system ("SyncVar"), for single read/write or * continuous data streaming. * - * To make a program using this interface, you have two solutions: - * - Modify the given test project "HRI_PC_Controller.pro". - * - Create a new Qt 5 project, then include hriboard.* and syncvar.*. + * Two programs are included : + * - HRI_PC_Controller is a ready-to-use application that allows to list, ready, + * write, plot and log the SyncVars of the board. + * - HriExampleProgram is a simple example that shows how to make a user + * interface that can interact with the HRI board. * * @section support_sec Maintenance and technical support - * Please report any bug or suggestions to Romain Baud (romain.baud@epfl.ch). + * Please report any bug or suggestion to Romain Baud (romain.baud@epfl.ch). * Technical support is also available. */ diff --git a/CPP/hriboard.h b/CPP/hriboard.h deleted file mode 100644 index 8554f18..0000000 --- a/CPP/hriboard.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef HRIBOARD_H -#define HRIBOARD_H - -#include -#include -#include - -#include "syncvar.h" - -/** @defgroup HriBoard HRI board - * @brief Interface to the HRI board. - * - * @addtogroup HriBoard - * @{ - */ - -/** - * @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, create one SyncVarPointer per SyncVar, and associate them to an - * actual SyncVar with the associate() method. - * - */ -class HriBoard : public QObject -{ - Q_OBJECT - -public: - HriBoard(); - void openLink(QString comPortName); - void setStreamedVars(QList varsToStream); - - bool associate(SyncVarPointerBase& svp, QString name); - void writeRemoteVar(SyncVar* var); - void readRemoteVar(SyncVar* var); - - static QStringList getComPorts(); - -public slots: - void onReceivedData(); - -signals: - void syncVarsListReceived(const QList &syncVars); - void syncVarsUpdated(); - -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]. -}; - -/** - * @} - */ - -#endif diff --git a/CPP/mainwindow.cpp b/CPP/mainwindow.cpp deleted file mode 100644 index 2290822..0000000 --- a/CPP/mainwindow.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include -#include -#include -#include - -#include "hriboard.h" - -/** - * @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); - - // Connect the GUI signals to the slots. - connect(ui->torqueSpin, SIGNAL(editingFinished()), - this, SLOT(setMotorTorque())); - - // 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, "HRI2_PC_Controller", - "Serial port:", ports, 0, - false, &ok); - - int portIndex = ports.indexOf(portChoice); - - if(portIndex == -1 || !ok) - exit(0); - - comPortName = ports[portIndex]; - } - - // Establish the link with the HRI board. - hriBoard.openLink(comPortName); - connect(&hriBoard, SIGNAL(syncVarsListReceived(QList)), - this, SLOT(onVarsListReceived(QList))); - connect(&hriBoard, SIGNAL(syncVarsUpdated()), this, SLOT(onVarsUpdated())); -} - -/** - * @brief Destructor. - */ -MainWindow::~MainWindow() -{ - 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) -{ - // Display the list of all the SyncVars. - qDebug() << "SyncVars list:"; - - for(const SyncVar &sv : syncVars) - qDebug() << sv.getName(); - - // Get the relevant variables from the list. - hriBoard.associate(hallVoltage, "hall_voltage [V]"); - hriBoard.associate(motorTorque, "motor_torque [N.m]"); - - if(!hallVoltage.isValid() || !motorTorque.isValid()) - { - qDebug() << "The expected SyncVars were not found in the list." << endl; - return; - } - - // Start the streaming. - hriBoard.setStreamedVars({&hallVoltage, &motorTorque}); -} - -/** - * @brief Updates the GUI indicators with the updated SyncVars values. - * @remark This slot function is called automatically by the HriBoard object, - * every time a streaming packet is received. - */ -void MainWindow::onVarsUpdated() -{ - // Update the UI widgets. - ui->hallVoltageBar->setValue((int)(*hallVoltage/3.3f*100.0f)); -} - -/** - * @brief Sets the motor torque, from the GUI control. - */ -void MainWindow::setMotorTorque() -{ - // Get the UI control value, and affect it to the SyncVar. - *motorTorque = (float)ui->torqueSpin->value(); - motorTorque.writeValue(); -} diff --git a/CPP/mainwindow.h b/CPP/mainwindow.h deleted file mode 100644 index 2eff315..0000000 --- a/CPP/mainwindow.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include - -#include "hriboard.h" -#include "syncvar.h" - -namespace Ui { -class MainWindow; -} - -/** - * @addtogroup DemoApp - * @{ - */ - -/** - * @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 onVarsUpdated(); - - void setMotorTorque(); - -private: - Ui::MainWindow *ui; ///< Graphical user interface from the ui file. - HriBoard hriBoard; ///< HRI board interface. - - SyncVarPointer hallVoltage; ///< SyncVar to access the Hall sensor voltage [V]. - SyncVarPointer motorTorque; ///< SyncVar to access the motor torque [N.m]. -}; - -/** - * @} - */ - -#endif diff --git a/CPP/syncvar.cpp b/CPP/syncvar.cpp deleted file mode 100644 index 59f7f56..0000000 --- a/CPP/syncvar.cpp +++ /dev/null @@ -1,192 +0,0 @@ -#include "syncvar.h" -#include "hriboard.h" - -#include - -VarType getType(const bool*) { return VarType::BOOL; } -VarType getType(const uint8_t*) { return VarType::UINT8; } -VarType getType(const int8_t*) { return VarType::INT8; } -VarType getType(const uint16_t*) { return VarType::UINT16; } -VarType getType(const int16_t*) { return VarType::INT16; } -VarType getType(const uint32_t*) { return VarType::UINT32; } -VarType getType(const int32_t*) { return VarType::INT32; } -VarType getType(const uint64_t*) { return VarType::UINT64; } -VarType getType(const int64_t*) { return VarType::INT64; } -VarType getType(const float*) { return VarType::FLOAT32; } -VarType getType(const double*) { return VarType::FLOAT64; } - -const int VAR_SIZE[] = { 1, 1, 1, 2, 2, 4, 4, 8, 8, 4, 8 }; - -/** - * @brief Constructor - * @param index index in the SyncVars list. - * @param name name of the SyncVar. - * @param type type of the SyncVar value. - * @param access access right of the SyncVar. - */ -SyncVar::SyncVar(int index, QString name, VarType type, VarAccess access) : - index(index), name(name), type(type), access(access) -{ - data.resize(VAR_SIZE[(int)type]); - upToDate = false; -} - -/** - * @brief Gets the index in the SyncVars list. - * @return the index. - */ -int SyncVar::getIndex() const -{ - return index; -} - -/** - * @brief Gets the name of the SyncVar. - * @return the name. - */ -QString SyncVar::getName() const -{ - return name; -} - -/** - * @brief Gets the size of the SyncVar value. - * @return the value size [byte]. - */ -int SyncVar::getSize() const -{ - return data.size(); -} - -/** - * @brief Gets the variable type. - * @return the variable type. - */ -VarType SyncVar::getType() const -{ - return type; -} - -/** - * @brief Gets the SyncVar access rights. - * @return the SyncVar access rights. - */ -VarAccess SyncVar::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 SyncVar::getData() const -{ - return data; -} - -/** - * @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). - */ -void SyncVar::setData(QByteArray newData) -{ - if(newData.size() != data.size()) - throw std::runtime_error("SyncVar::setData(): size do not match."); - - data = newData; - 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 SyncVar::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 SyncVar::setOutOfDate() -{ - upToDate = false; -} - -/** - * @brief Constructor. - */ -SyncVarPointerBase::SyncVarPointerBase() -{ - syncVar = nullptr; - hriBoard = nullptr; -} - -/** - * @brief Associates a SyncVarPointer to a SyncVar. - * @param hriBoard the HRI board object. - * @param syncVar the SyncVar to associate to the SyncVarPointer. - * @remark This method should only be called by HriBoard. - */ -void SyncVarPointerBase::associate(HriBoard *hriBoard, - SyncVar *syncVar) -{ - this->hriBoard = hriBoard; - this->syncVar = syncVar; -} - -/** - * @brief Gets if the SyncVarPointer can be "dereferenced". - * @return true if the pointer has been associated with a SyncVar, false - * otherwise. - */ -bool SyncVarPointerBase::isValid() const -{ - return hriBoard != nullptr; -} - -/** - * @brief Gets the SyncVar associated to the SyncVarPointer. - * @return a pointer to the associated SyncVar, or nullptr if it was not - * associated yet. - */ -SyncVar *SyncVarPointerBase::getVar() const -{ - return syncVar; -} - -/** - * @brief Updates the SyncVar value from the one on the board. - * This method requests the HRI board to send the value of the SyncVar. The - * local value will be updated later, when the "SyncVar value packet" will be - * received. - * To check if the value has actually been received, call isUpToDate(). - * @remark This method does nothing if the SyncVar is WRITEONLY. - */ -void SyncVarPointerBase::readValue() -{ - if(syncVar->getAccess() != WRITEONLY) - { - syncVar->setOutOfDate(); - hriBoard->readRemoteVar(syncVar); - } -} - -/** - * @brief Updates the SyncVar value on the board from local value. - * @remark This method does nothing if the SyncVar is READONLY. - */ -void SyncVarPointerBase::writeValue() -{ - if(syncVar->getAccess() != READONLY) - hriBoard->writeRemoteVar(syncVar); -} diff --git a/CPP/syncvar.h b/CPP/syncvar.h deleted file mode 100644 index bfdf689..0000000 --- a/CPP/syncvar.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef SYNCVAR_H -#define SYNCVAR_H - -#include -#include -#include - -#include "../Firmware/src/definitions.h" -typedef comm_VarType VarType; -typedef comm_VarAccess VarAccess; - -VarType getType(const bool*); -VarType getType(const uint8_t*); -VarType getType(const int8_t*); -VarType getType(const uint16_t*); -VarType getType(const int16_t*); -VarType getType(const uint32_t*); -VarType getType(const int32_t*); -VarType getType(const uint64_t*); -VarType getType(const int64_t*); -VarType getType(const float*); -VarType getType(const double*); - -template -class SyncVarPointer; - -template -T& operator*(SyncVarPointer &svp); - -class HriBoard; - -/** @defgroup SyncVar SyncVar - * @brief Set of classes to handle SyncVar operations conveniently. - * - * First, get the list of the SyncVars from the HRI board, by creating a - * HriBoard object and calling openLink(). The given slot function will be - * called when the SyncVars list will be received. Then, it is possible to - * create and associate SyncVarPointers to manipulate these SyncVars. - * - * A local SyncVar can be accessed from a SyncVarPointer, simply using the * - * operator. To actually synchronize this value with the one of the board, call - * writeRemoteVar() or readRemoteVar(). - * - * @addtogroup SyncVar - * @{ - */ - -/** - * @brief Variable that can be synchronized with its HRI board counterpart. - */ -class SyncVar -{ - template - friend T& operator*(SyncVarPointer &svp); - -public: - SyncVar(int index, QString name, - VarType type, VarAccess access); - - int getIndex() const; - QString getName() const; - int getSize() const; - VarType getType() const; - VarAccess getAccess() const; - QByteArray getData() const; - void setData(QByteArray newData); - - bool isUpToDate() const; - void setOutOfDate(); - -private: - QByteArray data; ///< SyncVar value, stored as raw bytes. - int index; ///< Index of the SyncVar in the list. - QString name; ///< Name describing the SyncVar. - VarType type; ///< Type of variable. - VarAccess access; ///< Access rights of the variable. - bool upToDate; ///< Indicates whether the local value is up-to-date or not. -}; - -/** - * @brief Generic version of SyncVarPointer. - * @remark This generic version of SyncVarPointer cannot be instanced, use - * SyncVarPointer instead. - */ -class SyncVarPointerBase -{ -public: - SyncVarPointerBase(); - - void associate(HriBoard *hriBoard, SyncVar *syncVar); - bool isValid() const; - - SyncVar* getVar() const; - - void readValue(); - void writeValue(); - - virtual VarType getType() = 0; - -protected: - HriBoard* hriBoard; ///< Pointer to the HRI board object. - SyncVar* syncVar; ///< Pointer to the associated SyncVar. - -}; - -/** - * @brief Typed handle to a SyncVar, for easy value manipulation. - * Using the value of the SyncVars is tedious, since their data array must be - * casted. SyncVarPointer is a typed object to handle easily a SyncVar value. - * - * First, create SyncVarPointer of the same type that the SyncVar to be - * manipulated. Then, associate it to the desired variable with associate(). - * - * To access the SyncVar value, just use the * operator, like a pointer. - * To synchronize the local SyncVar value with the one on the board, call - * readValue() or writeValue(). - */ -template -class SyncVarPointer : public SyncVarPointerBase -{ -protected: - /** - * @brief Gets the type of the SyncVarPointer. - * @return the type of the SyncVar that can be associated to this pointer. - */ - VarType getType() - { - return ::getType((T*)nullptr); - } -}; - -/** - * @brief Gets a typed reference to the SyncVar data, from a SyncVarPointer. - * @param svp the SyncVarPointer to get the SyncVar value from. - * @return a reference to the value of the SyncVar. - */ -template -T& operator*(SyncVarPointer &svp) -{ - if (svp.isValid()) - return (T&)*(T*)svp.getVar()->data.data(); - else - throw std::runtime_error("The SyncVar pointer does not have an associated SyncVar."); -} - -/** - * @} - */ - -#endif // SYNCVAR_H