Modbus Slave example

 /****************************************************************************
 **
 ** 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 "settingsdialog.h"
 #include "ui_mainwindow.h"

 #include <QModbusRtuSerialSlave>
 #include <QModbusTcpServer>
 #include <QRegularExpression>
 #include <QRegularExpressionValidator>
 #include <QStatusBar>
 #include <QUrl>

 enum ModbusConnection {
     Serial,
     Tcp
 };

 MainWindow::MainWindow(QWidget *parent)
     : QMainWindow(parent)
     , ui(new Ui::MainWindow)
     , modbusDevice(nullptr)
 {
     ui->setupUi(this);
     setupWidgetContainers();

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

     m_settingsDialog = new SettingsDialog(this);
     initActions();
 }

 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->disconnect();
         delete modbusDevice;
         modbusDevice = nullptr;
     }

     ModbusConnection type = static_cast<ModbusConnection> (index);
     if (type == Serial) {
         modbusDevice = new QModbusRtuSerialSlave(this);
     } else if (type == Tcp) {
         modbusDevice = new QModbusTcpServer(this);
         if (ui->portEdit->text().isEmpty())
             ui->portEdit->setText(QLatin1Literal("127.0.0.1:502"));
     }
     ui->listenOnlyBox->setEnabled(type == Serial);

     if (!modbusDevice) {
         ui->connectButton->setDisabled(true);
         if (type == Serial)
             statusBar()->showMessage(tr("Could not create Modbus slave."), 5000);
         else
             statusBar()->showMessage(tr("Could not create Modbus server."), 5000);
     } else {
         QModbusDataUnitMap reg;
         reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });
         reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });
         reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });
         reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 });

         modbusDevice->setMap(reg);

         connect(modbusDevice, &QModbusServer::dataWritten,
                 this, &MainWindow::updateWidgets);
         connect(modbusDevice, &QModbusServer::stateChanged,
                 this, &MainWindow::onStateChanged);
         connect(modbusDevice, &QModbusServer::errorOccurred,
                 this, &MainWindow::handleDeviceError);

         connect(ui->listenOnlyBox, &QCheckBox::toggled, this, [this](bool toggled) {
             if (modbusDevice)
                 modbusDevice->setValue(QModbusServer::ListenOnlyMode, toggled);
         });
         emit ui->listenOnlyBox->toggled(ui->listenOnlyBox->isChecked());
         connect(ui->setBusyBox, &QCheckBox::toggled, this, [this](bool toggled) {
             if (modbusDevice)
                 modbusDevice->setValue(QModbusServer::DeviceBusy, toggled ? 0xffff : 0x0000);
         });
         emit ui->setBusyBox->toggled(ui->setBusyBox->isChecked());

         setupDeviceData();
     }
 }

 void MainWindow::handleDeviceError(QModbusDevice::Error newError)
 {
     if (newError == QModbusDevice::NoError || !modbusDevice)
         return;

     statusBar()->showMessage(modbusDevice->errorString(), 5000);
 }

 void MainWindow::on_connectButton_clicked()
 {
     bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);

     statusBar()->clearMessage();

     if (intendToConnect) {
         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->setServerAddress(ui->serverEdit->text().toInt());
         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::coilChanged(int id)
 {
     QAbstractButton *button = coilButtons.button(id);
     bitChanged(id, QModbusDataUnit::Coils, button->isChecked());
 }

 void MainWindow::discreteInputChanged(int id)
 {
     QAbstractButton *button = discreteButtons.button(id);
     bitChanged(id, QModbusDataUnit::DiscreteInputs, button->isChecked());
 }

 void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool value)
 {
     if (!modbusDevice)
         return;

     if (!modbusDevice->setData(table, id, value))
         statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
 }

 void MainWindow::setRegister(const QString &value)
 {
     if (!modbusDevice)
         return;

     const QString objectName = QObject::sender()->objectName();
     if (registers.contains(objectName)) {
         bool ok = true;
         const int id = QObject::sender()->property("ID").toInt();
         if (objectName.startsWith(QStringLiteral("inReg")))
             ok = modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toInt(&ok, 16));
         else if (objectName.startsWith(QStringLiteral("holdReg")))
             ok = modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toInt(&ok, 16));

         if (!ok)
             statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),
                                      5000);
     }
 }

 void MainWindow::updateWidgets(QModbusDataUnit::RegisterType table, int address, int size)
 {
     for (int i = 0; i < size; ++i) {
         quint16 value;
         QString text;
         switch (table) {
         case QModbusDataUnit::Coils:
             modbusDevice->data(QModbusDataUnit::Coils, address + i, &value);
             coilButtons.button(address + i)->setChecked(value);
             break;
         case QModbusDataUnit::HoldingRegisters:
             modbusDevice->data(QModbusDataUnit::HoldingRegisters, address + i, &value);
             registers.value(QStringLiteral("holdReg_%1").arg(address + i))->setText(text
                 .setNum(value, 16));
             break;
         default:
             break;
         }
     }
 }

 // -- private

 void MainWindow::setupDeviceData()
 {
     if (!modbusDevice)
         return;

     for (int i = 0; i < coilButtons.buttons().count(); ++i)
         modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());

     for (int i = 0; i < discreteButtons.buttons().count(); ++i) {
         modbusDevice->setData(QModbusDataUnit::DiscreteInputs, i,
             discreteButtons.button(i)->isChecked());
     }

     bool ok;
     for (QLineEdit *widget : qAsConst(registers)) {
         if (widget->objectName().startsWith(QStringLiteral("inReg"))) {
             modbusDevice->setData(QModbusDataUnit::InputRegisters, widget->property("ID").toInt(),
                 widget->text().toInt(&ok, 16));
         } else if (widget->objectName().startsWith(QStringLiteral("holdReg"))) {
             modbusDevice->setData(QModbusDataUnit::HoldingRegisters, widget->property("ID").toInt(),
                 widget->text().toInt(&ok, 16));
         }
     }
 }

 void MainWindow::setupWidgetContainers()
 {
     coilButtons.setExclusive(false);
     discreteButtons.setExclusive(false);

     QRegularExpression regexp(QStringLiteral("coils_(?<ID>\\d+)"));
     const QList<QCheckBox *> coils = findChildren<QCheckBox *>(regexp);
     for (QCheckBox *cbx : coils)
         coilButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
     connect(&coilButtons, SIGNAL(buttonClicked(int)), this, SLOT(coilChanged(int)));

     regexp.setPattern(QStringLiteral("disc_(?<ID>\\d+)"));
     const QList<QCheckBox *> discs = findChildren<QCheckBox *>(regexp);
     for (QCheckBox *cbx : discs)
         discreteButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
     connect(&discreteButtons, SIGNAL(buttonClicked(int)), this, SLOT(discreteInputChanged(int)));

     regexp.setPattern(QLatin1String("(in|hold)Reg_(?<ID>\\d+)"));
     const QList<QLineEdit *> qle = findChildren<QLineEdit *>(regexp);
     for (QLineEdit *lineEdit : qle) {
         registers.insert(lineEdit->objectName(), lineEdit);
         lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
         lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
             QRegularExpression::CaseInsensitiveOption), this));
         connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setRegister);
     }
 }