/*
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 .
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include "utils.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setupAboutDialog();
// init UI signals
// menu signals
QObject::connect(ui->actionHelpAbout, &QAction::triggered,
&aboutDialog, &QWidget::show);
// port tab 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(ui->spNumOfSamples, SELECT::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()));
// 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);
QObject::connect(&numberFormatButtons, SIGNAL(buttonToggled(int, bool)),
this, SLOT(onNumberFormatButtonToggled(int, bool)));
// setup parity selection buttons
parityButtons.addButton(ui->rbNoParity, (int) QSerialPort::NoParity);
parityButtons.addButton(ui->rbEvenParity, (int) QSerialPort::EvenParity);
parityButtons.addButton(ui->rbOddParity, (int) QSerialPort::OddParity);
QObject::connect(&parityButtons,
SELECT::OVERLOAD_OF(&QButtonGroup::buttonClicked),
this, &MainWindow::selectParity);
// setup data bits selection buttons
dataBitsButtons.addButton(ui->rb8Bits, (int) QSerialPort::Data8);
dataBitsButtons.addButton(ui->rb7Bits, (int) QSerialPort::Data7);
dataBitsButtons.addButton(ui->rb6Bits, (int) QSerialPort::Data6);
dataBitsButtons.addButton(ui->rb5Bits, (int) QSerialPort::Data5);
QObject::connect(&dataBitsButtons,
SELECT::OVERLOAD_OF(&QButtonGroup::buttonClicked),
this, &MainWindow::selectDataBits);
// setup stop bits selection buttons
stopBitsButtons.addButton(ui->rb1StopBit, (int) QSerialPort::OneStop);
stopBitsButtons.addButton(ui->rb2StopBit, (int) QSerialPort::TwoStop);
QObject::connect(&stopBitsButtons,
SELECT::OVERLOAD_OF(&QButtonGroup::buttonClicked),
this, &MainWindow::selectStopBits);
// setup flow control selection buttons
flowControlButtons.addButton(ui->rbNoFlowControl,
(int) QSerialPort::NoFlowControl);
flowControlButtons.addButton(ui->rbHardwareControl,
(int) QSerialPort::HardwareControl);
flowControlButtons.addButton(ui->rbSoftwareControl,
(int) QSerialPort::SoftwareControl);
QObject::connect(&flowControlButtons,
SELECT::OVERLOAD_OF(&QButtonGroup::buttonClicked),
this, &MainWindow::selectFlowControl);
// init port signals
QObject::connect(&(this->serialPort), SIGNAL(error(QSerialPort::SerialPortError)),
this, SLOT(onPortError(QSerialPort::SerialPortError)));
QObject::connect(&(this->serialPort), &QSerialPort::readyRead,
this, &MainWindow::onDataReady);
// init skip byte button
skipByteRequested = false;
QObject::connect(ui->pbSkipByte, &QPushButton::clicked,
this, &MainWindow::skipByte);
loadPortList();
loadBaudRateList();
ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600"));
// set limits for axis limit boxes
ui->spYmin->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
ui->spYmax->setRange((-1) * std::numeric_limits::max(),
std::numeric_limits::max());
// init plot
numOfSamples = ui->spNumOfSamples->value();
dataArray.resize(numOfSamples);
dataX.resize(numOfSamples);
for (int i = 0; i < dataX.size(); i++)
{
dataX[i] = i;
}
curve.setSamples(dataX, dataArray);
curve.attach(ui->plot);
// init number format
if (numberFormatButtons.checkedId() >= 0)
{
selectNumberFormat((NumberFormat) numberFormatButtons.checkedId());
}
else
{
selectNumberFormat(NumberFormat_uint8);
}
}
MainWindow::~MainWindow()
{
if (serialPort.isOpen())
{
serialPort.close();
}
delete ui;
}
void MainWindow::setupAboutDialog()
{
Ui_AboutDialog uiAboutDialog;
uiAboutDialog.setupUi(&aboutDialog);
QObject::connect(uiAboutDialog.pbAboutQt, &QPushButton::clicked,
[](){ QApplication::aboutQt();});
}
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::selectParity(int parity)
{
if (serialPort.isOpen())
{
if(!serialPort.setParity((QSerialPort::Parity) parity))
{
qDebug() << "Set parity failed: " << serialPort.error();
}
}
}
void MainWindow::selectDataBits(int dataBits)
{
if (serialPort.isOpen())
{
if(!serialPort.setDataBits((QSerialPort::DataBits) dataBits))
{
qDebug() << "Set data bits failed: " << serialPort.error();
}
}
}
void MainWindow::selectStopBits(int stopBits)
{
if (serialPort.isOpen())
{
if(!serialPort.setStopBits((QSerialPort::StopBits) stopBits))
{
qDebug() << "Set stop bits failed: " << serialPort.error();
}
}
}
void MainWindow::selectFlowControl(int flowControl)
{
if (serialPort.isOpen())
{
if(!serialPort.setFlowControl((QSerialPort::FlowControl) flowControl))
{
qDebug() << "Set flow control failed: " << serialPort.error();
}
}
}
void MainWindow::onPortToggled(bool open)
{
ui->pbOpenPort->setChecked(open);
}
void MainWindow::onDataReady()
{
if (!ui->actionPause->isChecked())
{
int bytesAvailable = serialPort.bytesAvailable();
if (bytesAvailable > 0 && skipByteRequested)
{
serialPort.read(1);
skipByteRequested = false;
bytesAvailable--;
}
if (bytesAvailable < sampleSize)
{
return;
}
else
{
int numOfSamplesToRead =
(bytesAvailable - (bytesAvailable % sampleSize)) / sampleSize;
QVector samples(numOfSamplesToRead);
int i = 0;
while(i < numOfSamplesToRead)
{
samples.replace(i, (this->*readSample)());
i++;
}
addData(samples);
}
}
else
{
serialPort.clear(QSerialPort::Input);
}
}
void MainWindow::onPortError(QSerialPort::SerialPortError error)
{
switch(error)
{
case QSerialPort::NoError :
break;
case QSerialPort::ResourceError :
qDebug() << "Port error: resource unavaliable; most likely device removed.";
if (serialPort.isOpen())
{
qDebug() << "Closing port on resource error: " << serialPort.portName();
togglePort();
}
loadPortList();
break;
default:
qDebug() << "Unhandled port error: " << error;
break;
}
}
void MainWindow::skipByte()
{
skipByteRequested = true;
}
void MainWindow::addData(QVector data)
{
int offset = numOfSamples - data.size();
if (offset < 0)
{
for (int i = 0; i < numOfSamples; i++)
{
dataArray[i] = data[i - offset];
}
}
else if (offset == 0)
{
dataArray = data;
}
else
{
// shift old samples
int shift = data.size();
for (int i = 0; i < offset; i++)
{
dataArray[i] = dataArray[i + shift];
}
// place new samples
for (int i = 0; i < data.size(); i++)
{
dataArray[offset + i] = data[i];
}
}
// update plot
curve.setSamples(dataX, dataArray);
ui->plot->replot();
}
void MainWindow::clearPlot()
{
dataArray.fill(0.0);
// update plot
curve.setSamples(dataX, dataArray);
ui->plot->replot();
}
void MainWindow::onNumOfSamplesChanged(int value)
{
unsigned int oldNum = this->numOfSamples;
numOfSamples = value;
// resize data arrays
if (numOfSamples < oldNum)
{
dataX.resize(numOfSamples);
dataArray.remove(0, oldNum - numOfSamples);
}
else if(numOfSamples > oldNum)
{
dataX.resize(numOfSamples);
for (unsigned int i = oldNum; i < numOfSamples; i++)
{
dataX[i] = i;
dataArray.prepend(0);
}
}
}
void MainWindow::onAutoScaleChecked(bool checked)
{
if (checked)
{
ui->plot->setAxisAutoScale(QwtPlot::yLeft);
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->setAxisScale(QwtPlot::yLeft, ui->spYmin->value(),
ui->spYmax->value());
}
}
void MainWindow::onYScaleChanged()
{
ui->plot->setAxisScale(QwtPlot::yLeft, ui->spYmin->value(),
ui->spYmax->value());
}
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;
break;
case NumberFormat_int8:
sampleSize = 1;
readSample = &MainWindow::readSampleAs;
break;
case NumberFormat_uint16:
sampleSize = 2;
readSample = &MainWindow::readSampleAs;
break;
case NumberFormat_int16:
sampleSize = 2;
readSample = &MainWindow::readSampleAs;
break;
case NumberFormat_uint32:
sampleSize = 4;
readSample = &MainWindow::readSampleAs;
break;
case NumberFormat_int32:
sampleSize = 4;
readSample = &MainWindow::readSampleAs;
break;
}
}
template double MainWindow::readSampleAs()
{
T data;
this->serialPort.read((char*) &data, sizeof(data));
return double(data);
}