| /**************************************************************************** |
| ** |
| ** 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 <QModbusTcpClient> |
| #include <QModbusRtuSerialMaster> |
| #include <QStandardItemModel> |
| #include <QStatusBar> |
| #include <QUrl> |
| |
| 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<int>::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<int>::of(&QComboBox::currentIndexChanged), |
| this, &MainWindow::onConnectTypeChanged); |
| connect(ui->writeTable, QOverload<int>::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<ModbusConnection>(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<ModbusConnection>(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<QModbusReply *>(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<QModbusDataUnit::RegisterType>(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<QModbusDataUnit::RegisterType>(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); |
| } |