From a81bbc6a1f07862f27a38ce433e8e5b84b0aae4b Mon Sep 17 00:00:00 2001 From: Tristan Stenner Date: Thu, 5 Nov 2020 09:05:51 +0100 Subject: [PATCH 1/2] Port to CMake + modern Qt --- .appveyor.yml | 29 ++ .gitignore | 8 + CMakeLists.txt | 51 +++ README.md | 4 +- serialport_config.cfg => SerialPort.cfg | 0 SerialPort.sln | 26 -- SerialPort.vcproj | 541 ------------------------ VTune/SerialPort.vpj | Bin 5632 -> 0 bytes VTune/SerialPort.vws | Bin 16384 -> 0 bytes main.cpp | 25 +- mainwindow.cpp | 339 ++++++++------- mainwindow.h | 55 --- mainwindow.hpp | 37 ++ mainwindow.ui | 53 ++- 14 files changed, 346 insertions(+), 822 deletions(-) create mode 100644 .appveyor.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt rename serialport_config.cfg => SerialPort.cfg (100%) delete mode 100644 SerialPort.sln delete mode 100644 SerialPort.vcproj delete mode 100644 VTune/SerialPort.vpj delete mode 100644 VTune/SerialPort.vws delete mode 100644 mainwindow.h create mode 100644 mainwindow.hpp diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..80b36a9 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,29 @@ +version: 1.13.0.{build} +pull_requests: + do_not_increment_build_number: true +shallow_clone: true +environment: + lsltag: 1.13.0 + lslversion: 1.13.0 + LSLDIST_URL: "https://github.com/sccn/liblsl/releases/download" + CMakeArgs: "" + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + QTCOMPILER: msvc2017_64 + QTVER: 5.13 + - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu1804 + DEPLOYNAME: Linux64-bionic +install: +- cmd: appveyor DownloadFile %LSLDIST_URL%/%lsltag%/liblsl-%lslversion%-Win64.zip -FileName liblsl_x64.zip +- cmd: 7z x liblsl_x64.zip -oLSL +- sh: wget ${LSLDIST_URL}/${lsltag}/liblsl-${lslversion}-${DEPLOYNAME}.deb -O lsl.deb +- sh: sudo dpkg -i lsl.deb +- sh: sudo apt update && sudo apt install -y qtbase5-dev +build_script: +- cmd: cmake -S . -B build -DQt5_DIR=C:/Qt/%QTVER%/%QTCOMPILER%/lib/cmake/Qt5 -DLSL_INSTALL_ROOT=LSL/ %CMakeArgs% -A x64 +- sh: cmake -S . -B build -DLSL_UNIXFOLDERS=1 -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=1 ${CMakeArgs} +- cmake --build build --config Release -j --target package +artifacts: +- path: 'build/*.deb' +- path: 'build/*.tar.*' +- path: 'build/*.7z' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b4f87d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +ui_*.h +/build*/ +/CMakeLists.txt.user +/CMakeLists.json +/.vs/ +/CMakeSettings.json +/out/ +.DS_Store diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f43b645 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.10) + +project(SerialPort + LANGUAGES CXX + VERSION 1.13.0 + ) + +# also look for CMake modules in the cmake subfolder +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# Find an installed liblsl in paths set by the user (LSL_INSTALL_ROOT) +# and some default paths +find_package(LSL REQUIRED + HINTS ${LSL_INSTALL_ROOT} + "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/build/install" + "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/install/x64-Release" + PATH_SUFFIXES share/LSL) +get_filename_component(LSL_PATH ${LSL_CONFIG} DIRECTORY) +message(STATUS "Found LSL lib in ${LSL_PATH}") +LSLAPP_Setup_Boilerplate() + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt5 REQUIRED COMPONENTS Widgets) + +find_package(Threads REQUIRED) + +add_executable(${PROJECT_NAME} MACOSX_BUNDLE WIN32 + main.cpp + mainwindow.cpp + mainwindow.hpp + mainwindow.ui +) + +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt5::Widgets + Threads::Threads + LSL::lsl +) + +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_14) + +installLSLApp(${PROJECT_NAME}) +installLSLAuxFiles(${PROJECT_NAME} + ${PROJECT_NAME}.cfg +) +LSLGenerateCPackConfig() diff --git a/README.md b/README.md index 84bf3cc..57f428c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The program reads a byte stream from a given COM port and emits it as an LSL str # Usage * Start the SerialPort app. You should see a window like the following. -> ![serialport.png](screenshotsserialport.png) +> ![serialport.png](serialport.png) * Make sure that your device is plugged in and that you know its COM port (you can usually check this in the Device Manage). @@ -14,4 +14,4 @@ The program reads a byte stream from a given COM port and emits it as an LSL str * Click the "Link" button. If all goes well you should now have a stream on your lab network that has the name that you entered under Stream Name and type "Raw". Note that you cannot close the app while it is linked. - * For subsequent uses you can save the settings in the GUI via File / Save Configuration. If the app is frequently used with different settings you might can also make a shortcut on the desktop that points to the app and appends to the Target field the snippet `-c name_of_config.cfg`. + * For subsequent uses you can save the settings in the GUI via File / Save Configuration. If the app is frequently used with different settings you might can also make a shortcut on the desktop that points to the app and appends to the Target field the snippet `name_of_config.cfg`. diff --git a/serialport_config.cfg b/SerialPort.cfg similarity index 100% rename from serialport_config.cfg rename to SerialPort.cfg diff --git a/SerialPort.sln b/SerialPort.sln deleted file mode 100644 index 7976f1f..0000000 --- a/SerialPort.sln +++ /dev/null @@ -1,26 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SerialPort", "SerialPort.vcproj", "{F63650CA-477B-3725-8C9E-DF4E4A710E9C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Debug|Win32.ActiveCfg = Debug|Win32 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Debug|Win32.Build.0 = Debug|Win32 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Debug|x64.ActiveCfg = Debug|x64 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Debug|x64.Build.0 = Debug|x64 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Release|Win32.ActiveCfg = Release|Win32 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Release|Win32.Build.0 = Release|Win32 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Release|x64.ActiveCfg = Release|x64 - {F63650CA-477B-3725-8C9E-DF4E4A710E9C}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/SerialPort.vcproj b/SerialPort.vcproj deleted file mode 100644 index 608e814..0000000 --- a/SerialPort.vcproj +++ /dev/null @@ -1,541 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/VTune/SerialPort.vpj b/VTune/SerialPort.vpj deleted file mode 100644 index 69c67c3f2cb028370785f500f2918239ecd329e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5632 zcmeHLO-~b16ukp}fFeRM#IQnEn23>32{Fc`R0&2g4JBPQ(Nagnl!C1!RAc-Vx^bg% z>B^leE#@M9Hp+5($w7i0~49aaCUnLnwv|j|%Z`K>9u>Xukv3~OT5DN1qKl#_| zH+dS*zz@H5jPo?>&%(zTS)IIdYZSfYSdQJ#dD0M5Vh{_hLWAN}R|y{T=rW#E(HI?cO`4fuZpKkWH4S11_+ zG3#Jv31^tx{Q$+4Ymx{5xnTcwv`W~MW#tph3z;2ppB}668b&J6NkMZRI(c7a&s*;q zFVFFt(f^KLh1YK=Z+HE+XWudMFLOiYg<<`VaboUlJe}eYk;F{XTkAMD-!*78>lxX;I&p+P!?em`p z&y?W}^o0tt*(NhHhh=VRf311DcwOfo%shkE?`bx#_WxzU_*pA5+T+uoiA`yu1h>NC mH<_U=egiuSe*EwM=Gvp+2k%n&JH#~-{;7l0G}x>=wfYx$X>g|i diff --git a/VTune/SerialPort.vws b/VTune/SerialPort.vws deleted file mode 100644 index e4b61d8194e76088fda0908a5a7c75e8d363467f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeH}O-chn5QX1lR1z1WF0#l#A|p0@dU2Cg?a=% zh#6naPZGl<_J|vqS3^(NR9AO(f8Eu|+w0Q%!&B`8W3mMw{jh?$5xYm-N+gRwiHd#q z!!V4E09DC9BJjz%&Ln$i`)EZ{90CUj_;j&?9kf|@&`VR92Tr<(e#Llisjm2VbbF>zpI3Jt*YO}>o zY4+`6XW2)Y?{YN%oZ;E8*s@2`J#73M!aV;4j%JL@Kz2`Vx;Q%ewA2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz|y e2#A0Ph=2%)fCz|y2#A0Ph=2%)fCz{{E&^XYf-sN( diff --git a/main.cpp b/main.cpp index 7d495d9..3baed4f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,9 @@ -#include -#include "mainwindow.h" -#include -#include +#include "mainwindow.hpp" +#include -int main(int argc, char *argv[]) -{ - // determine the startup config file... - std::string config_file = "serialport_config.cfg"; - for (int k=1;k 1 ? argv[1] : nullptr); + w.show(); + return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index d74c474..406cda3 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,136 +1,113 @@ -#include "mainwindow.h" +#include "mainwindow.hpp" #include "ui_mainwindow.h" -#include -#include -#include -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent, const std::string &config_file) : -QMainWindow(parent), -ui(new Ui::MainWindow) -{ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent, const char *config_file) + : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); - - // parse startup config file - load_config(config_file); - - // make GUI connections - QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close())); - QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(on_link())); - QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); - QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); + connect(ui->actionLoad_Configuration, &QAction::triggered, [this]() { + load_config(QFileDialog::getOpenFileName( + this, "Load Configuration File", "", "Configuration Files (*.cfg)")); + }); + connect(ui->actionSave_Configuration, &QAction::triggered, [this]() { + save_config(QFileDialog::getSaveFileName( + this, "Save Configuration File", "", "Configuration Files (*.cfg)")); + }); + connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(ui->actionAbout, &QAction::triggered, [this]() { + QString infostr = QStringLiteral("LSL library version: ") + + QString::number(lsl::library_version()) + + "\nLSL library info:" + lsl::library_info(); + QMessageBox::about(this, "About this app", infostr); + }); + connect(ui->linkButton, &QPushButton::clicked, this, &MainWindow::toggleRecording); + + + //: At the end of the constructor, we load the supplied config file or find it + //: in one of the default paths + QString cfgfilepath = find_config_file(config_file); + load_config(cfgfilepath); } -void MainWindow::load_config_dialog() { - QString sel = QFileDialog::getOpenFileName(this,"Load Configuration File","","Configuration Files (*.cfg)"); - if (!sel.isEmpty()) - load_config(sel.toStdString()); +void MainWindow::load_config(const QString &filename) { + QSettings settings(filename, QSettings::Format::IniFormat); + ui->comPort->setValue(settings.value("coresettings.comport", 1).toInt()); + ui->baudRate->setValue(settings.value("coresettings.baudrate", 57600).toInt()); + ui->samplingRate->setValue(settings.value("streamsettings.samplingrate", 0).toInt()); + ui->chunkSize->setValue(settings.value("streamsettings.chunksize", 32).toInt()); + ui->input_name->setText(settings.value("streamsettings.streamname", "SerialPort").toString()); + ui->dataBits->setCurrentIndex(settings.value("miscsettings.databits", 4).toInt()); + ui->parity->setCurrentIndex(settings.value("miscsettings.parity", 0).toInt()); + ui->stopBits->setCurrentIndex(settings.value("miscsettings.stopbits", 0).toInt()); + ui->readIntervalTimeout->setValue( + settings.value("timeoutsettings.readintervaltimeout", 500).toInt()); + ui->readTotalTimeoutConstant->setValue( + settings.value("timeoutsettings.readtotaltimeoutconstant", 50).toInt()); + ui->readTotalTimeoutMultiplier->setValue( + settings.value("timeoutsettings.readtotaltimeoutmultiplier", 10).toInt()); } -void MainWindow::save_config_dialog() { - QString sel = QFileDialog::getSaveFileName(this,"Save Configuration File","","Configuration Files (*.cfg)"); - if (!sel.isEmpty()) - save_config(sel.toStdString()); + +//: Save function, same as above +void MainWindow::save_config(const QString &filename) { + QSettings settings(filename, QSettings::Format::IniFormat); + settings.beginGroup("coresettings"); + settings.setValue("comport", ui->comPort->value()); + settings.setValue("baudrate", ui->baudRate->value()); + settings.endGroup(); + settings.beginGroup("streamsettings"); + settings.setValue("samplingrate", ui->samplingRate->value()); + settings.setValue("chunksize", ui->chunkSize->value()); + settings.setValue("streamname", ui->input_name->text()); + settings.endGroup(); + settings.beginGroup("miscsettings"); + settings.setValue("databits", ui->dataBits->currentIndex()); + settings.setValue("parity", ui->parity->currentIndex()); + settings.setValue("stopbits", ui->stopBits->currentIndex()); + settings.endGroup(); + settings.beginGroup("timeoutsettings"); + settings.setValue("readintervaltimeout", ui->readIntervalTimeout->value()); + settings.setValue("readtotaltimeoutconstant", ui->readTotalTimeoutConstant->value()); + settings.setValue("readtotaltimeoutmultiplier", ui->readTotalTimeoutMultiplier->value()); + settings.sync(); } void MainWindow::closeEvent(QCloseEvent *ev) { - if (reader_thread_) + if (reader) { + QMessageBox::warning(this, "Recording still running", "Can't quit while recording"); ev->ignore(); -} - -void MainWindow::load_config(const std::string &filename) { - using boost::property_tree::ptree; - ptree pt; - - // parse file - try { - read_xml(filename, pt); - } catch(std::exception &e) { - QMessageBox::information(this,"Error",(std::string("Cannot read config file: ")+= e.what()).c_str(),QMessageBox::Ok); - return; - } - - // get config values - try { - ui->comPort->setValue(pt.get("coresettings.comport",1)); - ui->baudRate->setValue(pt.get("coresettings.baudrate",57600)); - ui->samplingRate->setValue(pt.get("streamsettings.samplingrate",0)); - ui->chunkSize->setValue(pt.get("streamsettings.chunksize",32)); - ui->streamName->setText(pt.get("streamsettings.streamname","SerialPort").c_str()); - ui->dataBits->setCurrentIndex(pt.get("miscsettings.databits",4)); - ui->parity->setCurrentIndex(pt.get("miscsettings.parity",0)); - ui->stopBits->setCurrentIndex(pt.get("miscsettings.stopbits",0)); - ui->readIntervalTimeout->setValue(pt.get("timeoutsettings.readintervaltimeout",500)); - ui->readTotalTimeoutConstant->setValue(pt.get("timeoutsettings.readtotaltimeoutconstant",50)); - ui->readTotalTimeoutMultiplier->setValue(pt.get("timeoutsettings.readtotaltimeoutmultiplier",10)); - } catch(std::exception &) { - QMessageBox::information(this,"Error in Config File","Could not read out config parameters.",QMessageBox::Ok); - return; } } -void MainWindow::save_config(const std::string &filename) { - using boost::property_tree::ptree; - ptree pt; - - // transfer UI content into property tree - try { - pt.put("coresettings.comport",ui->comPort->value()); - pt.put("coresettings.baudrate",ui->baudRate->value()); - pt.put("streamsettings.samplingrate",ui->samplingRate->value()); - pt.put("streamsettings.chunksize",ui->chunkSize->value()); - pt.put("streamsettings.streamname",ui->streamName->text().toStdString()); - pt.put("miscsettings.databits",ui->dataBits->currentIndex()); - pt.put("miscsettings.parity",ui->parity->currentIndex()); - pt.put("miscsettings.stopbits",ui->stopBits->currentIndex()); - pt.put("timeoutsettings.readintervaltimeout",ui->readIntervalTimeout->value()); - pt.put("timeoutsettings.readtotaltimeoutconstant",ui->readTotalTimeoutConstant->value()); - pt.put("timeoutsettings.readtotaltimeoutmultiplier",ui->readTotalTimeoutMultiplier->value()); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not prepare settings for saving: ")+=e.what()).c_str(),QMessageBox::Ok); - } - - // write to disk - try { - write_xml(filename, pt); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not write to config file: ")+=e.what()).c_str(),QMessageBox::Ok); - } -} - - -// start/stop the cognionics connection -void MainWindow::on_link() { - if (reader_thread_) { - // === perform unlink action === - try { - stop_ = true; - reader_thread_->interrupt(); - reader_thread_->join(); - reader_thread_.reset(); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); - return; - } - - // indicate that we are now successfully unlinked - ui->linkButton->setText("Link"); - } else { - // === perform link action === - HANDLE hPort = NULL; - +//: ## Toggling the recording state +//: Our record button has two functions: start a recording and +//: stop it if a recording is running already. +void MainWindow::toggleRecording() { + /*: the `std::unique_ptr` evaluates to false if it doesn't point to an object, + * so we need to start a recording. + * First, we load the configuration from the UI fields, set the shutdown flag + * to false so the recording thread doesn't quit immediately and create the + * recording thread. */ + if (!reader) { try { // get the UI parameters... int comPort = ui->comPort->value(); int baudRate = ui->baudRate->value(); int samplingRate = ui->samplingRate->value(); int chunkSize = ui->chunkSize->value(); - std::string streamName = ui->streamName->text().toStdString(); - int dataBits = ui->dataBits->currentIndex()+4; + std::string streamName = ui->input_name->text().toStdString(); + int dataBits = ui->dataBits->currentIndex() + 4; int parity = ui->parity->currentIndex(); int stopBits = ui->stopBits->currentIndex(); int readIntervalTimeout = ui->readIntervalTimeout->value(); @@ -138,91 +115,133 @@ void MainWindow::on_link() { int readTotalTimeoutMultiplier = ui->readTotalTimeoutMultiplier->value(); // try to open the serial port - std::string fname = "\\\\.\\COM" + boost::lexical_cast(comPort); - hPort = CreateFileA(fname.c_str(),GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); + std::string fname = "\\\\.\\COM" + std::to_string(comPort); + hPort = CreateFileA(fname.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0); if (hPort == INVALID_HANDLE_VALUE) - throw std::runtime_error("Could not open serial port. Please make sure that you are using the right COM port and that the device is ready."); + throw std::runtime_error( + "Could not open serial port. Please make sure that you are using the right COM " + "port and that the device is ready."); // try to set up serial port parameters DCB dcbSerialParams = {0}; if (!GetCommState(hPort, &dcbSerialParams)) - QMessageBox::critical(this,"Error","Could not get COM port state.",QMessageBox::Ok); - dcbSerialParams.BaudRate=baudRate; - dcbSerialParams.ByteSize=dataBits; - dcbSerialParams.StopBits=stopBits; - dcbSerialParams.Parity=parity; - if(!SetCommState(hPort, &dcbSerialParams)) - QMessageBox::critical(this,"Error","Could not set baud rate.",QMessageBox::Ok); + QMessageBox::critical( + this, "Error", "Could not get COM port state.", QMessageBox::Ok); + dcbSerialParams.BaudRate = baudRate; + dcbSerialParams.ByteSize = dataBits; + dcbSerialParams.StopBits = stopBits; + dcbSerialParams.Parity = parity; + if (!SetCommState(hPort, &dcbSerialParams)) + QMessageBox::critical(this, "Error", "Could not set baud rate.", QMessageBox::Ok); // try to set timeouts COMMTIMEOUTS timeouts = {0}; - if (!GetCommTimeouts(hPort,&timeouts)) - QMessageBox::critical(this,"Error","Could not get COM port timeouts.",QMessageBox::Ok); + if (!GetCommTimeouts(hPort, &timeouts)) + QMessageBox::critical( + this, "Error", "Could not get COM port timeouts.", QMessageBox::Ok); timeouts.ReadIntervalTimeout = readIntervalTimeout; timeouts.ReadTotalTimeoutConstant = readTotalTimeoutConstant; timeouts.ReadTotalTimeoutMultiplier = readTotalTimeoutMultiplier; - if (!SetCommTimeouts(hPort,&timeouts)) - QMessageBox::critical(this,"Error","Could not set COM port timeouts.",QMessageBox::Ok); - - // start reading - stop_ = false; - reader_thread_.reset(new boost::thread(&MainWindow::read_thread,this,hPort,comPort,baudRate,samplingRate,chunkSize,streamName)); - } - catch(std::exception &e) { - if (hPort != INVALID_HANDLE_VALUE) - CloseHandle(hPort); - QMessageBox::critical(this,"Error",(std::string("Error during initialization: ")+=e.what()).c_str(),QMessageBox::Ok); + if (!SetCommTimeouts(hPort, &timeouts)) + QMessageBox::critical( + this, "Error", "Could not set COM port timeouts.", QMessageBox::Ok); + reader = std::make_unique([&]() { + read_thread( + hPort, comPort, baudRate, samplingRate, chunkSize, streamName, shutdown); + }); + + } catch (std::exception &e) { + if (hPort != INVALID_HANDLE_VALUE) CloseHandle(hPort); + QMessageBox::critical(this, "Error", + QStringLiteral("Error during initialization: ") + e.what(), QMessageBox::Ok); return; } - - // done, all successful ui->linkButton->setText("Unlink"); + } else { + shutdown = true; + reader->join(); + reader.reset(); + ui->linkButton->setText("Link"); } } - // background data reader thread -void MainWindow::read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, const std::string &streamName) { +void read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, + const std::string streamName, std::atomic &shutdown) { try { // create streaminfo - lsl::stream_info info(streamName,"Raw",1,samplingRate,lsl::cf_int16,std::string("SerialPort_") + streamName); + lsl::stream_info info(streamName, "Raw", 1, samplingRate, lsl::cf_int16, + std::string("SerialPort_") + streamName); // append some meta-data lsl::xml_element channels = info.desc().append_child("channels"); channels.append_child("channel") - .append_child_value("label","Channel1") - .append_child_value("type","Raw") - .append_child_value("unit","integer"); - info.desc().append_child("acquisition") + .append_child_value("label", "Channel1") + .append_child_value("type", "Raw") + .append_child_value("unit", "integer"); + info.desc() + .append_child("acquisition") .append_child("hardware") - .append_child_value("com_port",boost::lexical_cast(comPort).c_str()) - .append_child_value("baud_rate",boost::lexical_cast(baudRate).c_str()); + .append_child_value("com_port", std::to_string(comPort)) + .append_child_value("baud_rate", std::to_string(baudRate)); // make a new outlet - lsl::stream_outlet outlet(info,chunkSize); + lsl::stream_outlet outlet(info, chunkSize); // enter transmission loop unsigned char byte; short sample; unsigned long bytes_read; - while (!stop_) { + while (!shutdown) { // get a sample - ReadFile(hPort,&byte,1,&bytes_read,NULL); sample = byte; + ReadFile(hPort, &byte, 1, &bytes_read, NULL); + sample = byte; // transmit it - if (bytes_read) - outlet.push_sample(&sample); + if (bytes_read) outlet.push_sample(&sample); } - } - catch(boost::thread_interrupted &) { - // thread was interrupted: no error - } - catch(std::exception &e) { + } catch (std::exception &e) { // any other error - QMessageBox::critical(this,"Error",(std::string("Error during processing: ")+=e.what()).c_str(),QMessageBox::Ok); + QMessageBox::critical(nullptr, "Error", + QStringLiteral("Error during processing: ") + e.what(), QMessageBox::Ok); } CloseHandle(hPort); } -MainWindow::~MainWindow() { - delete ui; +/** + * Find a config file to load. This is (in descending order or preference): + * - a file supplied on the command line + * - [executablename].cfg in one the the following folders: + * - the current working directory + * - the default config folder, e.g. '~/Library/Preferences' on OS X + * - the executable folder + * @param filename Optional file name supplied e.g. as command line parameter + * @return Path to a found config file + */ +QString MainWindow::find_config_file(const char *filename) { + if (filename) { + QString qfilename(filename); + if (!QFileInfo::exists(qfilename)) + QMessageBox(QMessageBox::Warning, "Config file not found", + QStringLiteral("The file '%1' doesn't exist").arg(qfilename), QMessageBox::Ok, + this); + else + return qfilename; + } + QFileInfo exeInfo(QCoreApplication::applicationFilePath()); + QString defaultCfgFilename(exeInfo.completeBaseName() + ".cfg"); + QStringList cfgpaths; + cfgpaths << QDir::currentPath() + << QStandardPaths::standardLocations(QStandardPaths::ConfigLocation) << exeInfo.path(); + for (auto path : cfgpaths) { + QString cfgfilepath = path + QDir::separator() + defaultCfgFilename; + if (QFileInfo::exists(cfgfilepath)) return cfgfilepath; + } + QMessageBox(QMessageBox::Warning, "No config file not found", + QStringLiteral("No default config file could be found"), QMessageBox::Ok, this); + return ""; } + + +//: Tell the compiler to put the default destructor in this object file +MainWindow::~MainWindow() noexcept = default; diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index 44c915d..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include -#include -#include -#include -#include - -// LSL API -#include - -#define WIN32_LEAN_AND_MEAN -#include "windows.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent, const std::string &config_file); - ~MainWindow(); - -private slots: - // config file dialog ops (from main menu) - void load_config_dialog(); - void save_config_dialog(); - - // start the cognionics connection - void on_link(); - - // close event (potentially disabled) - void closeEvent(QCloseEvent *ev); -private: - // background data reader thread - void read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, const std::string &streamName); - - // raw config file IO - void load_config(const std::string &filename); - void save_config(const std::string &filename); - - bool stop_; // whether the reader thread is supposed to stop - boost::shared_ptr reader_thread_; // our reader thread - - Ui::MainWindow *ui; -}; - -#endif // MAINWINDOW_H diff --git a/mainwindow.hpp b/mainwindow.hpp new file mode 100644 index 0000000..e6c1da6 --- /dev/null +++ b/mainwindow.hpp @@ -0,0 +1,37 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include //for std::unique_ptr +#include + + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow { + Q_OBJECT +public: + explicit MainWindow(QWidget *parent, const char *config_file); + ~MainWindow() noexcept override; + +private slots: + void closeEvent(QCloseEvent *ev) override; + void toggleRecording(); + +private: + QString find_config_file(const char *filename); + void load_config(const QString &filename); + void save_config(const QString &filename); + std::unique_ptr reader{nullptr}; + + std::unique_ptr ui; // window pointer + std::atomic shutdown{false}; // flag indicating whether the recording thread should quit +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index a09e1a9..876fda8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -9,8 +9,8 @@ 0 0 - 415 - 333 + 502 + 433 @@ -26,9 +26,6 @@ Core Port Settings - - QFormLayout::AllNonFixedFieldsGrow - @@ -78,9 +75,6 @@ Stream Settings - - QFormLayout::AllNonFixedFieldsGrow - @@ -93,6 +87,9 @@ Set this to a non-zero value if you know the sampling rate. + + Hz + 0 @@ -112,7 +109,7 @@ - + The name of the stream within LSL. @@ -294,8 +291,8 @@ - - Maximum time that may pass between two bytes + + ms 10000000 @@ -394,40 +391,56 @@ 0 0 - 415 - 18 + 502 + 30 - File + &File - + + + + + Help + + + - Load Configuration + &Load Configuration + + + Ctrl+L - Save Configuration + &Save Configuration + + + Ctrl+S - Quit + &Quit + + + Ctrl+Q - + - Quit + &About From 7021eff467ecee1c6b1778c28afcf9d9f3bc9fcc Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Wed, 5 Apr 2023 02:20:30 -0400 Subject: [PATCH 2/2] Following #3 --- CMakeLists.txt | 2 +- main.cpp | 19 +-- mainwindow.cpp | 432 ++++++++++++++++++++++++++----------------------- mainwindow.h | 56 ------- mainwindow.hpp | 37 +++++ mainwindow.ui | 55 ++++--- 6 files changed, 305 insertions(+), 296 deletions(-) delete mode 100644 mainwindow.h create mode 100644 mainwindow.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fdc019..9f9588f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ add_executable(${PROJECT_NAME}) target_sources(${PROJECT_NAME} PRIVATE main.cpp mainwindow.cpp - mainwindow.h + mainwindow.hpp mainwindow.ui ) diff --git a/main.cpp b/main.cpp index bf9ed41..d730ed4 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,9 @@ +#include "mainwindow.hpp" #include -#include "mainwindow.h" -#include - - -int main(int argc, char *argv[]) -{ - // determine the startup config file... - std::string config_file = "SerialPortLSL.cfg"; - for (int k=1;k 1 ? argv[1] : nullptr); w.show(); - - return a.exec(); -} +} \ No newline at end of file diff --git a/mainwindow.cpp b/mainwindow.cpp index 795954f..38850ab 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,221 +1,247 @@ -#include "mainwindow.h" +#include "mainwindow.hpp" #include "ui_mainwindow.h" -#include -#include - - -MainWindow::MainWindow(QWidget *parent, const std::string &config_file) : -QMainWindow(parent), -ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - // parse startup config file - load_config(config_file); - - // make GUI connections - QObject::connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close())); - QObject::connect(ui->linkButton, SIGNAL(clicked()), this, SLOT(on_link())); - QObject::connect(ui->actionLoad_Configuration, SIGNAL(triggered()), this, SLOT(load_config_dialog())); - QObject::connect(ui->actionSave_Configuration, SIGNAL(triggered()), this, SLOT(save_config_dialog())); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent, const char *config_file) + : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + connect(ui->actionLoad_Configuration, &QAction::triggered, [this]() { + load_config(QFileDialog::getOpenFileName( + this, "Load Configuration File", "", "Configuration Files (*.cfg)")); + }); + connect(ui->actionSave_Configuration, &QAction::triggered, [this]() { + save_config(QFileDialog::getSaveFileName( + this, "Save Configuration File", "", "Configuration Files (*.cfg)")); + }); + connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::close); + connect(ui->actionAbout, &QAction::triggered, [this]() { + QString infostr = QStringLiteral("LSL library version: ") + + QString::number(lsl::library_version()) + + "\nLSL library info:" + lsl::library_info(); + QMessageBox::about(this, "About this app", infostr); + }); + connect(ui->linkButton, &QPushButton::clicked, this, &MainWindow::toggleRecording); + + + //: At the end of the constructor, we load the supplied config file or find it + //: in one of the default paths + QString cfgfilepath = find_config_file(config_file); + load_config(cfgfilepath); } - -void MainWindow::load_config_dialog() { - QString sel = QFileDialog::getOpenFileName(this,"Load Configuration File","","Configuration Files (*.cfg)"); - if (!sel.isEmpty()) - load_config(sel.toStdString()); +void MainWindow::load_config(const QString &filename) { + QSettings settings(filename, QSettings::Format::IniFormat); + ui->comPort->setValue(settings.value("coresettings.comport", 1).toInt()); + ui->baudRate->setValue(settings.value("coresettings.baudrate", 57600).toInt()); + ui->samplingRate->setValue(settings.value("streamsettings.samplingrate", 0).toInt()); + ui->chunkSize->setValue(settings.value("streamsettings.chunksize", 32).toInt()); + ui->input_name->setText(settings.value("streamsettings.streamname", "SerialPort").toString()); + ui->dataBits->setCurrentIndex(settings.value("miscsettings.databits", 4).toInt()); + ui->parity->setCurrentIndex(settings.value("miscsettings.parity", 0).toInt()); + ui->stopBits->setCurrentIndex(settings.value("miscsettings.stopbits", 0).toInt()); + ui->readIntervalTimeout->setValue( + settings.value("timeoutsettings.readintervaltimeout", 500).toInt()); + ui->readTotalTimeoutConstant->setValue( + settings.value("timeoutsettings.readtotaltimeoutconstant", 50).toInt()); + ui->readTotalTimeoutMultiplier->setValue( + settings.value("timeoutsettings.readtotaltimeoutmultiplier", 10).toInt()); } -void MainWindow::save_config_dialog() { - QString sel = QFileDialog::getSaveFileName(this,"Save Configuration File","","Configuration Files (*.cfg)"); - if (!sel.isEmpty()) - save_config(sel.toStdString()); +//: Save function, same as above +void MainWindow::save_config(const QString &filename) { + QSettings settings(filename, QSettings::Format::IniFormat); + settings.beginGroup("coresettings"); + settings.setValue("comport", ui->comPort->value()); + settings.setValue("baudrate", ui->baudRate->value()); + settings.endGroup(); + settings.beginGroup("streamsettings"); + settings.setValue("samplingrate", ui->samplingRate->value()); + settings.setValue("chunksize", ui->chunkSize->value()); + settings.setValue("streamname", ui->input_name->text()); + settings.endGroup(); + settings.beginGroup("miscsettings"); + settings.setValue("databits", ui->dataBits->currentIndex()); + settings.setValue("parity", ui->parity->currentIndex()); + settings.setValue("stopbits", ui->stopBits->currentIndex()); + settings.endGroup(); + settings.beginGroup("timeoutsettings"); + settings.setValue("readintervaltimeout", ui->readIntervalTimeout->value()); + settings.setValue("readtotaltimeoutconstant", ui->readTotalTimeoutConstant->value()); + settings.setValue("readtotaltimeoutmultiplier", ui->readTotalTimeoutMultiplier->value()); + settings.sync(); } void MainWindow::closeEvent(QCloseEvent *ev) { - if (reader_thread_) - ev->ignore(); + if (reader) { + QMessageBox::warning(this, "Recording still running", "Can't quit while recording"); + ev->ignore(); + } } -void MainWindow::load_config(const std::string &filename) { - using boost::property_tree::ptree; - ptree pt; - - // parse file - try { - read_xml(filename, pt); - } catch(std::exception &e) { - QMessageBox::information(this,"Error",(std::string("Cannot read config file: ")+= e.what()).c_str(),QMessageBox::Ok); - return; - } - - // get config values - try { - ui->comPort->setValue(pt.get("coresettings.comport",1)); - ui->baudRate->setValue(pt.get("coresettings.baudrate",57600)); - ui->samplingRate->setValue(pt.get("streamsettings.samplingrate",0)); - ui->chunkSize->setValue(pt.get("streamsettings.chunksize",32)); - ui->streamName->setText(pt.get("streamsettings.streamname","SerialPort").c_str()); - ui->dataBits->setCurrentIndex(pt.get("miscsettings.databits",4)); - ui->parity->setCurrentIndex(pt.get("miscsettings.parity",0)); - ui->stopBits->setCurrentIndex(pt.get("miscsettings.stopbits",0)); - ui->readIntervalTimeout->setValue(pt.get("timeoutsettings.readintervaltimeout",500)); - ui->readTotalTimeoutConstant->setValue(pt.get("timeoutsettings.readtotaltimeoutconstant",50)); - ui->readTotalTimeoutMultiplier->setValue(pt.get("timeoutsettings.readtotaltimeoutmultiplier",10)); - } catch(std::exception &) { - QMessageBox::information(this,"Error in Config File","Could not read out config parameters.",QMessageBox::Ok); - return; - } -} -void MainWindow::save_config(const std::string &filename) { - using boost::property_tree::ptree; - ptree pt; - - // transfer UI content into property tree - try { - pt.put("coresettings.comport",ui->comPort->value()); - pt.put("coresettings.baudrate",ui->baudRate->value()); - pt.put("streamsettings.samplingrate",ui->samplingRate->value()); - pt.put("streamsettings.chunksize",ui->chunkSize->value()); - pt.put("streamsettings.streamname",ui->streamName->text().toStdString()); - pt.put("miscsettings.databits",ui->dataBits->currentIndex()); - pt.put("miscsettings.parity",ui->parity->currentIndex()); - pt.put("miscsettings.stopbits",ui->stopBits->currentIndex()); - pt.put("timeoutsettings.readintervaltimeout",ui->readIntervalTimeout->value()); - pt.put("timeoutsettings.readtotaltimeoutconstant",ui->readTotalTimeoutConstant->value()); - pt.put("timeoutsettings.readtotaltimeoutmultiplier",ui->readTotalTimeoutMultiplier->value()); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not prepare settings for saving: ")+=e.what()).c_str(),QMessageBox::Ok); - } - - // write to disk - try { - write_xml(filename, pt); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not write to config file: ")+=e.what()).c_str(),QMessageBox::Ok); - } +// background data reader thread +void read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, + const std::string streamName, std::atomic &shutdown) { + try { + + // create streaminfo + lsl::stream_info info(streamName,"Raw",1,samplingRate,lsl::cf_int16,std::string("SerialPort_") + streamName); + // append some meta-data + lsl::xml_element channels = info.desc().append_child("channels"); + channels.append_child("channel") + .append_child_value("label","Channel1") + .append_child_value("type","Raw") + .append_child_value("unit","integer"); + info.desc().append_child("acquisition") + .append_child("hardware") + .append_child_value("com_port", std::to_string(comPort)) + .append_child_value("baud_rate",std::to_string(baudRate)); + + // make a new outlet + lsl::stream_outlet outlet(info,chunkSize); + + // enter transmission loop + unsigned char byte; + short sample; + unsigned long bytes_read; + while (!shutdown) { + // get a sample + ReadFile(hPort,&byte,1,&bytes_read,NULL); sample = byte; + // transmit it + if (bytes_read) + outlet.push_sample(&sample); + } + } + catch(std::exception &e) { + // any other error +// QMessageBox::critical(this,"Error",(std::string("Error during processing: ")+=e.what()).c_str(),QMessageBox::Ok); + } + CloseHandle(hPort); } - -// start/stop the cognionics connection -void MainWindow::on_link() { - if (reader_thread_) { - // === perform unlink action === - try { - shutdown_ = true; - reader_thread_->join(); - reader_thread_.reset(); - } catch(std::exception &e) { - QMessageBox::critical(this,"Error",(std::string("Could not stop the background processing: ")+=e.what()).c_str(),QMessageBox::Ok); - return; - } - - // indicate that we are now successfully unlinked - ui->linkButton->setText("Link"); - } else { - // === perform link action === - HANDLE hPort = NULL; - - try { - // get the UI parameters... - int comPort = ui->comPort->value(); - int baudRate = ui->baudRate->value(); - int samplingRate = ui->samplingRate->value(); - int chunkSize = ui->chunkSize->value(); - std::string streamName = ui->streamName->text().toStdString(); - int dataBits = ui->dataBits->currentIndex()+4; - int parity = ui->parity->currentIndex(); - int stopBits = ui->stopBits->currentIndex(); - int readIntervalTimeout = ui->readIntervalTimeout->value(); - int readTotalTimeoutConstant = ui->readTotalTimeoutConstant->value(); - int readTotalTimeoutMultiplier = ui->readTotalTimeoutMultiplier->value(); - - // try to open the serial port - std::string fname = "\\\\.\\COM" + std::to_string(comPort); - hPort = CreateFileA(fname.c_str(),GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); - if (hPort == INVALID_HANDLE_VALUE) - throw std::runtime_error("Could not open serial port. Please make sure that you are using the right COM port and that the device is ready."); - - // try to set up serial port parameters - DCB dcbSerialParams = {0}; - if (!GetCommState(hPort, &dcbSerialParams)) - QMessageBox::critical(this,"Error","Could not get COM port state.",QMessageBox::Ok); - dcbSerialParams.BaudRate=baudRate; - dcbSerialParams.ByteSize=dataBits; - dcbSerialParams.StopBits=stopBits; - dcbSerialParams.Parity=parity; - if(!SetCommState(hPort, &dcbSerialParams)) - QMessageBox::critical(this,"Error","Could not set baud rate.",QMessageBox::Ok); - - // try to set timeouts - COMMTIMEOUTS timeouts = {0}; - if (!GetCommTimeouts(hPort,&timeouts)) - QMessageBox::critical(this,"Error","Could not get COM port timeouts.",QMessageBox::Ok); - timeouts.ReadIntervalTimeout = readIntervalTimeout; - timeouts.ReadTotalTimeoutConstant = readTotalTimeoutConstant; - timeouts.ReadTotalTimeoutMultiplier = readTotalTimeoutMultiplier; - if (!SetCommTimeouts(hPort,&timeouts)) - QMessageBox::critical(this,"Error","Could not set COM port timeouts.",QMessageBox::Ok); - - // start reading - shutdown_ = false; - reader_thread_ = std::make_unique(&MainWindow::read_thread, this, hPort, comPort, baudRate, samplingRate, chunkSize, streamName); - - } - catch(std::exception &e) { - if (hPort != INVALID_HANDLE_VALUE) - CloseHandle(hPort); - QMessageBox::critical(this,"Error",(std::string("Error during initialization: ")+=e.what()).c_str(),QMessageBox::Ok); - return; - } - - // done, all successful - ui->linkButton->setText("Unlink"); - } +//: ## Toggling the recording state +//: Our record button has two functions: start a recording and +//: stop it if a recording is running already. +void MainWindow::toggleRecording() { + /*: the `std::unique_ptr` evaluates to false if it doesn't point to an object, + * so we need to start a recording. + * First, we load the configuration from the UI fields, set the shutdown flag + * to false so the recording thread doesn't quit immediately and create the + * recording thread. */ + if (!reader) { + HANDLE hPort = NULL; + try { + + // get the UI parameters... + int comPort = ui->comPort->value(); + int baudRate = ui->baudRate->value(); + int samplingRate = ui->samplingRate->value(); + int chunkSize = ui->chunkSize->value(); + std::string streamName = ui->input_name->text().toStdString(); + int dataBits = ui->dataBits->currentIndex() + 4; + int parity = ui->parity->currentIndex(); + int stopBits = ui->stopBits->currentIndex(); + int readIntervalTimeout = ui->readIntervalTimeout->value(); + int readTotalTimeoutConstant = ui->readTotalTimeoutConstant->value(); + int readTotalTimeoutMultiplier = ui->readTotalTimeoutMultiplier->value(); + + // try to open the serial port + std::string fname = "\\\\.\\COM" + std::to_string(comPort); + hPort = CreateFileA(fname.c_str(), GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0); + if (hPort == INVALID_HANDLE_VALUE) + throw std::runtime_error( + "Could not open serial port. Please make sure that you are using the right COM " + "port and that the device is ready."); + + // try to set up serial port parameters + DCB dcbSerialParams = {0}; + if (!GetCommState(hPort, &dcbSerialParams)) + QMessageBox::critical( + this, "Error", "Could not get COM port state.", QMessageBox::Ok); + dcbSerialParams.BaudRate = baudRate; + dcbSerialParams.ByteSize = dataBits; + dcbSerialParams.StopBits = stopBits; + dcbSerialParams.Parity = parity; + if (!SetCommState(hPort, &dcbSerialParams)) + QMessageBox::critical(this, "Error", "Could not set baud rate.", QMessageBox::Ok); + + // try to set timeouts + COMMTIMEOUTS timeouts = {0}; + if (!GetCommTimeouts(hPort, &timeouts)) + QMessageBox::critical( + this, "Error", "Could not get COM port timeouts.", QMessageBox::Ok); + timeouts.ReadIntervalTimeout = readIntervalTimeout; + timeouts.ReadTotalTimeoutConstant = readTotalTimeoutConstant; + timeouts.ReadTotalTimeoutMultiplier = readTotalTimeoutMultiplier; + if (!SetCommTimeouts(hPort, &timeouts)) + QMessageBox::critical( + this, "Error", "Could not set COM port timeouts.", QMessageBox::Ok); + reader = std::make_unique([&]() { + read_thread( + hPort, comPort, baudRate, samplingRate, chunkSize, streamName, shutdown); + }); + + } catch (std::exception &e) { + if (hPort != INVALID_HANDLE_VALUE) CloseHandle(hPort); + QMessageBox::critical(this, "Error", + QStringLiteral("Error during initialization: ") + e.what(), QMessageBox::Ok); + return; + } + ui->linkButton->setText("Unlink"); + } else { + shutdown = true; + reader->join(); + reader.reset(); + ui->linkButton->setText("Link"); + } } -// background data reader thread -void MainWindow::read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, const std::string &streamName) { - try { - - // create streaminfo - lsl::stream_info info(streamName,"Raw",1,samplingRate,lsl::cf_int16,std::string("SerialPort_") + streamName); - // append some meta-data - lsl::xml_element channels = info.desc().append_child("channels"); - channels.append_child("channel") - .append_child_value("label","Channel1") - .append_child_value("type","Raw") - .append_child_value("unit","integer"); - info.desc().append_child("acquisition") - .append_child("hardware") - .append_child_value("com_port", std::to_string(comPort)) - .append_child_value("baud_rate",std::to_string(baudRate)); - - // make a new outlet - lsl::stream_outlet outlet(info,chunkSize); - - // enter transmission loop - unsigned char byte; - short sample; - unsigned long bytes_read; - while (!shutdown_) { - // get a sample - ReadFile(hPort,&byte,1,&bytes_read,NULL); sample = byte; - // transmit it - if (bytes_read) - outlet.push_sample(&sample); - } - } - catch(std::exception &e) { - // any other error - QMessageBox::critical(this,"Error",(std::string("Error during processing: ")+=e.what()).c_str(),QMessageBox::Ok); - } - CloseHandle(hPort); +/** + * Find a config file to load. This is (in descending order or preference): + * - a file supplied on the command line + * - [executablename].cfg in one the the following folders: + * - the current working directory + * - the default config folder, e.g. '~/Library/Preferences' on OS X + * - the executable folder + * @param filename Optional file name supplied e.g. as command line parameter + * @return Path to a found config file + */ +QString MainWindow::find_config_file(const char *filename) { + if (filename) { + QString qfilename(filename); + if (!QFileInfo::exists(qfilename)) + QMessageBox(QMessageBox::Warning, "Config file not found", + QStringLiteral("The file '%1' doesn't exist").arg(qfilename), QMessageBox::Ok, + this); + else + return qfilename; + } + QFileInfo exeInfo(QCoreApplication::applicationFilePath()); + QString defaultCfgFilename(exeInfo.completeBaseName() + ".cfg"); + QStringList cfgpaths; + cfgpaths << QDir::currentPath() + << QStandardPaths::standardLocations(QStandardPaths::ConfigLocation) << exeInfo.path(); + for (auto path : cfgpaths) { + QString cfgfilepath = path + QDir::separator() + defaultCfgFilename; + if (QFileInfo::exists(cfgfilepath)) return cfgfilepath; + } + QMessageBox(QMessageBox::Warning, "No config file not found", + QStringLiteral("No default config file could be found"), QMessageBox::Ok, this); + return ""; } -MainWindow::~MainWindow() { - delete ui; -} + +//: Tell the compiler to put the default destructor in this object file +MainWindow::~MainWindow() noexcept = default; \ No newline at end of file diff --git a/mainwindow.h b/mainwindow.h deleted file mode 100644 index a8e6161..0000000 --- a/mainwindow.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// LSL API -#include - -#define WIN32_LEAN_AND_MEAN -#include "windows.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent, const std::string &config_file); - ~MainWindow(); - -private slots: - // config file dialog ops (from main menu) - void load_config_dialog(); - void save_config_dialog(); - - // start the cognionics connection - void on_link(); - - // close event (potentially disabled) - void closeEvent(QCloseEvent *ev); -private: - // background data reader thread - void read_thread(HANDLE hPort, int comPort, int baudRate, int samplingRate, int chunkSize, const std::string &streamName); - - // raw config file IO - void load_config(const std::string &filename); - void save_config(const std::string &filename); - - std::unique_ptr reader_thread_{nullptr}; // our reader thread - std::atomic shutdown_{false}; // flag indicating whether the streaming thread should quit - - Ui::MainWindow *ui; -}; - -#endif // MAINWINDOW_H diff --git a/mainwindow.hpp b/mainwindow.hpp new file mode 100644 index 0000000..d455f49 --- /dev/null +++ b/mainwindow.hpp @@ -0,0 +1,37 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include //for std::unique_ptr +#include + + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow { +Q_OBJECT +public: + explicit MainWindow(QWidget *parent, const char *config_file); + ~MainWindow() noexcept override; + +private slots: + void closeEvent(QCloseEvent *ev) override; + void toggleRecording(); + +private: + QString find_config_file(const char *filename); + void load_config(const QString &filename); + void save_config(const QString &filename); + std::unique_ptr reader{nullptr}; + + std::unique_ptr ui; // window pointer + std::atomic shutdown{false}; // flag indicating whether the recording thread should quit +}; + +#endif // MAINWINDOW_H \ No newline at end of file diff --git a/mainwindow.ui b/mainwindow.ui index a09e1a9..5d8d014 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -9,8 +9,8 @@ 0 0 - 415 - 333 + 502 + 433 @@ -26,9 +26,6 @@ Core Port Settings - - QFormLayout::AllNonFixedFieldsGrow - @@ -78,9 +75,6 @@ Stream Settings - - QFormLayout::AllNonFixedFieldsGrow - @@ -93,6 +87,9 @@ Set this to a non-zero value if you know the sampling rate. + + Hz + 0 @@ -112,7 +109,7 @@ - + The name of the stream within LSL. @@ -294,8 +291,8 @@ - - Maximum time that may pass between two bytes + + ms 10000000 @@ -394,44 +391,60 @@ 0 0 - 415 - 18 + 502 + 30 - File + &File - + + + + + Help + + + - Load Configuration + &Load Configuration + + + Ctrl+L - Save Configuration + &Save Configuration + + + Ctrl+S - Quit + &Quit + + + Ctrl+Q - + - Quit + &About - + \ No newline at end of file