mainwindow.cpp Example File

modbus/master/mainwindow.cpp
 /****************************************************************************
 **
 ** 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)
     , lastRequest(nullptr)
     , modbusDevice(nullptr)
 {
     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(),
         static_cast<void (QWidget::*)()>(&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);

     ui->connectType->setCurrentIndex(0);
     on_connectType_currentIndexChanged(0);

     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 = static_cast<void (QSpinBox::*)(int)> (&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->actionConnect, &QAction::triggered,
             this, &MainWindow::on_connectButton_clicked);
     connect(ui->actionDisconnect, &QAction::triggered,
             this, &MainWindow::on_connectButton_clicked);

     connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
     connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
 }

 void MainWindow::on_connectType_currentIndexChanged(int index)
 {
     if (modbusDevice) {
         modbusDevice->disconnectDevice();
         delete modbusDevice;
         modbusDevice = nullptr;
     }

     auto type = static_cast<ModbusConnection> (index);
     if (type == Serial) {
         modbusDevice = new QModbusRtuSerialMaster(this);
     } else if (type == Tcp) {
         modbusDevice = new QModbusTcpClient(this);
         if (ui->portEdit->text().isEmpty())
             ui->portEdit->setText(QLatin1Literal("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::onStateChanged);
     }
 }

 void MainWindow::on_connectButton_clicked()
 {
     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());
             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);
         } 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::onStateChanged(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::on_readButton_clicked()
 {
     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::readReady);
         else
             delete reply; // broadcast replies return immediately
     } else {
         statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
     }
 }

 void MainWindow::readReady()
 {
     auto reply = qobject_cast<QModbusReply *>(sender());
     if (!reply)
         return;

     if (reply->error() == QModbusDevice::NoError) {
         const QModbusDataUnit unit = reply->result();
         for (uint i = 0; i < unit.valueCount(); 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::on_writeButton_clicked()
 {
     if (!modbusDevice)
         return;
     statusBar()->clearMessage();

     QModbusDataUnit writeUnit = writeRequest();
     QModbusDataUnit::RegisterType table = writeUnit.registerType();
     for (uint i = 0; i < writeUnit.valueCount(); 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::on_readWriteButton_clicked()
 {
     if (!modbusDevice)
         return;
     ui->readValue->clear();
     statusBar()->clearMessage();

     QModbusDataUnit writeUnit = writeRequest();
     QModbusDataUnit::RegisterType table = writeUnit.registerType();
     for (uint i = 0; i < writeUnit.valueCount(); 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::readReady);
         else
             delete reply; // broadcast replies return immediately
     } else {
         statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
     }
 }

 void MainWindow::on_writeTable_currentIndexChanged(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
     int numberOfEntries = qMin(ui->readSize->currentText().toInt(), 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
     int numberOfEntries = qMin(ui->writeSize->currentText().toInt(), 10 - startAddress);
     return QModbusDataUnit(table, startAddress, numberOfEntries);
 }