diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 2.8.11) + +project(serialplot) + +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Instruct CMake to run moc automatically when needed. +set(CMAKE_AUTOMOC ON) + +# add local path for cmake modules +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/") + +# Find the QtWidgets library +find_package(Qt5Widgets) +find_package(Qwt 6.1 REQUIRED) + +# includes +include_directories(${QWT_INCLUDE_DIR}) + +# wrap UI files +qt5_wrap_ui(UI_FILES mainwindow.ui) + +# Tell CMake to create the helloworld executable +add_executable(serialplot main.cpp mainwindow.cpp customcheckablebutton.cpp ${UI_FILES}) + +# Use the Widgets module from Qt 5. +qt5_use_modules(serialplot Widgets SerialPort) +target_link_libraries(serialplot ${QWT_LIBRARY}) + +# Enable C++11 support, fail if not supported +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") +endif() + +# add make run target +add_custom_target(run + COMMAND serialplot + DEPENDS serialplot + WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ + +Small and simple software for plotting data from serial port. + +# Dependencies +- Qt 5, including serialport module +- qwt 6.1 +- libudev-dev (required for QSerialPort) diff --git a/cmake/modules/FindQwt.cmake b/cmake/modules/FindQwt.cmake new file mode 100644 --- /dev/null +++ b/cmake/modules/FindQwt.cmake @@ -0,0 +1,120 @@ +# Qt Widgets for Technical Applications +# available at http://www.http://qwt.sourceforge.net/ +# +# The module defines the following variables: +# QWT_FOUND - the system has Qwt +# QWT_INCLUDE_DIR - where to find qwt_plot.h +# QWT_INCLUDE_DIRS - qwt includes +# QWT_LIBRARY - where to find the Qwt library +# QWT_LIBRARIES - aditional libraries +# QWT_MAJOR_VERSION - major version +# QWT_MINOR_VERSION - minor version +# QWT_PATCH_VERSION - patch version +# QWT_VERSION_STRING - version (ex. 5.2.1) +# QWT_ROOT_DIR - root dir (ex. /usr/local) + +#============================================================================= +# Copyright 2010-2013, Julien Schueller +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# The views and conclusions contained in the software and documentation are those +# of the authors and should not be interpreted as representing official policies, +# either expressed or implied, of the FreeBSD Project. +#============================================================================= + +# TODO: handle multiple versions of qwt +file(GLOB QWT_GLOB_DIR "/usr/local/qwt*/") + +find_path ( QWT_INCLUDE_DIR + NAMES qwt_plot.h + HINTS ${QT_INCLUDE_DIR} "${QWT_GLOB_DIR}/include" + PATH_SUFFIXES qwt qwt-qt3 qwt-qt4 qwt-qt5 +) + +set ( QWT_INCLUDE_DIRS ${QWT_INCLUDE_DIR} ) + +# version +set ( _VERSION_FILE ${QWT_INCLUDE_DIR}/qwt_global.h ) +if ( EXISTS ${_VERSION_FILE} ) + file ( STRINGS ${_VERSION_FILE} _VERSION_LINE REGEX "define[ ]+QWT_VERSION_STR" ) + if ( _VERSION_LINE ) + string ( REGEX REPLACE ".*define[ ]+QWT_VERSION_STR[ ]+\"(.*)\".*" "\\1" QWT_VERSION_STRING "${_VERSION_LINE}" ) + string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" QWT_MAJOR_VERSION "${QWT_VERSION_STRING}" ) + string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" QWT_MINOR_VERSION "${QWT_VERSION_STRING}" ) + string ( REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3" QWT_PATCH_VERSION "${QWT_VERSION_STRING}" ) + endif () +endif () + + +# check version +set ( _QWT_VERSION_MATCH TRUE ) +if ( Qwt_FIND_VERSION AND QWT_VERSION_STRING ) + if ( Qwt_FIND_VERSION_EXACT ) + if ( NOT Qwt_FIND_VERSION VERSION_EQUAL QWT_VERSION_STRING ) + set ( _QWT_VERSION_MATCH FALSE ) + endif () + else () + if ( QWT_VERSION_STRING VERSION_LESS Qwt_FIND_VERSION ) + set ( _QWT_VERSION_MATCH FALSE ) + endif () + endif () +endif () + + +find_library ( QWT_LIBRARY + NAMES qwt qwt-qt3 qwt-qt4 qwt-qt5 + HINTS ${QT_LIBRARY_DIR} "${QWT_GLOB_DIR}/lib" +) + +set ( QWT_LIBRARIES ${QWT_LIBRARY} ) + + +# try to guess root dir from include dir +if ( QWT_INCLUDE_DIR ) + string ( REGEX REPLACE "(.*)/include.*" "\\1" QWT_ROOT_DIR ${QWT_INCLUDE_DIR} ) +# try to guess root dir from library dir +elseif ( QWT_LIBRARY ) + string ( REGEX REPLACE "(.*)/lib[/|32|64].*" "\\1" QWT_ROOT_DIR ${QWT_LIBRARY} ) +endif () + + +# handle the QUIETLY and REQUIRED arguments +include ( FindPackageHandleStandardArgs ) +if ( CMAKE_VERSION LESS 2.8.3 ) + find_package_handle_standard_args( Qwt DEFAULT_MSG QWT_LIBRARY QWT_INCLUDE_DIR _QWT_VERSION_MATCH ) +else () + find_package_handle_standard_args( Qwt REQUIRED_VARS QWT_LIBRARY QWT_INCLUDE_DIR _QWT_VERSION_MATCH VERSION_VAR QWT_VERSION_STRING ) +endif () + + +mark_as_advanced ( + QWT_LIBRARY + QWT_LIBRARIES + QWT_INCLUDE_DIR + QWT_INCLUDE_DIRS + QWT_MAJOR_VERSION + QWT_MINOR_VERSION + QWT_PATCH_VERSION + QWT_VERSION_STRING + QWT_ROOT_DIR +) diff --git a/customcheckablebutton.cpp b/customcheckablebutton.cpp new file mode 100644 --- /dev/null +++ b/customcheckablebutton.cpp @@ -0,0 +1,12 @@ +#include "customcheckablebutton.h" + +CustomCheckableButton::CustomCheckableButton(QWidget *parent) : + QPushButton(parent) +{ + this->setCheckable(true); +} + +void CustomCheckableButton::nextCheckState() +{ + /* Do nothing! Check state will be altered by parent. */ +} diff --git a/customcheckablebutton.h b/customcheckablebutton.h new file mode 100644 --- /dev/null +++ b/customcheckablebutton.h @@ -0,0 +1,21 @@ +#ifndef CUSTOMCHECKABLEBUTTON_H +#define CUSTOMCHECKABLEBUTTON_H + +#include + +class CustomCheckableButton : public QPushButton +{ + Q_OBJECT +public: + explicit CustomCheckableButton(QWidget *parent = 0); + +signals: + +public slots: + +private: + void nextCheckState(); + +}; + +#endif // CUSTOMCHECKABLEBUTTON_H diff --git a/main.cpp b/main.cpp new file mode 100644 --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,150 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include + +#include "utils.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // init UI signals + QObject::connect(ui->pbReloadPorts, &QPushButton::clicked, + this, &MainWindow::loadPortList); + + QObject::connect(ui->pbOpenPort, &QPushButton::clicked, + this, &MainWindow::togglePort); + + QObject::connect(this, &MainWindow::portToggled, + this, &MainWindow::onPortToggled); + + QObject::connect(ui->cbPortList, + SELECT::OVERLOAD_OF(&QComboBox::activated), + this, &MainWindow::selectPort); + + QObject::connect(ui->cbBaudRate, + SELECT::OVERLOAD_OF(&QComboBox::activated), + this, &MainWindow::selectBaudRate); + + QObject::connect(&(this->serialPort), &QSerialPort::readyRead, + this, &MainWindow::onDataReady); + + loadPortList(); + loadBaudRateList(); + ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600")); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::loadPortList() +{ + QString currentSelection = ui->cbPortList->currentText(); + + ui->cbPortList->clear(); + + for (auto port : QSerialPortInfo::availablePorts()) + { + ui->cbPortList->addItem(port.portName()); + } + + // find current selection in the new list, maybe it doesn't exist anymore? + int currentSelectionIndex = ui->cbPortList->findText(currentSelection); + if (currentSelectionIndex >= 0) + { + ui->cbPortList->setCurrentIndex(currentSelectionIndex); + } + else // our port doesn't exist anymore, close port if it's open + { + if (serialPort.isOpen()) togglePort(); + } +} + +void MainWindow::loadBaudRateList() +{ + ui->cbBaudRate->clear(); + + for (auto baudRate : QSerialPortInfo::standardBaudRates()) + { + ui->cbBaudRate->addItem(QString::number(baudRate)); + } +} + +void MainWindow::togglePort() +{ + if (serialPort.isOpen()) + { + serialPort.close(); + qDebug() << "Port closed, " << serialPort.portName(); + emit portToggled(false); + } + else + { + serialPort.setPortName(ui->cbPortList->currentText()); + + // open port + if (serialPort.open(QIODevice::ReadWrite)) + { + qDebug() << "Port opened, " << serialPort.portName(); + emit portToggled(true); + + // set baud rate + if (!serialPort.setBaudRate(ui->cbBaudRate->currentText().toInt())) + { + qDebug() << "Set baud rate failed during port opening: " + << serialPort.error(); + } + } + else + { + qDebug() << "Port open error: " << serialPort.error(); + } + } +} + +void MainWindow::selectPort(QString portName) +{ + // has selection actually changed + if (portName != serialPort.portName()) + { + // if another port is already open, close it by toggling + if (serialPort.isOpen()) + { + togglePort(); + + // open new selection by toggling + togglePort(); + } + } +} + +void MainWindow::selectBaudRate(QString baudRate) +{ + if (serialPort.isOpen()) + { + if (!serialPort.setBaudRate(baudRate.toInt())) + { + qDebug() << "Set baud rate failed during select: " + << serialPort.error(); + } + else + { + qDebug() << "Baud rate changed: " << serialPort.baudRate(); + } + } +} + +void MainWindow::onPortToggled(bool open) +{ + ui->pbOpenPort->setChecked(open); +} + +void MainWindow::onDataReady() +{ + qDebug() << "Data: " << serialPort.readAll().toHex(); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,38 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + QSerialPort serialPort; + +private slots: + void loadPortList(); + void loadBaudRateList(); + void togglePort(); + void selectPort(QString portName); + void onPortToggled(bool open); + void selectBaudRate(QString baudRate); + + void onDataReady(); + +signals: + void portToggled(bool open); +}; + +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,425 @@ + + + MainWindow + + + + 0 + 0 + 652 + 534 + + + + MainWindow + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + 0 + + + false + + + + Port + + + + + + + + + + Port: + + + + + + + + 0 + 0 + + + + + + + + Qt::ImhPreferNumbers + + + true + + + + + + + Baud Rate: + + + + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + 🔄 + + + + + + + + + + + QFrame::NoFrame + + + + + + No Parity + + + + + + + Odd Parity + + + + + + + Even Parity + + + + + + + Qt::Vertical + + + + 20 + 2 + + + + + + + + + + + + + + 8 bits + + + + + + + 7 bits + + + + + + + 6 bits + + + + + + + 5 bits + + + + + + + + + + + + + 1 Stop Bit + + + + + + + 2 Stop Bit + + + + + + + Qt::Vertical + + + + 20 + 2 + + + + + + + + + + + + + + No Flow Control + + + + + + + Hardware Control + + + + + + + Software Control + + + + + + + Qt::Vertical + + + + 20 + 2 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 10000 + 20 + + + + + + + + + + + 0 + 50 + + + + Open + + + true + + + + + + + Skip Byte + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Tab 2 + + + + + + + + + + 0 + 0 + 652 + 20 + + + + + File + + + + + + + Help + + + + + + + + + + TopToolBarArea + + + false + + + + + + + + true + + + Start + + + + + Clear + + + + + Data Formats + + + + + About + + + + + Load Profile + + + + + Save Profile + + + + + + + QwtPlot + QWidget +
qwt_plot.h
+ 1 +
+ + CustomCheckableButton + QPushButton +
customcheckablebutton.h
+
+
+ + +
diff --git a/serialplot.pro b/serialplot.pro new file mode 100644 --- /dev/null +++ b/serialplot.pro @@ -0,0 +1,27 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2015-03-04T08:20:06 +# +#------------------------------------------------- + +QT += core gui serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = serialplot +TEMPLATE = app + +CONFIG += qwt + + +SOURCES += main.cpp\ + mainwindow.cpp \ + customcheckablebutton.cpp + +HEADERS += mainwindow.h \ + utils.h \ + customcheckablebutton.h + +FORMS += mainwindow.ui + +CONFIG += c++11 diff --git a/utils.h b/utils.h new file mode 100644 --- /dev/null +++ b/utils.h @@ -0,0 +1,8 @@ + +// credits: peppe@stackoverflow [http://stackoverflow.com/a/16795664/432492] +template struct SELECT { + template + static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { + return pmf; + } +};