/* Copyright © 2019 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) 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::selectListedPort); QObject::connect(&tbPortList, SELECT::OVERLOAD_OF(&QComboBox::activated), this, &PortControl::selectListedPort); // setup buttons ui->pbOpenPort->setDefaultAction(&openAction); ui->pbReloadPorts->setDefaultAction(&loadPortListAction); loadPortList(); } 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::_selectBaudRate(QString baudRate) { if (serialPort->isOpen()) { if (!serialPort->setBaudRate(baudRate.toInt())) { qCritical() << "Can't set baud rate!"; } } } void PortControl::togglePort() { if (serialPort->isOpen()) { 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("115200"); qDebug() << "Opened port:" << serialPort->portName(); emit portToggled(true); } } openAction.setChecked(serialPort->isOpen()); } void PortControl::selectListedPort(QString portName) { // portName may be coming from combobox portName = portName.split(" ")[0]; QSerialPortInfo portInfo(portName); if (portInfo.isNull()) { qWarning() << "Device doesn't exists:" << 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(); } } } 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) { #ifdef Q_OS_UNIX // For suppressing "Invalid argument" errors that happens with pseudo terminals auto isPtsInvalidArgErr = [this] () -> bool { return serialPort->portName().contains("pts/") && serialPort->errorString().contains("Invalid argument"); }; #endif 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: #ifdef Q_OS_UNIX // Qt 5.5 gives "Invalid argument" with 'UnsupportedOperationError' if (isPtsInvalidArgErr()) break; #endif qCritical() << "Operation is not supported."; break; case QSerialPort::TimeoutError: qCritical() << "A timeout error occurred."; break; case QSerialPort::UnknownError: #ifdef Q_OS_UNIX // Qt 5.2 gives "Invalid argument" with 'UnknownError' if (isPtsInvalidArgErr()) break; #endif qCritical() << "Unknown error! Error: " << serialPort->errorString(); break; default: qCritical() << "Unhandled port error: " << error; break; } } void PortControl::selectPort(QString portName) { int portIndex = portList.indexOfName(portName); if (portIndex < 0) // not in list, add to model and update the selections { portList.appendRow(new PortListItem(portName)); portIndex = portList.rowCount()-1; } ui->cbPortList->setCurrentIndex(portIndex); tbPortList.setCurrentIndex(portIndex); selectListedPort(portName); } void PortControl::openPort() { if (!serialPort->isOpen()) { openAction.trigger(); } } void PortControl::saveSettings(QSettings* settings) { settings->beginGroup(SettingGroup_Port); settings->setValue(SG_Port_SelectedPort, selectedPortName()); 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); } settings->endGroup(); }