/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the QtSerialBus module. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "mainwindow.h" #include "ui_mainwindow.h" #include "settingsdialog.h" #include "writeregistermodel.h" #include #include #include #include #include enum ModbusConnection { Serial, Tcp }; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); m_settingsDialog = new SettingsDialog(this); initActions(); writeModel = new WriteRegisterModel(this); writeModel->setStartAddress(ui->writeAddress->value()); writeModel->setNumberOfValues(ui->writeSize->currentText()); ui->writeValueTable->setModel(writeModel); ui->writeValueTable->hideColumn(2); connect(writeModel, &WriteRegisterModel::updateViewport, ui->writeValueTable->viewport(), QOverload<>::of(&QWidget::update)); ui->writeTable->addItem(tr("Coils"), QModbusDataUnit::Coils); ui->writeTable->addItem(tr("Discrete Inputs"), QModbusDataUnit::DiscreteInputs); ui->writeTable->addItem(tr("Input Registers"), QModbusDataUnit::InputRegisters); ui->writeTable->addItem(tr("Holding Registers"), QModbusDataUnit::HoldingRegisters); #if QT_CONFIG(modbus_serialport) ui->connectType->setCurrentIndex(0); onConnectTypeChanged(0); #else // lock out the serial port option ui->connectType->setCurrentIndex(1); onConnectTypeChanged(1); ui->connectType->setEnabled(false); #endif auto model = new QStandardItemModel(10, 1, this); for (int i = 0; i < 10; ++i) model->setItem(i, new QStandardItem(QStringLiteral("%1").arg(i + 1))); ui->writeSize->setModel(model); ui->writeSize->setCurrentText("10"); connect(ui->writeSize, &QComboBox::currentTextChanged, writeModel, &WriteRegisterModel::setNumberOfValues); auto valueChanged = QOverload::of(&QSpinBox::valueChanged); connect(ui->writeAddress, valueChanged, writeModel, &WriteRegisterModel::setStartAddress); connect(ui->writeAddress, valueChanged, this, [this, model](int i) { int lastPossibleIndex = 0; const int currentIndex = ui->writeSize->currentIndex(); for (int ii = 0; ii < 10; ++ii) { if (ii < (10 - i)) { lastPossibleIndex = ii; model->item(ii)->setEnabled(true); } else { model->item(ii)->setEnabled(false); } } if (currentIndex > lastPossibleIndex) ui->writeSize->setCurrentIndex(lastPossibleIndex); }); } MainWindow::~MainWindow() { if (modbusDevice) modbusDevice->disconnectDevice(); delete modbusDevice; delete ui; } void MainWindow::initActions() { ui->actionConnect->setEnabled(true); ui->actionDisconnect->setEnabled(false); ui->actionExit->setEnabled(true); ui->actionOptions->setEnabled(true); connect(ui->connectButton, &QPushButton::clicked, this, &MainWindow::onConnectButtonClicked); connect(ui->actionConnect, &QAction::triggered, this, &MainWindow::onConnectButtonClicked); connect(ui->actionDisconnect, &QAction::triggered, this, &MainWindow::onConnectButtonClicked); connect(ui->readButton, &QPushButton::clicked, this, &MainWindow::onReadButtonClicked); connect(ui->writeButton, &QPushButton::clicked, this, &MainWindow::onWriteButtonClicked); connect(ui->readWriteButton, &QPushButton::clicked, this, &MainWindow::onReadWriteButtonClicked); connect(ui->connectType, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onConnectTypeChanged); connect(ui->writeTable, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onWriteTableChanged); connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close); connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show); } void MainWindow::onConnectTypeChanged(int index) { if (modbusDevice) { modbusDevice->disconnectDevice(); delete modbusDevice; modbusDevice = nullptr; } auto type = static_cast(index); if (type == Serial) { #if QT_CONFIG(modbus_serialport) modbusDevice = new QModbusRtuSerialMaster(this); #endif } else if (type == Tcp) { modbusDevice = new QModbusTcpClient(this); if (ui->portEdit->text().isEmpty()) ui->portEdit->setText(QLatin1String("127.0.0.1:502")); } connect(modbusDevice, &QModbusClient::errorOccurred, [this](QModbusDevice::Error) { statusBar()->showMessage(modbusDevice->errorString(), 5000); }); if (!modbusDevice) { ui->connectButton->setDisabled(true); if (type == Serial) statusBar()->showMessage(tr("Could not create Modbus master."), 5000); else statusBar()->showMessage(tr("Could not create Modbus client."), 5000); } else { connect(modbusDevice, &QModbusClient::stateChanged, this, &MainWindow::onModbusStateChanged); } } void MainWindow::onConnectButtonClicked() { if (!modbusDevice) return; statusBar()->clearMessage(); if (modbusDevice->state() != QModbusDevice::ConnectedState) { if (static_cast(ui->connectType->currentIndex()) == Serial) { modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, ui->portEdit->text()); #if QT_CONFIG(modbus_serialport) modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, m_settingsDialog->settings().parity); modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_settingsDialog->settings().baud); modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_settingsDialog->settings().dataBits); modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_settingsDialog->settings().stopBits); #endif } else { const QUrl url = QUrl::fromUserInput(ui->portEdit->text()); modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port()); modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host()); } modbusDevice->setTimeout(m_settingsDialog->settings().responseTime); modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries); if (!modbusDevice->connectDevice()) { statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000); } else { ui->actionConnect->setEnabled(false); ui->actionDisconnect->setEnabled(true); } } else { modbusDevice->disconnectDevice(); ui->actionConnect->setEnabled(true); ui->actionDisconnect->setEnabled(false); } } void MainWindow::onModbusStateChanged(int state) { bool connected = (state != QModbusDevice::UnconnectedState); ui->actionConnect->setEnabled(!connected); ui->actionDisconnect->setEnabled(connected); if (state == QModbusDevice::UnconnectedState) ui->connectButton->setText(tr("Connect")); else if (state == QModbusDevice::ConnectedState) ui->connectButton->setText(tr("Disconnect")); } void MainWindow::onReadButtonClicked() { if (!modbusDevice) return; ui->readValue->clear(); statusBar()->clearMessage(); if (auto *reply = modbusDevice->sendReadRequest(readRequest(), ui->serverEdit->value())) { if (!reply->isFinished()) connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady); else delete reply; // broadcast replies return immediately } else { statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000); } } void MainWindow::onReadReady() { auto reply = qobject_cast(sender()); if (!reply) return; if (reply->error() == QModbusDevice::NoError) { const QModbusDataUnit unit = reply->result(); for (int i = 0, total = int(unit.valueCount()); i < total; ++i) { const QString entry = tr("Address: %1, Value: %2").arg(unit.startAddress() + i) .arg(QString::number(unit.value(i), unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16)); ui->readValue->addItem(entry); } } else if (reply->error() == QModbusDevice::ProtocolError) { statusBar()->showMessage(tr("Read response error: %1 (Mobus exception: 0x%2)"). arg(reply->errorString()). arg(reply->rawResult().exceptionCode(), -1, 16), 5000); } else { statusBar()->showMessage(tr("Read response error: %1 (code: 0x%2)"). arg(reply->errorString()). arg(reply->error(), -1, 16), 5000); } reply->deleteLater(); } void MainWindow::onWriteButtonClicked() { if (!modbusDevice) return; statusBar()->clearMessage(); QModbusDataUnit writeUnit = writeRequest(); QModbusDataUnit::RegisterType table = writeUnit.registerType(); for (int i = 0, total = int(writeUnit.valueCount()); i < total; ++i) { if (table == QModbusDataUnit::Coils) writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]); else writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]); } if (auto *reply = modbusDevice->sendWriteRequest(writeUnit, ui->serverEdit->value())) { if (!reply->isFinished()) { connect(reply, &QModbusReply::finished, this, [this, reply]() { if (reply->error() == QModbusDevice::ProtocolError) { statusBar()->showMessage(tr("Write response error: %1 (Mobus exception: 0x%2)") .arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16), 5000); } else if (reply->error() != QModbusDevice::NoError) { statusBar()->showMessage(tr("Write response error: %1 (code: 0x%2)"). arg(reply->errorString()).arg(reply->error(), -1, 16), 5000); } reply->deleteLater(); }); } else { // broadcast replies return immediately reply->deleteLater(); } } else { statusBar()->showMessage(tr("Write error: ") + modbusDevice->errorString(), 5000); } } void MainWindow::onReadWriteButtonClicked() { if (!modbusDevice) return; ui->readValue->clear(); statusBar()->clearMessage(); QModbusDataUnit writeUnit = writeRequest(); QModbusDataUnit::RegisterType table = writeUnit.registerType(); for (int i = 0, total = int(writeUnit.valueCount()); i < total; ++i) { if (table == QModbusDataUnit::Coils) writeUnit.setValue(i, writeModel->m_coils[i + writeUnit.startAddress()]); else writeUnit.setValue(i, writeModel->m_holdingRegisters[i + writeUnit.startAddress()]); } if (auto *reply = modbusDevice->sendReadWriteRequest(readRequest(), writeUnit, ui->serverEdit->value())) { if (!reply->isFinished()) connect(reply, &QModbusReply::finished, this, &MainWindow::onReadReady); else delete reply; // broadcast replies return immediately } else { statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000); } } void MainWindow::onWriteTableChanged(int index) { const bool coilsOrHolding = index == 0 || index == 3; if (coilsOrHolding) { ui->writeValueTable->setColumnHidden(1, index != 0); ui->writeValueTable->setColumnHidden(2, index != 3); ui->writeValueTable->resizeColumnToContents(0); } ui->readWriteButton->setEnabled(index == 3); ui->writeButton->setEnabled(coilsOrHolding); ui->writeGroupBox->setEnabled(coilsOrHolding); } QModbusDataUnit MainWindow::readRequest() const { const auto table = static_cast(ui->writeTable->currentData().toInt()); int startAddress = ui->readAddress->value(); Q_ASSERT(startAddress >= 0 && startAddress < 10); // do not go beyond 10 entries quint16 numberOfEntries = qMin(ui->readSize->currentText().toUShort(), quint16(10 - startAddress)); return QModbusDataUnit(table, startAddress, numberOfEntries); } QModbusDataUnit MainWindow::writeRequest() const { const auto table = static_cast(ui->writeTable->currentData().toInt()); int startAddress = ui->writeAddress->value(); Q_ASSERT(startAddress >= 0 && startAddress < 10); // do not go beyond 10 entries quint16 numberOfEntries = qMin(ui->writeSize->currentText().toUShort(), quint16(10 - startAddress)); return QModbusDataUnit(table, startAddress, numberOfEntries); }