Changeset - 3b97826f2f6b
[Not reviewed]
Merge default
0 4 13
Hasan Yavuz ÖZDERYA - 10 years ago 2015-10-18 05:28:08
hy@ozderya.net
Merge with command-panel
17 files changed with 776 insertions and 5 deletions:
0 comments (0 inline, 0 general)
CMakeLists.txt
Show inline comments
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
cmake_minimum_required(VERSION 2.8.11)
 

	
 
project(serialplot)
 

	
 
set(PROGRAM_NAME ${CMAKE_PROJECT_NAME} CACHE STRING "Output program name")
 
set(PROGRAM_DISPLAY_NAME "SerialPlot" CACHE STRING "Display name (menus etc) of the program")
 

	
 
# 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 QWT or use static
 
set(QWT_USE_STATIC false CACHE BOOL "Use a static version of Qwt provided by user.")
 
set(QWT_STATIC_LIBRARY "" CACHE FILEPATH "Path to the static Qwt library, libqwt.a.")
 
set(QWT_STATIC_INCLUDE "" CACHE PATH "Path to the Qwt include directory when building Qwt static.")
 

	
 
if (QWT_USE_STATIC)
 
  set(QWT_LIBRARY ${QWT_STATIC_LIBRARY})
 
  set(QWT_INCLUDE_DIR ${QWT_STATIC_INCLUDE})
 
else (QWT_USE_STATIC)
 
  find_package(Qwt 6.1 REQUIRED)
 
endif (QWT_USE_STATIC)
 

	
 
# includes
 
include_directories(${QWT_INCLUDE_DIR})
 

	
 
# wrap UI and resource files
 
qt5_wrap_ui(UI_FILES
 
  mainwindow.ui
 
  portcontrol.ui
 
  about_dialog.ui
 
  snapshotview.ui
 
  commandpanel.ui
 
  commandwidget.ui
 
  )
 

	
 
if (WIN32)
 
  qt5_add_resources(RES_FILES misc/icons.qrc misc/winicons.qrc)
 
else (WIN32)
 
qt5_add_resources(RES_FILES misc/icons.qrc)
 
endif (WIN32)
 

	
 
add_executable(${PROGRAM_NAME} WIN32
 
  main.cpp
 
  mainwindow.cpp
 
  portcontrol.cpp
 
  plot.cpp
 
  zoomer.cpp
 
  hidabletabwidget.cpp
 
  framebuffer.cpp
 
  scalepicker.cpp
 
  scalezoomer.cpp
 
  portlist.cpp
 
  snapshot.cpp
 
  snapshotview.cpp
 
  snapshotmanager.cpp
 
  plotsnapshotoverlay.cpp
 
  commandpanel.cpp
 
  commandwidget.cpp
 
  commandedit.cpp
 
  ${UI_FILES}
 
  ${RES_FILES}
 
  misc/windows_icon.rc
 
  )
 

	
 
# Use the Widgets module from Qt 5.
 
target_link_libraries(${PROGRAM_NAME} ${QWT_LIBRARY})
 
qt5_use_modules(${PROGRAM_NAME} Widgets SerialPort Svg)
 

	
 
# set compiler flags
 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
 

	
 
# 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()
 

	
 
# version number
 
set(MAJOR_VERSION 0 CACHE INT "Program major version number.")
 
set(MINOR_VERSION 5 CACHE INT "Program minor version number.")
 
set(PATCH_VERSION 1 CACHE INT "Program patch version number.")
 
set(VERSION_STRING "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}")
 

	
 
# get revision number from mercurial
 
find_program(MERCURIAL hg)
 

	
 
if (MERCURIAL)
 
  execute_process(COMMAND ${MERCURIAL} id -i
 
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 
    RESULT_VARIABLE MERCURIAL_RESULT
 
    OUTPUT_VARIABLE VERSION_REVISION
 
    OUTPUT_STRIP_TRAILING_WHITESPACE)
 
  if(NOT MERCURIAL_RESULT EQUAL 0)
 
    set(VERSION_SCM_REVISION false)
 
  endif(NOT MERCURIAL_RESULT EQUAL 0)
 
endif (MERCURIAL)
 

	
 
if (NOT VERSION_REVISION)
 
  set(VERSION_REVISION "0")
 
endif (NOT VERSION_REVISION)
 

	
 
message("SCM revision: ${VERSION_REVISION}")
 

	
 
# configure version file
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/version.h")
 

	
 
# add make run target
 
add_custom_target(run
 
    COMMAND ${PROGRAM_NAME}
 
    DEPENDS ${PROGRAM_NAME}
 
    WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
 
)
 

	
 
# installing
 
install(TARGETS ${PROGRAM_NAME} DESTINATION bin)
 

	
 
# for windows put libraries to install directory
 
if (WIN32)
 
  file(GLOB WINDOWS_INSTALL_LIBRARIES
 
    "${CMAKE_BINARY_DIR}/windows_install_libraries/*.*")
 
  install(FILES ${WINDOWS_INSTALL_LIBRARIES} DESTINATION bin)
 
endif (WIN32)
 

	
 
# install menu item and icon
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.desktop.in"
 
  "${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop")
 
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/misc/program_name.png"
 
  "${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png" COPYONLY)
 

	
 
find_program(XDG-DESKTOP-MENU xdg-desktop-menu)
 
find_program(XDG-ICON-RESOURCE xdg-icon-resource)
 

	
 
if (UNIX)
 
  # first copy files to share/serialplot/
 
  install(FILES
 
    ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.desktop
 
    ${CMAKE_BINARY_DIR}/${PROGRAM_NAME}.png
 
    DESTINATION share/${PROGRAM_NAME})
 

	
 
  # install copyright file (in debian format)
 
  install(FILES
 
    ${CMAKE_SOURCE_DIR}/debian/copyright
 
    DESTINATION share/doc/${PROGRAM_NAME}/)
 

	
 
  install(CODE "
 
  execute_process(COMMAND ${XDG-DESKTOP-MENU} install --novendor ${CMAKE_INSTALL_PREFIX}/share/${PROGRAM_NAME}/${PROGRAM_NAME}.desktop)
 
  execute_process(COMMAND ${XDG-ICON-RESOURCE} install --novendor --size 256 ${CMAKE_INSTALL_PREFIX}/share/${PROGRAM_NAME}/${PROGRAM_NAME}.png)
 
")
 
endif (UNIX)
 

	
 
# uninstalling
 
configure_file(
 
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
 
  "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
 
  @ONLY)
 

	
 
if (UNIX)
 
  add_custom_target(uninstall
 
    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
 
    COMMAND ${XDG-DESKTOP-MENU} uninstall --novendor ${PROGRAM_NAME}.desktop
 
    COMMAND ${XDG-ICON-RESOURCE} uninstall --novendor --size 256 ${PROGRAM_NAME})
 
endif (UNIX)
 

	
 
# packaging
 
if (UNIX)
 
  set(CPACK_GENERATOR "DEB")
 
elseif (WIN32)
 
  set(CPACK_GENERATOR "NSIS")
 
endif (UNIX)
 

	
 
include(InstallRequiredSystemLibraries)
 

	
 
# configure debian scripts
 
configure_file(${CMAKE_SOURCE_DIR}/debian/postinst.in ${CMAKE_BINARY_DIR}/debian/postinst)
 
configure_file(${CMAKE_SOURCE_DIR}/debian/postrm.in ${CMAKE_BINARY_DIR}/debian/postrm)
 

	
 
set(CPACK_PACKAGE_NAME "${PROGRAM_NAME}")
 
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Small and simple software for plotting data from serial port")
 
set(CPACK_PACKAGE_CONTACT "Hasan Yavuz Özderya <hy@ozderya.net>")
 
set(CPACK_PACKAGE_VERSION_MAJOR ${MAJOR_VERSION})
 
set(CPACK_PACKAGE_VERSION_MINOR ${MINOR_VERSION})
 
set(CPACK_PACKAGE_VERSION_PATCH ${PATCH_VERSION})
 
set(CPACK_STRIP_FILES TRUE)
 
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5widgets5 (>= 5.2.1), libqt5serialport5 (>= 5.2.1), libc6 (>= 2.19)")
 
if (NOT QWT_USE_STATIC)
 
  set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}, libqwt6-qt5 (>= 6.1.1)")
 
endif (NOT QWT_USE_STATIC)
 
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
 
  "${CMAKE_BINARY_DIR}/debian/postinst"
 
  "${CMAKE_BINARY_DIR}/debian/postrm")
 

	
 
if (UNIX)
 
  set(CPACK_PACKAGE_EXECUTABLES "${PROGRAM_NAME}")
 
elseif (WIN32)
 
  set(CPACK_PACKAGE_EXECUTABLES "${PROGRAM_NAME};${PROGRAM_DISPLAY_NAME}")
 
  set(CPACK_CREATE_DESKTOP_LINKS "${PROGRAM_NAME}")
 
  set(CPACK_NSIS_MODIFY_PATH "ON")
 
  set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/misc/serialplot.bmp")
 
  string(REPLACE "/" "\\\\" CPACK_PACKAGE_ICON ${CPACK_PACKAGE_ICON})
 
  set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING")
 
  set(CPACK_NSIS_MENU_LINKS
 
    "https://bitbucket.org/hyOzd/serialplot" "SerialPlot source code on bitbucket.org")
 
endif (UNIX)
 

	
 
if (UNIX)
 
  # set debian package name
 
  string(TOLOWER "${CPACK_PACKAGE_NAME}" CPACK_PACKAGE_NAME_LOWERCASE)
 
  find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems")
 
  if(DPKG_PROGRAM)
 
    execute_process(
 
      COMMAND ${DPKG_PROGRAM} --print-architecture
 
      OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
 
      OUTPUT_STRIP_TRAILING_WHITESPACE)
 
    set(CPACK_PACKAGE_FILE_NAME
 
      "${CPACK_PACKAGE_NAME_LOWERCASE}_${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
 
  else(DPKG_PROGRAM)
 
    set(CPACK_PACKAGE_FILE_NAME
 
      "${CPACK_PACKAGE_NAME_LOWERCASE}_${PROJECT_VERSION}-${PROJECT_VERSION_REVISION}_${CMAKE_SYSTEM_NAME}")
 
  endif(DPKG_PROGRAM)
 
endif (UNIX)
 

	
 
include(CPack)
commandedit.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include <QKeyEvent>
 

	
 
#include <QtDebug>
 

	
 
#include "commandedit.h"
 

	
 
class HexCommandValidator : public QRegExpValidator
 
{
 
public:
 
    explicit HexCommandValidator(QObject* parent = 0);
 
    QValidator::State validate(QString & input, int & pos) const;
 
};
 

	
 
HexCommandValidator::HexCommandValidator(QObject* parent) :
 
    QRegExpValidator(parent)
 
{
 
    QRegExp regExp("^(?:(?:[0-9A-F]{2}[ ])+(?:[0-9A-F]{2}))|(?:[0-9A-F]{2})$");
 
    setRegExp(regExp);
 
}
 

	
 
QValidator::State HexCommandValidator::validate(QString & input, int & pos) const
 
{
 
    input = input.toUpper();
 

	
 
    // don't let pos to be altered at this stage
 
    int orgPos = pos;
 
    auto r = QRegExpValidator::validate(input, pos);
 
    pos = orgPos;
 

	
 
    // try fixing up spaces
 
    if (r != QValidator::Acceptable)
 
    {
 
        input = input.replace(" ", "");
 
        input.replace(QRegExp("([0-9A-F]{2}(?!$))"), "\\1 ");
 
        if (pos == input.size()-1) pos = input.size();
 
        r = QRegExpValidator::validate(input, pos);
 
    }
 

	
 
    return r;
 
}
 

	
 
CommandEdit::CommandEdit(QWidget *parent) :
 
    QLineEdit(parent)
 
{
 
    hexValidator = new HexCommandValidator(this);
 
    asciiValidator = new QRegExpValidator(QRegExp("[\\x0000-\\x007F]+"));
 
    ascii_mode = true;
 
    setValidator(asciiValidator);
 
}
 

	
 
CommandEdit::~CommandEdit()
 
{
 
    delete hexValidator;
 
}
 

	
 
QString unEscape(QString str);
 
QString escape(QString str);
 

	
 
void CommandEdit::setMode(bool ascii)
 
{
 
    ascii_mode = ascii;
 
    if (ascii)
 
    {
 
        setValidator(asciiValidator);
 

	
 
        auto hexText = text().remove(" ");
 
        // try patching HEX string in case of missing nibble so that
 
        // input doesn't turn into gibberish
 
        if (hexText.size() % 2 == 1)
 
        {
 
            hexText.replace(hexText.size()-1, 1, "3F"); // 0x3F = '?'
 
            qWarning() << "Broken byte in hex command is replaced. Check your command!";
 
        }
 

	
 
        setText(escape(QByteArray::fromHex(hexText.toLatin1())));
 
    }
 
    else
 
    {
 
        setValidator(hexValidator);
 
        setText(unEscape(text()).toLatin1().toHex());
 
    }
 
}
 

	
 
void CommandEdit::keyPressEvent(QKeyEvent * event)
 
{
 
    if (ascii_mode)
 
    {
 
        QLineEdit::keyPressEvent(event);
 
        return;
 
    }
 

	
 
    if (event->key() == Qt::Key_Backspace && !hasSelectedText())
 
    {
 
        int cursor = cursorPosition();
 
        if (cursor != 0 && text()[cursor-1] == ' ')
 
        {
 
            setCursorPosition(cursor-1);
 
        }
 
    }
 

	
 
    QLineEdit::keyPressEvent(event);
 
}
 

	
 
QString CommandEdit::unEscapedText()
 
{
 
    return unEscape(text());
 
}
 

	
 
QString unEscape(QString str)
 
{
 
    str.replace("\\n", "\n");
 
    str.replace("\\r", "\r");
 
    str.replace("\\t", "\t");
 
    return str;
 
}
 

	
 
QString escape(QString str)
 
{
 
    str.replace("\n", "\\n");
 
    str.replace("\r", "\\r");
 
    str.replace("\t", "\\t");
 
    return str;
 
}
commandedit.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef COMMANDEDIT_H
 
#define COMMANDEDIT_H
 

	
 
#include <QLineEdit>
 
#include <QValidator>
 
#include <QString>
 

	
 
class CommandEdit : public QLineEdit
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit CommandEdit(QWidget *parent = 0);
 
    ~CommandEdit();
 
    void setMode(bool ascii); // true = ascii, false = hex
 
    QString unEscapedText(); // return unescaped text(), used in ascii_mode only
 

	
 
private:
 
    bool ascii_mode;
 
    QValidator* hexValidator;
 
    QValidator* asciiValidator;
 

	
 
protected:
 
    void keyPressEvent(QKeyEvent * event) Q_DECL_OVERRIDE;
 
};
 

	
 
#endif // COMMANDEDIT_H
commandpanel.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "commandpanel.h"
 
#include "ui_commandpanel.h"
 

	
 
#include <QByteArray>
 
#include <QtDebug>
 

	
 
CommandPanel::CommandPanel(QSerialPort* port, QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::CommandPanel)
 
{
 
    serialPort = port;
 

	
 
    ui->setupUi(this);
 
    ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout);
 

	
 
#ifdef Q_OS_WIN
 
    ui->pbNew->setIcon(QIcon(":/icons/list-add"));
 
#endif // Q_OS_WIN
 

	
 
    connect(ui->pbNew, &QPushButton::clicked, this, &CommandPanel::newCommand);
 

	
 
    newCommand(); // add an empty slot by default
 
}
 

	
 
CommandPanel::~CommandPanel()
 
{
 
    delete ui;
 
}
 

	
 
void CommandPanel::newCommand()
 
{
 
    auto command = new CommandWidget();
 
    ui->scrollAreaWidgetContents->layout()->addWidget(command);
 
    connect(command, &CommandWidget::sendCommand, this, &CommandPanel::sendCommand);
 
}
 

	
 
void CommandPanel::sendCommand(QByteArray command)
 
{
 
    if (!serialPort->isOpen())
 
    {
 
        qCritical() << "Port is not open!";
 
        return;
 
    }
 

	
 
    if (serialPort->write(command) < 0)
 
    {
 
        qCritical() << "Send command failed!";
 
    }
 
}
commandpanel.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef COMMANDPANEL_H
 
#define COMMANDPANEL_H
 

	
 
#include <QWidget>
 
#include <QSerialPort>
 
#include <QByteArray>
 

	
 
#include "commandwidget.h"
 

	
 
namespace Ui {
 
class CommandPanel;
 
}
 

	
 
class CommandPanel : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit CommandPanel(QSerialPort* port, QWidget *parent = 0);
 
    ~CommandPanel();
 

	
 
private:
 
    Ui::CommandPanel *ui;
 
    QSerialPort* serialPort;
 

	
 
private slots:
 
    void newCommand();
 
    void sendCommand(QByteArray command);
 
};
 

	
 
#endif // COMMANDPANEL_H
commandpanel.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>CommandPanel</class>
 
 <widget class="QWidget" name="CommandPanel">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>583</width>
 
    <height>109</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <property name="autoFillBackground">
 
   <bool>false</bool>
 
  </property>
 
  <layout class="QVBoxLayout" name="verticalLayout">
 
   <item>
 
    <widget class="QScrollArea" name="scrollArea">
 
     <property name="frameShape">
 
      <enum>QFrame::StyledPanel</enum>
 
     </property>
 
     <property name="widgetResizable">
 
      <bool>true</bool>
 
     </property>
 
     <property name="alignment">
 
      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
 
     </property>
 
     <widget class="QWidget" name="scrollAreaWidgetContents">
 
      <property name="geometry">
 
       <rect>
 
        <x>0</x>
 
        <y>0</y>
 
        <width>563</width>
 
        <height>16</height>
 
       </rect>
 
      </property>
 
      <property name="sizePolicy">
 
       <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
 
        <horstretch>0</horstretch>
 
        <verstretch>0</verstretch>
 
       </sizepolicy>
 
      </property>
 
     </widget>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QPushButton" name="pbNew">
 
     <property name="text">
 
      <string>New Command</string>
 
     </property>
 
     <property name="icon">
 
      <iconset theme="list-add"/>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <resources/>
 
 <connections/>
 
</ui>
commandwidget.cpp
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "commandwidget.h"
 
#include "ui_commandwidget.h"
 

	
 
#include <QRegExp>
 
#include <QRegExpValidator>
 
#include <QtDebug>
 
#include <QIcon>
 

	
 
CommandWidget::CommandWidget(QWidget *parent) :
 
    QWidget(parent),
 
    ui(new Ui::CommandWidget)
 
{
 
    ui->setupUi(this);
 

	
 
#ifdef Q_OS_WIN
 
    ui->pbDelete->setIcon(QIcon(":/icons/list-remove"));
 
#endif // Q_OS_WIN
 

	
 
    connect(ui->pbDelete, &QPushButton::clicked, this, &CommandWidget::onDeleteClicked);
 
    connect(ui->pbSend, &QPushButton::clicked, this, &CommandWidget::onSendClicked);
 
    connect(ui->pbASCII, &QPushButton::toggled, this, &CommandWidget::onASCIIToggled);
 
}
 

	
 
CommandWidget::~CommandWidget()
 
{
 
    delete ui;
 
}
 

	
 
void CommandWidget::onDeleteClicked()
 
{
 
    this->deleteLater();
 
}
 

	
 
void CommandWidget::onSendClicked()
 
{
 
    auto command = ui->leCommand->text();
 

	
 
    if (command.isEmpty())
 
    {
 
        qWarning() << "Enter a command to send!";
 
        ui->leCommand->setFocus(Qt::OtherFocusReason);
 
        return;
 
    }
 

	
 
    if (isASCIIMode())
 
    {
 
        qDebug() << "Sending:" << command;
 
        emit sendCommand(ui->leCommand->unEscapedText().toLatin1());
 
    }
 
    else // hex mode
 
    {
 
        command = command.remove(' ');
 
        // check if nibbles are missing
 
        if (command.size() % 2 == 1)
 
        {
 
            qWarning() << "HEX command is missing a nibble at the end!";
 
            ui->leCommand->setFocus(Qt::OtherFocusReason);
 
            // highlight the byte that is missing a nibble (last byte obviously)
 
            int textSize = ui->leCommand->text().size();
 
            ui->leCommand->setSelection(textSize-1, textSize);
 
            return;
 
        }
 
        qDebug() << "Sending HEX:" << command;
 
        emit sendCommand(QByteArray::fromHex(command.toLatin1()));
 
    }
 
}
 

	
 
void CommandWidget::onASCIIToggled(bool checked)
 
{
 
    ui->leCommand->setMode(checked);
 
}
 

	
 
bool CommandWidget::isASCIIMode()
 
{
 
    return ui->pbASCII->isChecked();
 
}
commandwidget.h
Show inline comments
 
new file 100644
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef COMMANDWIDGET_H
 
#define COMMANDWIDGET_H
 

	
 
#include <QWidget>
 
#include <QByteArray>
 

	
 
namespace Ui {
 
class CommandWidget;
 
}
 

	
 
class CommandWidget : public QWidget
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit CommandWidget(QWidget *parent = 0);
 
    ~CommandWidget();
 

	
 
signals:
 
    void deleteRequested(CommandWidget* thisWidget); // emitted when delete button is clicked
 

	
 
    // emitted when send button is clicked
 
    //
 
    // in case of hex mode, command text should be a hexadecimal
 
    // string containing hexadecimal characters only (not even spaces)
 
    void sendCommand(QByteArray command);
 

	
 
private:
 
    Ui::CommandWidget *ui;
 

	
 
    bool isASCIIMode(); // true: ascii mode, false hex mode
 

	
 
private slots:
 
    void onDeleteClicked();
 
    void onSendClicked();
 
    void onASCIIToggled(bool checked);
 
};
 

	
 
#endif // COMMANDWIDGET_H
commandwidget.ui
Show inline comments
 
new file 100644
 
<?xml version="1.0" encoding="UTF-8"?>
 
<ui version="4.0">
 
 <class>CommandWidget</class>
 
 <widget class="QWidget" name="CommandWidget">
 
  <property name="geometry">
 
   <rect>
 
    <x>0</x>
 
    <y>0</y>
 
    <width>433</width>
 
    <height>34</height>
 
   </rect>
 
  </property>
 
  <property name="windowTitle">
 
   <string>Form</string>
 
  </property>
 
  <layout class="QHBoxLayout" name="horizontalLayout_2">
 
   <property name="leftMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="topMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="rightMargin">
 
    <number>0</number>
 
   </property>
 
   <property name="bottomMargin">
 
    <number>0</number>
 
   </property>
 
   <item>
 
    <widget class="QToolButton" name="pbDelete">
 
     <property name="minimumSize">
 
      <size>
 
       <width>30</width>
 
       <height>0</height>
 
      </size>
 
     </property>
 
     <property name="text">
 
      <string/>
 
     </property>
 
     <property name="icon">
 
      <iconset theme="list-remove">
 
       <normaloff/>
 
      </iconset>
 
     </property>
 
     <property name="autoRaise">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="CommandEdit" name="leCommand">
 
     <property name="sizePolicy">
 
      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
 
       <horstretch>0</horstretch>
 
       <verstretch>0</verstretch>
 
      </sizepolicy>
 
     </property>
 
     <property name="toolTip">
 
      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter your command here.&lt;/p&gt;&lt;p&gt;In ASCII mode you can use backslash '\' to send some special characters. These are:&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;\n&lt;/span&gt; : Line Feed (new line)&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;\r&lt;/span&gt; : Carriage Return&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;\t&lt;/span&gt; : Tab&lt;/p&gt;&lt;p&gt;Be careful though, you cannot escape the backslash character itself!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
 
     </property>
 
     <property name="clearButtonEnabled">
 
      <bool>true</bool>
 
     </property>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QFrame" name="frame">
 
     <property name="sizePolicy">
 
      <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
 
       <horstretch>0</horstretch>
 
       <verstretch>0</verstretch>
 
      </sizepolicy>
 
     </property>
 
     <property name="frameShape">
 
      <enum>QFrame::StyledPanel</enum>
 
     </property>
 
     <property name="frameShadow">
 
      <enum>QFrame::Raised</enum>
 
     </property>
 
     <layout class="QHBoxLayout" name="horizontalLayout">
 
      <property name="spacing">
 
       <number>0</number>
 
      </property>
 
      <property name="leftMargin">
 
       <number>0</number>
 
      </property>
 
      <property name="topMargin">
 
       <number>0</number>
 
      </property>
 
      <property name="bottomMargin">
 
       <number>0</number>
 
      </property>
 
      <item>
 
       <widget class="QPushButton" name="pbASCII">
 
        <property name="sizePolicy">
 
         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
 
          <horstretch>0</horstretch>
 
          <verstretch>0</verstretch>
 
         </sizepolicy>
 
        </property>
 
        <property name="minimumSize">
 
         <size>
 
          <width>40</width>
 
          <height>0</height>
 
         </size>
 
        </property>
 
        <property name="text">
 
         <string>ASCII</string>
 
        </property>
 
        <property name="checkable">
 
         <bool>true</bool>
 
        </property>
 
        <property name="checked">
 
         <bool>true</bool>
 
        </property>
 
        <property name="autoExclusive">
 
         <bool>true</bool>
 
        </property>
 
       </widget>
 
      </item>
 
      <item>
 
       <widget class="QPushButton" name="pbHEX">
 
        <property name="sizePolicy">
 
         <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
 
          <horstretch>0</horstretch>
 
          <verstretch>0</verstretch>
 
         </sizepolicy>
 
        </property>
 
        <property name="minimumSize">
 
         <size>
 
          <width>40</width>
 
          <height>0</height>
 
         </size>
 
        </property>
 
        <property name="text">
 
         <string>HEX</string>
 
        </property>
 
        <property name="checkable">
 
         <bool>true</bool>
 
        </property>
 
        <property name="autoExclusive">
 
         <bool>true</bool>
 
        </property>
 
       </widget>
 
      </item>
 
     </layout>
 
    </widget>
 
   </item>
 
   <item>
 
    <widget class="QPushButton" name="pbSend">
 
     <property name="sizePolicy">
 
      <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
 
       <horstretch>0</horstretch>
 
       <verstretch>0</verstretch>
 
      </sizepolicy>
 
     </property>
 
     <property name="text">
 
      <string>Send</string>
 
     </property>
 
    </widget>
 
   </item>
 
  </layout>
 
 </widget>
 
 <customwidgets>
 
  <customwidget>
 
   <class>CommandEdit</class>
 
   <extends>QLineEdit</extends>
 
   <header>commandedit.h</header>
 
  </customwidget>
 
 </customwidgets>
 
 <resources/>
 
 <connections/>
 
</ui>
mainwindow.cpp
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#include "mainwindow.h"
 
#include "ui_mainwindow.h"
 
#include <QByteArray>
 
#include <QApplication>
 
#include <QFileDialog>
 
#include <QFile>
 
#include <QTextStream>
 
#include <QtDebug>
 
#include <QtEndian>
 
#include <qwt_plot.h>
 
#include <limits.h>
 
#include <cmath>
 
#include <iostream>
 

	
 
#include <plot.h>
 

	
 
#include "utils.h"
 
#include "version.h"
 
#include "floatswap.h"
 

	
 
#if defined(Q_OS_WIN) && defined(QT_STATIC)
 
#include <QtPlugin>
 
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
 
#endif
 

	
 
struct Range
 
{
 
    double rmin;
 
    double rmax;
 
};
 

	
 
Q_DECLARE_METATYPE(Range);
 

	
 
MainWindow::MainWindow(QWidget *parent) :
 
    QMainWindow(parent),
 
    ui(new Ui::MainWindow),
 
    portControl(&serialPort),
 
    commandPanel(&serialPort),
 
    snapshotMan(this, &channelBuffers)
 
{
 
    ui->setupUi(this);
 
    ui->tabWidget->insertTab(0, &portControl, "Port");
 
    ui->tabWidget->insertTab(3, &commandPanel, "Commands");
 
    ui->tabWidget->setCurrentIndex(0);
 
    addToolBar(portControl.toolBar());
 

	
 
    ui->mainToolBar->addAction(snapshotMan.takeSnapshotAction());
 
    ui->menuBar->insertMenu(ui->menuHelp->menuAction(), snapshotMan.menu());
 

	
 
    setupAboutDialog();
 

	
 
    // init view menu
 
    for (auto a : ui->plot->menuActions())
 
    {
 
        ui->menuView->addAction(a);
 
    }
 

	
 
    // init UI signals
 

	
 
    // menu signals
 
    QObject::connect(ui->actionHelpAbout, &QAction::triggered,
 
              &aboutDialog, &QWidget::show);
 

	
 
    QObject::connect(ui->actionExportCsv, &QAction::triggered,
 
                     this, &MainWindow::onExportCsv);
 

	
 
    ui->actionQuit->setShortcutContext(Qt::ApplicationShortcut);
 

	
 
    QObject::connect(ui->actionQuit, &QAction::triggered,
 
                     this, &MainWindow::close);
 

	
 
    // port control signals
 
    QObject::connect(&portControl, &PortControl::portToggled,
 
                     this, &MainWindow::onPortToggled);
 

	
 
    QObject::connect(&portControl, &PortControl::skipByteRequested,
 
                     this, &MainWindow::skipByte);
 

	
 
    QObject::connect(ui->spNumOfSamples, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
                     this, &MainWindow::onNumOfSamplesChanged);
 

	
 
    QObject::connect(ui->cbAutoScale, &QCheckBox::toggled,
 
                     this, &MainWindow::onAutoScaleChecked);
 

	
 
    QObject::connect(ui->spYmin, SIGNAL(valueChanged(double)),
 
                     this, SLOT(onYScaleChanged()));
 

	
 
    QObject::connect(ui->spYmax, SIGNAL(valueChanged(double)),
 
                     this, SLOT(onYScaleChanged()));
 

	
 
    QObject::connect(ui->actionClear, SIGNAL(triggered(bool)),
 
                     this, SLOT(clearPlot()));
 

	
 
    QObject::connect(snapshotMan.takeSnapshotAction(), &QAction::triggered,
 
                     ui->plot, &Plot::flashSnapshotOverlay);
 

	
 
    // setup number of channels spinbox
 
    QObject::connect(ui->spNumOfChannels,
 
                     SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged),
 
                     this, &MainWindow::onNumOfChannelsChanged);
 

	
 
    // setup number format buttons
 
    numberFormatButtons.addButton(ui->rbUint8,  NumberFormat_uint8);
 
    numberFormatButtons.addButton(ui->rbUint16, NumberFormat_uint16);
 
    numberFormatButtons.addButton(ui->rbUint32, NumberFormat_uint32);
 
    numberFormatButtons.addButton(ui->rbInt8,   NumberFormat_int8);
 
    numberFormatButtons.addButton(ui->rbInt16,  NumberFormat_int16);
 
    numberFormatButtons.addButton(ui->rbInt32,  NumberFormat_int32);
 
    numberFormatButtons.addButton(ui->rbFloat,  NumberFormat_float);
 
    numberFormatButtons.addButton(ui->rbASCII,  NumberFormat_ASCII);
 

	
 
    QObject::connect(&numberFormatButtons, SIGNAL(buttonToggled(int, bool)),
 
                     this, SLOT(onNumberFormatButtonToggled(int, bool)));
 

	
 
    // init port signals
 
    QObject::connect(&(this->serialPort), SIGNAL(error(QSerialPort::SerialPortError)),
 
                     this, SLOT(onPortError(QSerialPort::SerialPortError)));
 

	
 
    // set limits for axis limit boxes
 
    ui->spYmin->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 

	
 
    ui->spYmax->setRange((-1) * std::numeric_limits<double>::max(),
 
                         std::numeric_limits<double>::max());
 

	
 
    // init data arrays and plot
 

	
 
    numOfSamples = ui->spNumOfSamples->value();
 
    numOfChannels = ui->spNumOfChannels->value();
 

	
 
    // init channel data and curve list
 
    for (unsigned int i = 0; i < numOfChannels; i++)
 
    {
 
        channelBuffers.append(new FrameBuffer(numOfSamples));
 
        curves.append(new QwtPlotCurve());
 
        curves[i]->setSamples(channelBuffers[i]);
 
        curves[i]->setPen(Plot::makeColor(i));
 
        curves[i]->attach(ui->plot);
 
    }
 

	
 
    // init auto scale
 
    ui->plot->setAxis(ui->cbAutoScale->isChecked(),
 
                      ui->spYmin->value(), ui->spYmax->value());
 

	
 
    // init scale range preset list
 
    for (int nbits = 8; nbits <= 24; nbits++) // signed binary formats
 
    {
 
        int rmax = pow(2, nbits-1)-1;
 
        int rmin = -rmax-1;
 
        Range r = {double(rmin),  double(rmax)};
 
        ui->cbRangePresets->addItem(
 
            QString().sprintf("Signed %d bits %d to +%d", nbits, rmin, rmax),
 
            QVariant::fromValue(r));
 
    }
 
    for (int nbits = 8; nbits <= 24; nbits++) // unsigned binary formats
 
    {
 
        int rmax = pow(2, nbits)-1;
 
        ui->cbRangePresets->addItem(
 
            QString().sprintf("Unsigned %d bits %d to +%d", nbits, 0, rmax),
 
            QVariant::fromValue(Range{0, double(rmax)}));
 
    }
 
    ui->cbRangePresets->addItem("-1 to +1", QVariant::fromValue(Range{-1, +1}));
 
    ui->cbRangePresets->addItem("0 to +1", QVariant::fromValue(Range{0, +1}));
 
    ui->cbRangePresets->addItem("-100 to +100", QVariant::fromValue(Range{-100, +100}));
 
    ui->cbRangePresets->addItem("0 to +100", QVariant::fromValue(Range{0, +100}));
 

	
 
    QObject::connect(ui->cbRangePresets, SIGNAL(activated(int)),
 
                     this, SLOT(onRangeSelected()));
 

	
 
    // init number format
 
    if (numberFormatButtons.checkedId() >= 0)
 
    {
 
        selectNumberFormat((NumberFormat) numberFormatButtons.checkedId());
 
    }
 
    else
 
    {
 
        selectNumberFormat(NumberFormat_uint8);
 
    }
 

	
 
    // Init sps (sample per second) counter
 
    sampleCount = 0;
 
    spsLabel.setText("0sps");
 
    spsLabel.setToolTip("samples per second (total of all channels)");
 
    ui->statusBar->addPermanentWidget(&spsLabel);
 
    spsTimer.start(SPS_UPDATE_TIMEOUT * 1000);
 
    QObject::connect(&spsTimer, &QTimer::timeout,
 
                     this, &MainWindow::spsTimerTimeout);
 

	
 
    // Init demo mode
 
    demoCount = 0;
 
    demoTimer.setInterval(100);
 
    QObject::connect(&demoTimer, &QTimer::timeout,
 
                     this, &MainWindow::demoTimerTimeout);
 
    QObject::connect(ui->actionDemoMode, &QAction::toggled,
 
                     this, &MainWindow::enableDemo);
 

	
 
    {   // init demo indicator
 
        QwtText demoText(" DEMO RUNNING ");  // looks better with spaces
 
        demoText.setColor(QColor("white"));
 
        demoText.setBackgroundBrush(Qt::darkRed);
 
        demoText.setBorderRadius(4);
 
        demoText.setRenderFlags(Qt::AlignLeft | Qt::AlignTop);
 
        demoIndicator.setText(demoText);
 
        demoIndicator.hide();
 
        demoIndicator.attach(ui->plot);
 
    }
 

	
 
}
 

	
 
MainWindow::~MainWindow()
 
{
 
    for (auto curve : curves)
 
    {
 
        // also deletes respective FrameBuffer
 
        delete curve;
 
    }
 

	
 
    if (serialPort.isOpen())
 
    {
 
        serialPort.close();
 
    }
 

	
 
    delete ui;
 
    ui = NULL; // we check if ui is deleted in messageHandler
 
}
 

	
 
void MainWindow::setupAboutDialog()
 
{
 
    Ui_AboutDialog uiAboutDialog;
 
    uiAboutDialog.setupUi(&aboutDialog);
 

	
 
    QObject::connect(uiAboutDialog.pbAboutQt, &QPushButton::clicked,
 
                     [](){ QApplication::aboutQt();});
 

	
 
    QString aboutText = uiAboutDialog.lbAbout->text();
 
    aboutText.replace("$VERSION_STRING$", VERSION_STRING);
 
    aboutText.replace("$VERSION_REVISION$", VERSION_REVISION);
 
    uiAboutDialog.lbAbout->setText(aboutText);
 
}
 

	
 
void MainWindow::onPortToggled(bool open)
 
{
 
    // make sure demo mode is disabled
 
    if (open && isDemoRunning()) enableDemo(false);
 
    ui->actionDemoMode->setEnabled(!open);
 
}
 

	
 
void MainWindow::onDataReady()
 
{
 
    if (ui->actionPause->isChecked())
 
    {
 
        serialPort.clear(QSerialPort::Input);
 
        return;
 
    }
 

	
 
    // a package is a set of channel data like {CHAN0_SAMPLE, CHAN1_SAMPLE...}
 
    int packageSize = sampleSize * numOfChannels;
 
    int bytesAvailable = serialPort.bytesAvailable();
 

	
 
    if (bytesAvailable > 0 && skipByteRequested)
 
    {
 
        serialPort.read(1);
 
        skipByteRequested = false;
 
        bytesAvailable--;
 
    }
 

	
 
    if (bytesAvailable < packageSize) return;
 

	
 
    int numOfPackagesToRead =
 
        (bytesAvailable - (bytesAvailable % packageSize)) / packageSize;
 
    double* channelSamples = new double[numOfPackagesToRead*numOfChannels];
 

	
 
    int i = 0;
 
    while(i < numOfPackagesToRead)
 
    {
 
        for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
        {
 
            // channelSamples[ci].replace(i, (this->*readSample)());
 
            channelSamples[ci*numOfPackagesToRead+i] = (this->*readSample)();
 
        }
 
        i++;
 
    }
 

	
 
    for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
    {
 
        addChannelData(ci,
 
                       channelSamples + ci*numOfPackagesToRead,
 
                       numOfPackagesToRead);
 
    }
 
    ui->plot->replot();
 

	
 
    delete channelSamples;
 
}
 

	
 
void MainWindow::onDataReadyASCII()
 
{
 
    while(serialPort.canReadLine())
 
    {
 
        QByteArray line = serialPort.readLine();
 

	
 
        // discard data if paused
 
        if (ui->actionPause->isChecked())
 
        {
 
            return;
 
        }
 

	
 
        line = line.trimmed();
 
        auto separatedValues = line.split(',');
 

	
 
        int numReadChannels; // effective number of channels to read
 
        if (separatedValues.length() >= int(numOfChannels))
 
        {
 
            numReadChannels = numOfChannels;
 
        }
 
        else // there is missing channel data
 
        {
 
            numReadChannels = separatedValues.length();
 
            qWarning() << "Incoming data is missing data for some channels!";
 
        }
 

	
 
        // parse read line
 
        for (int ci = 0; ci < numReadChannels; ci++)
 
        {
 
            bool ok;
 
            double channelSample = separatedValues[ci].toDouble(&ok);
 
            if (ok)
 
            {
 
                addChannelData(ci, &channelSample, 1);
 
            }
 
            else
 
            {
 
                qWarning() << "Data parsing error for channel: " << ci;
 
            }
 
        }
 
        ui->plot->replot();
 
    }
 
}
 

	
 
void MainWindow::onPortError(QSerialPort::SerialPortError error)
 
{
 
    switch(error)
 
    {
 
        case QSerialPort::NoError :
 
            break;
 
        case QSerialPort::ResourceError :
 
            qWarning() << "Port error: resource unavaliable; most likely device removed.";
 
            if (serialPort.isOpen())
 
            {
 
                qWarning() << "Closing port on resource error: " << serialPort.portName();
 
                portControl.togglePort();
 
            }
 
            portControl.loadPortList();
 
            break;
 
        case QSerialPort::DeviceNotFoundError:
 
            qCritical() << "Device doesn't exists: " << serialPort.portName();
 
            break;
 
        case QSerialPort::PermissionError:
 
            qCritical() << "Permission denied. Either you don't have \
 
required privileges or device is already opened by another process.";
 
            break;
 
        case QSerialPort::OpenError:
 
            qWarning() << "Device is already opened!";
 
            break;
 
        case QSerialPort::NotOpenError:
 
            qCritical() << "Device is not open!";
 
            break;
 
        case QSerialPort::ParityError:
 
            qCritical() << "Parity error detected.";
 
            break;
 
        case QSerialPort::FramingError:
 
            qCritical() << "Framing error detected.";
 
            break;
 
        case QSerialPort::BreakConditionError:
 
            qCritical() << "Break condition is detected.";
 
            break;
 
        case QSerialPort::WriteError:
 
            qCritical() << "An error occurred while writing data.";
 
            break;
 
        case QSerialPort::ReadError:
 
            qCritical() << "An error occurred while reading data.";
 
            break;
 
        case QSerialPort::UnsupportedOperationError:
 
            qCritical() << "Operation is not supported.";
 
            break;
 
        case QSerialPort::TimeoutError:
 
            qCritical() << "A timeout error occurred.";
 
            break;
 
        case QSerialPort::UnknownError:
 
            qCritical() << "Unknown error!";
 
            break;
 
        default:
 
            qCritical() << "Unhandled port error: " << error;
 
            break;
 
    }
 
}
 

	
 
void MainWindow::skipByte()
 
{
 
    skipByteRequested = true;
 
}
 

	
 
void MainWindow::addChannelData(unsigned int channel, double* data, unsigned size)
 
{
 
    channelBuffers[channel]->addSamples(data, size);
 
    sampleCount += size;
 
}
 

	
 
void MainWindow::clearPlot()
 
{
 
    for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
    {
 
        channelBuffers[ci]->clear();
 
    }
 
    ui->plot->replot();
 
}
 

	
 
void MainWindow::onNumOfSamplesChanged(int value)
 
{
 
    numOfSamples = value;
 

	
 
    for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
    {
 
        channelBuffers[ci]->resize(numOfSamples);
 
    }
 

	
 
    ui->plot->replot();
 
}
 

	
 
void MainWindow::onNumOfChannelsChanged(int value)
 
{
 
    unsigned int oldNum = this->numOfChannels;
 
    this->numOfChannels = value;
 

	
 
    if (numOfChannels > oldNum)
 
    {
 
        // add new channels
 
        for (unsigned int i = 0; i < numOfChannels - oldNum; i++)
 
        {
 
            channelBuffers.append(new FrameBuffer(numOfSamples));
 
            curves.append(new QwtPlotCurve());
 
            curves.last()->setSamples(channelBuffers.last());
 
            curves.last()->setPen(Plot::makeColor(curves.length()-1));
 
            curves.last()->attach(ui->plot);
 
        }
 
    }
 
    else if(numOfChannels < oldNum)
 
    {
 
        // remove channels
 
        for (unsigned int i = 0; i < oldNum - numOfChannels; i++)
 
        {
 
            // also deletes owned FrameBuffer
 
            delete curves.takeLast();
 
            channelBuffers.removeLast();
 
        }
 
    }
 
}
 

	
 
void MainWindow::onAutoScaleChecked(bool checked)
 
{
 
    if (checked)
 
    {
 
        ui->plot->setAxis(true);
 
        ui->lYmin->setEnabled(false);
 
        ui->lYmax->setEnabled(false);
 
        ui->spYmin->setEnabled(false);
 
        ui->spYmax->setEnabled(false);
 
    }
 
    else
 
    {
 
        ui->lYmin->setEnabled(true);
 
        ui->lYmax->setEnabled(true);
 
        ui->spYmin->setEnabled(true);
 
        ui->spYmax->setEnabled(true);
 

	
 
        ui->plot->setAxis(false,  ui->spYmin->value(), ui->spYmax->value());
 
    }
 
}
 

	
 
void MainWindow::onYScaleChanged()
 
{
 
    ui->plot->setAxis(false,  ui->spYmin->value(), ui->spYmax->value());
 
}
 

	
 
void MainWindow::onRangeSelected()
 
{
 
    Range r = ui->cbRangePresets->currentData().value<Range>();
 
    ui->spYmin->setValue(r.rmin);
 
    ui->spYmax->setValue(r.rmax);
 
    ui->cbAutoScale->setChecked(false);
 
}
 

	
 
void MainWindow::onNumberFormatButtonToggled(int numberFormatId, bool checked)
 
{
 
    if (checked) selectNumberFormat((NumberFormat) numberFormatId);
 
}
 

	
 
void MainWindow::selectNumberFormat(NumberFormat numberFormatId)
 
{
 
    numberFormat = numberFormatId;
 

	
 
    switch(numberFormat)
 
    {
 
        case NumberFormat_uint8:
 
            sampleSize = 1;
 
            readSample = &MainWindow::readSampleAs<quint8>;
 
            break;
 
        case NumberFormat_int8:
 
            sampleSize = 1;
 
            readSample = &MainWindow::readSampleAs<qint8>;
 
            break;
 
        case NumberFormat_uint16:
 
            sampleSize = 2;
 
            readSample = &MainWindow::readSampleAs<quint16>;
 
            break;
 
        case NumberFormat_int16:
 
            sampleSize = 2;
 
            readSample = &MainWindow::readSampleAs<qint16>;
 
            break;
 
        case NumberFormat_uint32:
 
            sampleSize = 4;
 
            readSample = &MainWindow::readSampleAs<quint32>;
 
            break;
 
        case NumberFormat_int32:
 
            sampleSize = 4;
 
            readSample = &MainWindow::readSampleAs<qint32>;
 
            break;
 
        case NumberFormat_float:
 
            sampleSize = 4;
 
            readSample = &MainWindow::readSampleAs<float>;
 
            break;
 
        case NumberFormat_ASCII:
 
            sampleSize = 0;    // these two members should not be used
 
            readSample = NULL; // in this mode
 
            break;
 
    }
 

	
 
    if (numberFormat == NumberFormat_ASCII)
 
    {
 
        QObject::disconnect(&(this->serialPort), &QSerialPort::readyRead, 0, 0);
 
        QObject::connect(&(this->serialPort), &QSerialPort::readyRead,
 
                         this, &MainWindow::onDataReadyASCII);
 
        portControl.enableSkipByte();
 
    }
 
    else
 
    {
 
        QObject::disconnect(&(this->serialPort), &QSerialPort::readyRead, 0, 0);
 
        QObject::connect(&(this->serialPort), &QSerialPort::readyRead,
 
                         this, &MainWindow::onDataReady);
 
        portControl.enableSkipByte(false);
 
    }
 
}
 

	
 
template<typename T> double MainWindow::readSampleAs()
 
{
 
    T data;
 
    this->serialPort.read((char*) &data, sizeof(data));
 

	
 
    if (ui->rbLittleE->isChecked())
 
    {
 
        data = qFromLittleEndian(data);
 
    }
 
    else
 
    {
 
        data = qFromBigEndian(data);
 
    }
 

	
 
    return double(data);
 
}
 

	
 
bool MainWindow::isDemoRunning()
 
{
 
    return ui->actionDemoMode->isChecked();
 
}
 

	
 
void MainWindow::spsTimerTimeout()
 
{
 
    spsLabel.setText(QString::number(sampleCount/SPS_UPDATE_TIMEOUT) + "sps");
 
    sampleCount = 0;
 
}
 

	
 
void MainWindow::demoTimerTimeout()
 
{
 
    const double period = 100;
 
    demoCount++;
 
    if (demoCount >= 100) demoCount = 0;
 

	
 
    if (!ui->actionPause->isChecked())
 
    {
 
        for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
        {
 
            // we are calculating the fourier components of square wave
 
            double value = 4*sin(2*M_PI*double((ci+1)*demoCount)/period)/((2*(ci+1))*M_PI);
 
            addChannelData(ci, &value, 1);
 
        }
 
        ui->plot->replot();
 
    }
 
}
 

	
 
void MainWindow::enableDemo(bool enabled)
 
{
 
    if (enabled)
 
    {
 
        if (!serialPort.isOpen())
 
        {
 
            demoTimer.start();
 
            ui->actionDemoMode->setChecked(true);
 
            demoIndicator.show();
 
            ui->plot->replot();
 
        }
 
        else
 
        {
 
            ui->actionDemoMode->setChecked(false);
 
        }
 
    }
 
    else
 
    {
 
        demoTimer.stop();
 
        ui->actionDemoMode->setChecked(false);
 
        demoIndicator.hide();
 
        ui->plot->replot();
 
    }
 
}
 

	
 
void MainWindow::onExportCsv()
 
{
 
    bool wasPaused = ui->actionPause->isChecked();
 
    ui->actionPause->setChecked(true); // pause plotting
 

	
 
    QString fileName = QFileDialog::getSaveFileName(this, tr("Export CSV File"));
 

	
 
    if (fileName.isNull())  // user canceled export
 
    {
 
        ui->actionPause->setChecked(wasPaused);
 
    }
 
    else
 
    {
 
        QFile file(fileName);
 
        if (file.open(QIODevice::WriteOnly | QIODevice::Text))
 
        {
 
            QTextStream fileStream(&file);
 

	
 
            for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
            {
 
                fileStream << "Channel " << ci;
 
                if (ci != numOfChannels-1) fileStream << ",";
 
            }
 
            fileStream << '\n';
 

	
 
            for (unsigned int i = 0; i < numOfSamples; i++)
 
            {
 
                for (unsigned int ci = 0; ci < numOfChannels; ci++)
 
                {
 
                    fileStream << channelBuffers[ci]->sample(i).y();
 
                    if (ci != numOfChannels-1) fileStream << ",";
 
                }
 
                fileStream << '\n';
 
            }
 
        }
 
        else
 
        {
 
            qCritical() << "File open error during export: " << file.error();
 
        }
 
    }
 
}
 

	
 
void MainWindow::messageHandler(QtMsgType type,
 
                                const QMessageLogContext &context,
 
                                const QString &msg)
 
{
 
    QString logString;
 

	
 
    switch (type)
 
    {
 
        case QtDebugMsg:
 
            logString = "[Debug] " + msg;
 
            break;
 
        case QtWarningMsg:
 
            logString = "[Warning] " + msg;
 
            break;
 
        case QtCriticalMsg:
 
            logString = "[Error] " + msg;
 
            break;
 
        case QtFatalMsg:
 
            logString = "[Fatal] " + msg;
 
            break;
 
    }
 

	
 
    if (ui != NULL) ui->ptLog->appendPlainText(logString);
 
    std::cerr << logString.toStdString() << std::endl;
 

	
 
    if (type != QtDebugMsg && ui != NULL)
 
    {
 
        ui->statusBar->showMessage(msg, 5000);
 
    }
 
}
mainwindow.h
Show inline comments
 
/*
 
  Copyright © 2015 Hasan Yavuz Özderya
 

	
 
  This file is part of serialplot.
 

	
 
  serialplot is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  serialplot is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU General Public License for more details.
 

	
 
  You should have received a copy of the GNU General Public License
 
  along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
#ifndef MAINWINDOW_H
 
#define MAINWINDOW_H
 

	
 
#include <QMainWindow>
 
#include <QButtonGroup>
 
#include <QLabel>
 
#include <QString>
 
#include <QVector>
 
#include <QList>
 
#include <QSerialPort>
 
#include <QSignalMapper>
 
#include <QTimer>
 
#include <QColor>
 
#include <QtGlobal>
 
#include <qwt_plot_curve.h>
 
#include <qwt_plot_textlabel.h>
 

	
 
#include "portcontrol.h"
 
#include "commandpanel.h"
 
#include "ui_about_dialog.h"
 
#include "framebuffer.h"
 
#include "snapshotmanager.h"
 

	
 
namespace Ui {
 
class MainWindow;
 
}
 

	
 
class MainWindow : public QMainWindow
 
{
 
    Q_OBJECT
 

	
 
public:
 
    explicit MainWindow(QWidget *parent = 0);
 
    ~MainWindow();
 

	
 
    void messageHandler(QtMsgType type, const QMessageLogContext &context,
 
                        const QString &msg);
 

	
 
private:
 
    enum NumberFormat
 
    {
 
        NumberFormat_uint8,
 
        NumberFormat_uint16,
 
        NumberFormat_uint32,
 
        NumberFormat_int8,
 
        NumberFormat_int16,
 
        NumberFormat_int32,
 
        NumberFormat_float,
 
        NumberFormat_ASCII
 
    };
 

	
 
    Ui::MainWindow *ui;
 
    QButtonGroup numberFormatButtons;
 

	
 
    QDialog aboutDialog;
 
    void setupAboutDialog();
 

	
 
    QSerialPort serialPort;
 
    PortControl portControl;
 

	
 
    unsigned int numOfSamples;
 
    unsigned int numOfChannels;
 

	
 
    QList<QwtPlotCurve*> curves;
 
    // Note: FrameBuffer s are owned by their respective QwtPlotCurve s.
 
    QList<FrameBuffer*> channelBuffers;
 

	
 
    // `data` contains i th channels data
 
    void addChannelData(unsigned int channel, double* data, unsigned size);
 

	
 
    NumberFormat numberFormat;
 
    unsigned int sampleSize; // number of bytes in the selected number format
 
    double (MainWindow::*readSample)();
 

	
 
    // note that serialPort should already have enough bytes present
 
    template<typename T> double readSampleAs();
 

	
 
    bool skipByteRequested;
 

	
 
    const int SPS_UPDATE_TIMEOUT = 1;  // second
 
    QLabel spsLabel;
 
    unsigned int sampleCount;
 
    QTimer spsTimer;
 

	
 
    CommandPanel commandPanel;
 

	
 
    SnapshotManager snapshotMan;
 

	
 
    // demo
 
    QTimer demoTimer;
 
    int demoCount;
 
    bool isDemoRunning();
 
    QwtPlotTextLabel demoIndicator;
 

	
 
private slots:
 
    void onPortToggled(bool open);
 
    void onDataReady();      // used with binary number formats
 
    void onDataReadyASCII(); // used with ASCII number format
 
    void onPortError(QSerialPort::SerialPortError error);
 

	
 
    void skipByte();
 

	
 
    void onNumOfSamplesChanged(int value);
 
    void onAutoScaleChecked(bool checked);
 
    void onYScaleChanged();
 
    void onRangeSelected();
 

	
 
    void onNumOfChannelsChanged(int value);
 
    void onNumberFormatButtonToggled(int numberFormatId, bool checked);
 
    void selectNumberFormat(NumberFormat numberFormatId);
 

	
 
    void clearPlot();
 

	
 
    void spsTimerTimeout();
 

	
 
    void demoTimerTimeout();
 
    void enableDemo(bool enabled);
 

	
 
    void onExportCsv();
 
};
 

	
 
#endif // MAINWINDOW_H
misc/pseudo_device_2.py
Show inline comments
 
new file 100755
 
#!/usr/bin/python3
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
import os, pty, time
 

	
 
def run():
 
    # create the pseudo terminal
 
    master, slave = pty.openpty()
 

	
 
    slave_name = os.ttyname(slave)
 
    print("Slave terminal: {}".format(slave_name))
 

	
 
    masterw = os.fdopen(master, 'w')
 
    masterr = os.fdopen(master, 'r')
 

	
 
    while True:
 
        line = masterr.read(4)
 
        print(">" + line)
 
        if (line.strip() == "data"):
 
            masterw.write("1,1\r\n")
 
        time.sleep(0.01)
 

	
 
if __name__ == "__main__":
 
    run()
misc/tango/license_notice
Show inline comments
 
new file 100644
 
Icon files in this directory are taken from Tango project
 
(http://tango.freedesktop.org/). Please see referenced web page for
 
more information.
 
\ No newline at end of file
misc/tango/list-add.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
misc/tango/list-remove.png
Show inline comments
 
new file 100644
 
binary diff not shown
Show images
misc/winicons.qrc
Show inline comments
 
new file 100644
 
<RCC>
 
    <qresource prefix="/icons">
 
        <file alias="list-add">tango/list-add.png</file>
 
        <file alias="list-remove">tango/list-remove.png</file>
 
    </qresource>
 
</RCC>
serialplot.pro
Show inline comments
 
#
 
# Copyright © 2015 Hasan Yavuz Özderya
 
#
 
# This file is part of serialplot.
 
#
 
# serialplot is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# serialplot is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with serialplot.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 
#-------------------------------------------------
 
#
 
# 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 \
 
    portcontrol.cpp \
 
    plot.cpp \
 
    zoomer.cpp \
 
    hidabletabwidget.cpp \
 
    framebuffer.cpp \
 
    scalepicker.cpp \
 
    scalezoomer.cpp \
 
    portlist.cpp \
 
    snapshotview.cpp \
 
    snapshotmanager.cpp \
 
    snapshot.cpp \
 
    plotsnapshotoverlay.cpp
 
    plotsnapshotoverlay.cpp \
 
    commandpanel.cpp \
 
    commandwidget.cpp \
 
    commandedit.cpp
 

	
 
HEADERS  += mainwindow.h \
 
    utils.h \
 
    portcontrol.h \
 
    floatswap.h \
 
    plot.h \
 
    zoomer.h \
 
    hidabletabwidget.h \
 
    framebuffer.h \
 
    scalepicker.h \
 
    scalezoomer.h \
 
    portlist.h \
 
    snapshotview.h \
 
    snapshotmanager.h \
 
    snapshot.h \
 
    plotsnapshotoverlay.h
 
    plotsnapshotoverlay.h \
 
    commandpanel.h \
 
    commandwidget.h \
 
    commandedit.h
 

	
 
FORMS    += mainwindow.ui \
 
    about_dialog.ui \
 
    portcontrol.ui \
 
    snapshotview.ui
 
    snapshotview.ui \
 
    commandpanel.ui \
 
    commandwidget.ui
 

	
 
INCLUDEPATH += qmake/
 

	
 
CONFIG += c++11
 

	
 
RESOURCES += \
 
    misc/icons.qrc
 
RESOURCES += misc/icons.qrc
 

	
 
win32 {
 
    RESOURCES += misc/winicons.qrc
 
}
0 comments (0 inline, 0 general)