/* Copyright © 2017 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 "portcontrol.h" #include "ui_portcontrol.h" #include #include #include #include #include #include "setting_defines.h" #include "utils.h" #define TBPORTLIST_MINWIDTH (200) // setting mappings const QMap paritySettingMap({ {QSerialPort::NoParity, "none"}, {QSerialPort::OddParity, "odd"}, {QSerialPort::EvenParity, "even"}, }); PortControl::PortControl(QSerialPort* port, QWidget* parent) : QWidget(parent), ui(new Ui::PortControl), portToolBar("Port Toolbar"), openAction("Open", this), loadPortListAction("↺", this) { ui->setupUi(this); serialPort = port; connect(serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onPortError(QSerialPort::SerialPortError))); // setup actions openAction.setCheckable(true); openAction.setShortcut(QKeySequence("F12")); openAction.setToolTip("Open Port"); QObject::connect(&openAction, &QAction::triggered, this, &PortControl::openActionTriggered); loadPortListAction.setToolTip("Reload port list"); QObject::connect(&loadPortListAction, &QAction::triggered, [this](bool checked){loadPortList();}); // setup toolbar portToolBar.addWidget(&tbPortList); portToolBar.addAction(&loadPortListAction); portToolBar.addAction(&openAction); // setup port selection widgets tbPortList.setMinimumWidth(TBPORTLIST_MINWIDTH); tbPortList.setModel(&portList); ui->cbPortList->setModel(&portList); QObject::connect(ui->cbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::onCbPortListActivated); QObject::connect(&tbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::onTbPortListActivated); QObject::connect(ui->cbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::selectPort); QObject::connect(&tbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::selectPort); // setup buttons ui->pbOpenPort->setDefaultAction(&openAction); ui->pbReloadPorts->setDefaultAction(&loadPortListAction); // setup baud rate selection widget QObject::connect(ui->cbBaudRate, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::selectBaudRate); // 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, &PortControl::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, &PortControl::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, &PortControl::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, &PortControl::selectFlowControl); // initialize signal leds ui->ledDTR->setOn(true); ui->ledRTS->setOn(true); // connect output signals connect(ui->pbDTR, &QPushButton::clicked, [this]() { // toggle DTR ui->ledDTR->toggle(); if (serialPort->isOpen()) { serialPort->setDataTerminalReady(ui->ledDTR->isOn()); } }); connect(ui->pbRTS, &QPushButton::clicked, [this]() { // toggle RTS ui->ledRTS->toggle(); if (serialPort->isOpen()) { serialPort->setRequestToSend(ui->ledRTS->isOn()); } }); // setup pin update leds ui->ledDCD->setColor(Qt::yellow); ui->ledDSR->setColor(Qt::yellow); ui->ledRI->setColor(Qt::yellow); ui->ledCTS->setColor(Qt::yellow); pinUpdateTimer.setInterval(1000); // ms connect(&pinUpdateTimer, &QTimer::timeout, this, &PortControl::updatePinLeds); loadPortList(); loadBaudRateList(); ui->cbBaudRate->setCurrentIndex(ui->cbBaudRate->findText("9600")); } PortControl::~PortControl() { delete ui; } void PortControl::loadPortList() { QString currentSelection = ui->cbPortList->currentData(PortNameRole).toString(); portList.loadPortList(); int index = portList.indexOf(currentSelection); if (index >= 0) { ui->cbPortList->setCurrentIndex(index); tbPortList.setCurrentIndex(index); } } void PortControl::loadBaudRateList() { ui->cbBaudRate->clear(); for (auto baudRate : QSerialPortInfo::standardBaudRates()) { ui->cbBaudRate->addItem(QString::number(baudRate)); } } void PortControl::selectBaudRate(QString baudRate) { if (serialPort->isOpen()) { if (!serialPort->setBaudRate(baudRate.toInt())) { qCritical() << "Can't set baud rate!"; } } } void PortControl::selectParity(int parity) { if (serialPort->isOpen()) { if(!serialPort->setParity((QSerialPort::Parity) parity)) { qCritical() << "Can't set parity option!"; } } } void PortControl::selectDataBits(int dataBits) { if (serialPort->isOpen()) { if(!serialPort->setDataBits((QSerialPort::DataBits) dataBits)) { qCritical() << "Can't set numer of data bits!"; } } } void PortControl::selectStopBits(int stopBits) { if (serialPort->isOpen()) { if(!serialPort->setStopBits((QSerialPort::StopBits) stopBits)) { qCritical() << "Can't set number of stop bits!"; } } } void PortControl::selectFlowControl(int flowControl) { if (serialPort->isOpen()) { if(!serialPort->setFlowControl((QSerialPort::FlowControl) flowControl)) { qCritical() << "Can't set flow control option!"; } } } void PortControl::togglePort() { if (serialPort->isOpen()) { pinUpdateTimer.stop(); serialPort->close(); qDebug() << "Closed port:" << serialPort->portName(); emit portToggled(false); } else { // we get the port name from the edit text, which may not be // in the portList if user hasn't pressed Enter // Also note that, portText may not be the `portName` QString portText = ui->cbPortList->currentText(); QString portName; int portIndex = portList.indexOf(portText); if (portIndex < 0) // not in list, add to model and update the selections { portList.appendRow(new PortListItem(portText)); ui->cbPortList->setCurrentIndex(portList.rowCount()-1); tbPortList.setCurrentIndex(portList.rowCount()-1); portName = portText; } else { // get the port name from the data field portName = static_cast(portList.item(portIndex))->portName(); } serialPort->setPortName(ui->cbPortList->currentData(PortNameRole).toString()); // open port if (serialPort->open(QIODevice::ReadWrite)) { // set port settings selectBaudRate(ui->cbBaudRate->currentText()); selectParity((QSerialPort::Parity) parityButtons.checkedId()); selectDataBits((QSerialPort::DataBits) dataBitsButtons.checkedId()); selectStopBits((QSerialPort::StopBits) stopBitsButtons.checkedId()); selectFlowControl((QSerialPort::FlowControl) flowControlButtons.checkedId()); // set output signals serialPort->setDataTerminalReady(ui->ledDTR->isOn()); serialPort->setRequestToSend(ui->ledRTS->isOn()); // update pin signals updatePinLeds(); pinUpdateTimer.start(); qDebug() << "Opened port:" << serialPort->portName(); emit portToggled(true); } } openAction.setChecked(serialPort->isOpen()); } void PortControl::selectPort(QString portName) { // portName may be coming from combobox portName = portName.split(" ")[0]; // 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(); } } } QString PortControl::selectedPortName() { QString portText = ui->cbPortList->currentText(); int portIndex = portList.indexOf(portText); if (portIndex < 0) // not in the list yet { // return the displayed name as port name return portText; } else { // get the port name from the 'port list' return static_cast(portList.item(portIndex))->portName(); } } QToolBar* PortControl::toolBar() { return &portToolBar; } void PortControl::openActionTriggered(bool checked) { togglePort(); } void PortControl::onCbPortListActivated(int index) { tbPortList.setCurrentIndex(index); } void PortControl::onTbPortListActivated(int index) { ui->cbPortList->setCurrentIndex(index); } void PortControl::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(); togglePort(); } 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! Error: " << serialPort->errorString(); break; default: qCritical() << "Unhandled port error: " << error; break; } } void PortControl::updatePinLeds(void) { auto pins = serialPort->pinoutSignals(); ui->ledDCD->setOn(pins & QSerialPort::DataCarrierDetectSignal); ui->ledDSR->setOn(pins & QSerialPort::DataSetReadySignal); ui->ledRI->setOn(pins & QSerialPort::RingIndicatorSignal); ui->ledCTS->setOn(pins & QSerialPort::ClearToSendSignal); } QString PortControl::currentParityText() { return paritySettingMap.value( (QSerialPort::Parity) parityButtons.checkedId()); } QString PortControl::currentFlowControlText() { if (flowControlButtons.checkedId() == QSerialPort::HardwareControl) { return "hardware"; } else if (flowControlButtons.checkedId() == QSerialPort::SoftwareControl) { return "software"; } else // no parity { return "none"; } } void PortControl::saveSettings(QSettings* settings) { settings->beginGroup(SettingGroup_Port); settings->setValue(SG_Port_SelectedPort, selectedPortName()); settings->setValue(SG_Port_BaudRate, ui->cbBaudRate->currentText()); settings->setValue(SG_Port_Parity, currentParityText()); settings->setValue(SG_Port_DataBits, dataBitsButtons.checkedId()); settings->setValue(SG_Port_StopBits, stopBitsButtons.checkedId()); settings->setValue(SG_Port_FlowControl, currentFlowControlText()); settings->endGroup(); } void PortControl::loadSettings(QSettings* settings) { // make sure the port is closed if (serialPort->isOpen()) togglePort(); settings->beginGroup(SettingGroup_Port); // set port name if it exists in the current list otherwise ignore QString portName = settings->value(SG_Port_SelectedPort, QString()).toString(); if (!portName.isEmpty()) { int index = portList.indexOfName(portName); if (index > -1) ui->cbPortList->setCurrentIndex(index); } // load baud rate setting if it exists in baud rate list QString baudSetting = settings->value( SG_Port_BaudRate, ui->cbBaudRate->currentText()).toString(); int baudIndex = ui->cbBaudRate->findText(baudSetting); if (baudIndex > -1) ui->cbBaudRate->setCurrentIndex(baudIndex); // load parity setting QString parityText = settings->value(SG_Port_Parity, currentParityText()).toString(); QSerialPort::Parity paritySetting = paritySettingMap.key( parityText, (QSerialPort::Parity) parityButtons.checkedId()); parityButtons.button(paritySetting)->setChecked(true); // load number of bits int dataBits = settings->value(SG_Port_Parity, dataBitsButtons.checkedId()).toInt(); if (dataBits >=5 && dataBits <= 8) { dataBitsButtons.button((QSerialPort::DataBits) dataBits)->setChecked(true); } // load stop bits int stopBits = settings->value(SG_Port_StopBits, stopBitsButtons.checkedId()).toInt(); if (stopBits == QSerialPort::OneStop) { ui->rb1StopBit->setChecked(true); } else if (stopBits == QSerialPort::TwoStop) { ui->rb2StopBit->setChecked(true); } // load flow control QString flowControlSetting = settings->value(SG_Port_FlowControl, currentFlowControlText()).toString(); if (flowControlSetting == "hardware") { ui->rbHardwareControl->setChecked(true); } else if (flowControlSetting == "software") { ui->rbSoftwareControl->setChecked(true); } else { ui->rbNoFlowControl->setChecked(true); } settings->endGroup(); }