mainwindow.cpp Example File

modbus/slave/mainwindow.cpp

  /****************************************************************************
  **
  ** Copyright (C) 2015 The Qt Company Ltd.
  ** Contact: http://www.qt.io/licensing/
  **
  ** This file is part of the examples of the QtSerialBus module.
  **
  ** $QT_BEGIN_LICENSE:BSD$
  ** 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 <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;
      foreach (QLineEdit *widget, 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);
      foreach (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);
      foreach (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);
      foreach (QLineEdit *lineEdit, qle) {
          registers.insert(lineEdit->objectName(), lineEdit);
          lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
          lineEdit->setValidator(new QRegExpValidator(QRegExp(QStringLiteral("[0-9a-f]{0,4}"),
              Qt::CaseInsensitive), this));
          connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setRegister);
      }
  }