diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2021-02-24 14:26:24 +0100 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2021-03-02 11:44:29 +0100 |
commit | 10e4ff602f5ab415bf057ac779f35f0e60b849b2 (patch) | |
tree | 27af336d03b116c6d5b31e0ddc9a624f0cde348f /examples/serialbus/modbus/client | |
parent | 7ba42fb04403bb6b30ffceb0b429ae831a053ead (diff) |
Reword example and documentations from master/slave to client/server
Task-number: QTBUG-91213
Task-number: QTBUG-91212
Task-number: QTBUG-91211
Change-Id: I2b585d73b5fa3194459709fa57d44a8f42dceeed
Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
Diffstat (limited to 'examples/serialbus/modbus/client')
18 files changed, 1908 insertions, 0 deletions
diff --git a/examples/serialbus/modbus/client/CMakeLists.txt b/examples/serialbus/modbus/client/CMakeLists.txt new file mode 100644 index 0000000..09ae8f5 --- /dev/null +++ b/examples/serialbus/modbus/client/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.14) +project(modbusclient LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/serialbus/modbus/client") + +find_package(Qt6 COMPONENTS Core) +find_package(Qt6 COMPONENTS Gui) +find_package(Qt6 COMPONENTS SerialBus) +find_package(Qt6 COMPONENTS Widgets) + +qt_add_executable(modbusclient + main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui + settingsdialog.cpp settingsdialog.h settingsdialog.ui + writeregistermodel.cpp writeregistermodel.h +) +set_target_properties(modbusclient PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) +target_link_libraries(modbusclient PUBLIC + Qt::Core + Qt::Gui + Qt::SerialBus + Qt::Widgets +) + + +# Resources: +set(client_resource_files + "images/application-exit.png" + "images/connect.png" + "images/disconnect.png" + "images/settings.png" +) + +qt6_add_resources(modbusclient "client" + PREFIX + "/" + FILES + ${client_resource_files} +) + +if(QT_FEATURE_modbus_serialport) + target_link_libraries(modbusclient PUBLIC + Qt::SerialPort + ) +endif() + +install(TARGETS modbusclient + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/serialbus/modbus/client/client.pro b/examples/serialbus/modbus/client/client.pro new file mode 100644 index 0000000..fa31012 --- /dev/null +++ b/examples/serialbus/modbus/client/client.pro @@ -0,0 +1,29 @@ +QT += serialbus widgets +requires(qtConfig(combobox)) + +qtConfig(modbus-serialport): QT += serialport + +TARGET = modbusclient +TEMPLATE = app +CONFIG += c++11 + +SOURCES += \ + main.cpp\ + mainwindow.cpp \ + settingsdialog.cpp \ + writeregistermodel.cpp + +HEADERS += \ + mainwindow.h \ + settingsdialog.h \ + writeregistermodel.h + +FORMS += \ + mainwindow.ui \ + settingsdialog.ui + +RESOURCES += \ + client.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/serialbus/modbus/client +INSTALLS += target diff --git a/examples/serialbus/modbus/client/client.qrc b/examples/serialbus/modbus/client/client.qrc new file mode 100644 index 0000000..5e8e998 --- /dev/null +++ b/examples/serialbus/modbus/client/client.qrc @@ -0,0 +1,8 @@ +<RCC> + <qresource prefix="/"> + <file>images/application-exit.png</file> + <file>images/connect.png</file> + <file>images/disconnect.png</file> + <file>images/settings.png</file> + </qresource> +</RCC> diff --git a/examples/serialbus/modbus/client/doc/images/modbusclient.png b/examples/serialbus/modbus/client/doc/images/modbusclient.png Binary files differnew file mode 100644 index 0000000..b2d771c --- /dev/null +++ b/examples/serialbus/modbus/client/doc/images/modbusclient.png diff --git a/examples/serialbus/modbus/client/doc/src/modbusclient.qdoc b/examples/serialbus/modbus/client/doc/src/modbusclient.qdoc new file mode 100644 index 0000000..7467b6c --- /dev/null +++ b/examples/serialbus/modbus/client/doc/src/modbusclient.qdoc @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! \example modbus/client + \title Modbus Client example + + \brief The example implements a Modbus client application. + + The example acts as Modbus client sending Modbus request via serial line + and TCP respectively. The shown dialog allows the definition of standard + requests and displays incoming responses. + + \image ../images/modbusclient.png + + \include examples-run.qdocinc + + The example must be used in conjunction with the \l {Modbus server example} + or another Modbus device which is either connected via TCP or Serial Port. +*/ diff --git a/examples/serialbus/modbus/client/images/application-exit.png b/examples/serialbus/modbus/client/images/application-exit.png Binary files differnew file mode 100644 index 0000000..32be6b3 --- /dev/null +++ b/examples/serialbus/modbus/client/images/application-exit.png diff --git a/examples/serialbus/modbus/client/images/connect.png b/examples/serialbus/modbus/client/images/connect.png Binary files differnew file mode 100644 index 0000000..dd5a51e --- /dev/null +++ b/examples/serialbus/modbus/client/images/connect.png diff --git a/examples/serialbus/modbus/client/images/disconnect.png b/examples/serialbus/modbus/client/images/disconnect.png Binary files differnew file mode 100644 index 0000000..fd58f7a --- /dev/null +++ b/examples/serialbus/modbus/client/images/disconnect.png diff --git a/examples/serialbus/modbus/client/images/settings.png b/examples/serialbus/modbus/client/images/settings.png Binary files differnew file mode 100644 index 0000000..3d1042e --- /dev/null +++ b/examples/serialbus/modbus/client/images/settings.png diff --git a/examples/serialbus/modbus/client/main.cpp b/examples/serialbus/modbus/client/main.cpp new file mode 100644 index 0000000..19a1d5b --- /dev/null +++ b/examples/serialbus/modbus/client/main.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 <QApplication> +#include <QLoggingCategory> + +int main(int argc, char *argv[]) +{ + // Uncomment the following line to enable logging + // QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = true")); + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/serialbus/modbus/client/mainwindow.cpp b/examples/serialbus/modbus/client/mainwindow.cpp new file mode 100644 index 0000000..a7d1d5e --- /dev/null +++ b/examples/serialbus/modbus/client/mainwindow.cpp @@ -0,0 +1,394 @@ +/**************************************************************************** +** +** 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 <QModbusRtuSerialClient> +#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 QModbusRtuSerialClient(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); + 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 (Modbus 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 (Modbus 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); +} diff --git a/examples/serialbus/modbus/client/mainwindow.h b/examples/serialbus/modbus/client/mainwindow.h new file mode 100644 index 0000000..186d004 --- /dev/null +++ b/examples/serialbus/modbus/client/mainwindow.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QModbusDataUnit> + +QT_BEGIN_NAMESPACE + +class QModbusClient; +class QModbusReply; + +namespace Ui { +class MainWindow; +class SettingsDialog; +} + +QT_END_NAMESPACE + +class SettingsDialog; +class WriteRegisterModel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + void initActions(); + QModbusDataUnit readRequest() const; + QModbusDataUnit writeRequest() const; + +private slots: + void onConnectButtonClicked(); + void onModbusStateChanged(int state); + + void onReadButtonClicked(); + void onReadReady(); + + void onWriteButtonClicked(); + void onReadWriteButtonClicked(); + + void onConnectTypeChanged(int); + void onWriteTableChanged(int); + +private: + Ui::MainWindow *ui = nullptr; + QModbusReply *lastRequest = nullptr; + QModbusClient *modbusDevice = nullptr; + SettingsDialog *m_settingsDialog = nullptr; + WriteRegisterModel *writeModel = nullptr; +}; + +#endif // MAINWINDOW_H diff --git a/examples/serialbus/modbus/client/mainwindow.ui b/examples/serialbus/modbus/client/mainwindow.ui new file mode 100644 index 0000000..4c4eb4e --- /dev/null +++ b/examples/serialbus/modbus/client/mainwindow.ui @@ -0,0 +1,520 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>516</width> + <height>378</height> + </rect> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>1000</height> + </size> + </property> + <property name="windowTitle"> + <string>Modbus Client Example</string> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="5"> + <widget class="QLabel" name="label_27"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Server Address:</string> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QPushButton" name="connectButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Connect</string> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + <property name="default"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="4"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="6"> + <widget class="QSpinBox" name="serverEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>247</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="connectType"> + <item> + <property name="text"> + <string>Serial</string> + </property> + </item> + <item> + <property name="text"> + <string>TCP</string> + </property> + </item> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Connection type:</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLineEdit" name="portEdit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Read</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Start address:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="readAddress"> + <property name="maximum"> + <number>9</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Number of values:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="readSize"> + <property name="currentIndex"> + <number>9</number> + </property> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>2</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>4</string> + </property> + </item> + <item> + <property name="text"> + <string>5</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>7</string> + </property> + </item> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + <item> + <property name="text"> + <string>10</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Result:</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QListWidget" name="readValue"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="writeGroupBox"> + <property name="minimumSize"> + <size> + <width>225</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Write</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Start address:</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QTreeView" name="writeValueTable"> + <property name="showDropIndicator" stdset="0"> + <bool>true</bool> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>false</bool> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <property name="itemsExpandable"> + <bool>false</bool> + </property> + <property name="expandsOnDoubleClick"> + <bool>false</bool> + </property> + <attribute name="headerVisible"> + <bool>true</bool> + </attribute> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="writeAddress"> + <property name="maximum"> + <number>9</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Number of values:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="writeSize"> + <property name="currentIndex"> + <number>9</number> + </property> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>2</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>4</string> + </property> + </item> + <item> + <property name="text"> + <string>5</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>7</string> + </property> + </item> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + <item> + <property name="text"> + <string>9</string> + </property> + </item> + <item> + <property name="text"> + <string>10</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Table:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="writeTable"/> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>13</width> + <height>17</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="readButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Read</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="writeButton"> + <property name="text"> + <string>Write</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="readWriteButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Read-Write</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QStatusBar" name="statusBar"/> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>516</width> + <height>21</height> + </rect> + </property> + <widget class="QMenu" name="menuDevice"> + <property name="title"> + <string>&Device</string> + </property> + <addaction name="actionConnect"/> + <addaction name="actionDisconnect"/> + <addaction name="separator"/> + <addaction name="actionExit"/> + </widget> + <widget class="QMenu" name="menuToo_ls"> + <property name="title"> + <string>Too&ls</string> + </property> + <addaction name="actionOptions"/> + </widget> + <addaction name="menuDevice"/> + <addaction name="menuToo_ls"/> + </widget> + <action name="actionConnect"> + <property name="icon"> + <iconset resource="client.qrc"> + <normaloff>:/images/connect.png</normaloff>:/images/connect.png</iconset> + </property> + <property name="text"> + <string>&Connect</string> + </property> + </action> + <action name="actionDisconnect"> + <property name="icon"> + <iconset resource="client.qrc"> + <normaloff>:/images/disconnect.png</normaloff>:/images/disconnect.png</iconset> + </property> + <property name="text"> + <string>&Disconnect</string> + </property> + </action> + <action name="actionExit"> + <property name="icon"> + <iconset resource="client.qrc"> + <normaloff>:/images/application-exit.png</normaloff>:/images/application-exit.png</iconset> + </property> + <property name="text"> + <string>&Quit</string> + </property> + </action> + <action name="actionOptions"> + <property name="icon"> + <iconset resource="client.qrc"> + <normaloff>:/images/settings.png</normaloff>:/images/settings.png</iconset> + </property> + <property name="text"> + <string>&Options</string> + </property> + </action> + </widget> + <layoutdefault spacing="6" margin="11"/> + <tabstops> + <tabstop>connectType</tabstop> + <tabstop>portEdit</tabstop> + <tabstop>serverEdit</tabstop> + <tabstop>connectButton</tabstop> + <tabstop>readAddress</tabstop> + <tabstop>readSize</tabstop> + <tabstop>readValue</tabstop> + <tabstop>writeAddress</tabstop> + <tabstop>writeSize</tabstop> + <tabstop>writeValueTable</tabstop> + <tabstop>writeTable</tabstop> + <tabstop>readButton</tabstop> + <tabstop>writeButton</tabstop> + <tabstop>readWriteButton</tabstop> + </tabstops> + <resources> + <include location="client.qrc"/> + </resources> + <connections/> +</ui> diff --git a/examples/serialbus/modbus/client/settingsdialog.cpp b/examples/serialbus/modbus/client/settingsdialog.cpp new file mode 100644 index 0000000..0a5eb43 --- /dev/null +++ b/examples/serialbus/modbus/client/settingsdialog.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "settingsdialog.h" +#include "ui_settingsdialog.h" + +SettingsDialog::SettingsDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + ui->parityCombo->setCurrentIndex(1); +#if QT_CONFIG(modbus_serialport) + ui->baudCombo->setCurrentText(QString::number(m_settings.baud)); + ui->dataBitsCombo->setCurrentText(QString::number(m_settings.dataBits)); + ui->stopBitsCombo->setCurrentText(QString::number(m_settings.stopBits)); +#endif + ui->timeoutSpinner->setValue(m_settings.responseTime); + ui->retriesSpinner->setValue(m_settings.numberOfRetries); + + connect(ui->applyButton, &QPushButton::clicked, [this]() { +#if QT_CONFIG(modbus_serialport) + m_settings.parity = ui->parityCombo->currentIndex(); + if (m_settings.parity > 0) + m_settings.parity++; + m_settings.baud = ui->baudCombo->currentText().toInt(); + m_settings.dataBits = ui->dataBitsCombo->currentText().toInt(); + m_settings.stopBits = ui->stopBitsCombo->currentText().toInt(); +#endif + m_settings.responseTime = ui->timeoutSpinner->value(); + m_settings.numberOfRetries = ui->retriesSpinner->value(); + + hide(); + }); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +SettingsDialog::Settings SettingsDialog::settings() const +{ + return m_settings; +} diff --git a/examples/serialbus/modbus/client/settingsdialog.h b/examples/serialbus/modbus/client/settingsdialog.h new file mode 100644 index 0000000..fbeb97a --- /dev/null +++ b/examples/serialbus/modbus/client/settingsdialog.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SETTINGSDIALOG_H +#define SETTINGSDIALOG_H + +#include <QtSerialBus/qtserialbusglobal.h> +#include <QDialog> +#if QT_CONFIG(modbus_serialport) +#include <QSerialPort> +#endif + +QT_BEGIN_NAMESPACE + +namespace Ui { +class SettingsDialog; +} + +QT_END_NAMESPACE + +class SettingsDialog : public QDialog +{ + Q_OBJECT + +public: + struct Settings { +#if QT_CONFIG(modbus_serialport) + int parity = QSerialPort::EvenParity; + int baud = QSerialPort::Baud19200; + int dataBits = QSerialPort::Data8; + int stopBits = QSerialPort::OneStop; +#endif + int responseTime = 1000; + int numberOfRetries = 3; + }; + + explicit SettingsDialog(QWidget *parent = nullptr); + ~SettingsDialog(); + + Settings settings() const; + +private: + Settings m_settings; + Ui::SettingsDialog *ui = nullptr; +}; + +#endif // SETTINGSDIALOG_H diff --git a/examples/serialbus/modbus/client/settingsdialog.ui b/examples/serialbus/modbus/client/settingsdialog.ui new file mode 100644 index 0000000..fb594f4 --- /dev/null +++ b/examples/serialbus/modbus/client/settingsdialog.ui @@ -0,0 +1,237 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SettingsDialog</class> + <widget class="QDialog" name="SettingsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>239</width> + <height>256</height> + </rect> + </property> + <property name="windowTitle"> + <string>Modbus Settings</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="3" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>43</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="timeoutSpinner"> + <property name="accelerated"> + <bool>true</bool> + </property> + <property name="suffix"> + <string> ms</string> + </property> + <property name="minimum"> + <number>-1</number> + </property> + <property name="maximum"> + <number>5000</number> + </property> + <property name="singleStep"> + <number>20</number> + </property> + <property name="value"> + <number>200</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Response Timeout:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QPushButton" name="applyButton"> + <property name="text"> + <string>Apply</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Serial Parameters</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Parity:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="parityCombo"> + <item> + <property name="text"> + <string>No</string> + </property> + </item> + <item> + <property name="text"> + <string>Even</string> + </property> + </item> + <item> + <property name="text"> + <string>Odd</string> + </property> + </item> + <item> + <property name="text"> + <string>Space</string> + </property> + </item> + <item> + <property name="text"> + <string>Mark</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Baud Rate:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="baudCombo"> + <item> + <property name="text"> + <string>1200</string> + </property> + </item> + <item> + <property name="text"> + <string>2400</string> + </property> + </item> + <item> + <property name="text"> + <string>4800</string> + </property> + </item> + <item> + <property name="text"> + <string>9600</string> + </property> + </item> + <item> + <property name="text"> + <string>19200</string> + </property> + </item> + <item> + <property name="text"> + <string>38400</string> + </property> + </item> + <item> + <property name="text"> + <string>57600</string> + </property> + </item> + <item> + <property name="text"> + <string>115200</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Data Bits:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QComboBox" name="dataBitsCombo"> + <item> + <property name="text"> + <string>5</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + <item> + <property name="text"> + <string>7</string> + </property> + </item> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Stop Bits:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="stopBitsCombo"> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>2</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Number of retries:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="retriesSpinner"> + <property name="value"> + <number>3</number> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/examples/serialbus/modbus/client/writeregistermodel.cpp b/examples/serialbus/modbus/client/writeregistermodel.cpp new file mode 100644 index 0000000..441f643 --- /dev/null +++ b/examples/serialbus/modbus/client/writeregistermodel.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** 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 "writeregistermodel.h" + +enum { NumColumn = 0, CoilsColumn = 1, HoldingColumn = 2, ColumnCount = 3, RowCount = 10 }; + +WriteRegisterModel::WriteRegisterModel(QObject *parent) + : QAbstractTableModel(parent), + m_coils(RowCount, false), m_holdingRegisters(RowCount, 0u) +{ +} + +int WriteRegisterModel::rowCount(const QModelIndex &/*parent*/) const +{ + return RowCount; +} + +int WriteRegisterModel::columnCount(const QModelIndex &/*parent*/) const +{ + return ColumnCount; +} + +QVariant WriteRegisterModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount) + return QVariant(); + + Q_ASSERT(m_coils.count() == RowCount); + Q_ASSERT(m_holdingRegisters.count() == RowCount); + + if (index.column() == NumColumn && role == Qt::DisplayRole) + return QString::number(index.row()); + + if (index.column() == CoilsColumn && role == Qt::CheckStateRole) // coils + return m_coils.at(index.row()) ? Qt::Checked : Qt::Unchecked; + + if (index.column() == HoldingColumn && role == Qt::DisplayRole) // holding registers + return QString("0x%1").arg(QString::number(m_holdingRegisters.at(index.row()), 16)); + + return QVariant(); + +} + +QVariant WriteRegisterModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) { + switch (section) { + case NumColumn: + return QStringLiteral("#"); + case CoilsColumn: + return QStringLiteral("Coils "); + case HoldingColumn: + return QStringLiteral("Holding Registers"); + default: + break; + } + } + return QVariant(); +} + +bool WriteRegisterModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount) + return false; + + Q_ASSERT(m_coils.count() == RowCount); + Q_ASSERT(m_holdingRegisters.count() == RowCount); + + if (index.column() == CoilsColumn && role == Qt::CheckStateRole) { // coils + auto s = static_cast<Qt::CheckState>(value.toUInt()); + s == Qt::Checked ? m_coils.setBit(index.row()) : m_coils.clearBit(index.row()); + emit dataChanged(index, index); + return true; + } + + if (index.column() == HoldingColumn && role == Qt::EditRole) { // holding registers + bool result = false; + quint16 newValue = value.toString().toUShort(&result, 16); + if (result) + m_holdingRegisters[index.row()] = newValue; + + emit dataChanged(index, index); + return result; + } + + return false; +} + +Qt::ItemFlags WriteRegisterModel::flags(const QModelIndex &index) const +{ + if (!index.isValid() || index.row() >= RowCount || index.column() >= ColumnCount) + return QAbstractTableModel::flags(index); + + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + if ((index.row() < m_address) || (index.row() >= (m_address + m_number))) + flags &= ~Qt::ItemIsEnabled; + + if (index.column() == CoilsColumn) // coils + return flags | Qt::ItemIsUserCheckable; + if (index.column() == HoldingColumn) // holding registers + return flags | Qt::ItemIsEditable; + + return flags; +} + +void WriteRegisterModel::setStartAddress(int address) +{ + m_address = address; + emit updateViewport(); +} + +void WriteRegisterModel::setNumberOfValues(const QString &number) +{ + m_number = number.toInt(); + emit updateViewport(); +} diff --git a/examples/serialbus/modbus/client/writeregistermodel.h b/examples/serialbus/modbus/client/writeregistermodel.h new file mode 100644 index 0000000..4f29812 --- /dev/null +++ b/examples/serialbus/modbus/client/writeregistermodel.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef WRITEREGISTERMODEL_H +#define WRITEREGISTERMODEL_H + +#include <QAbstractItemModel> +#include <QBitArray> +#include <QObject> + +class WriteRegisterModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + WriteRegisterModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + +public slots: + void setStartAddress(int address); + void setNumberOfValues(const QString &number); + +signals: + void updateViewport(); + +public: + int m_number = 0; + int m_address = 0; + QBitArray m_coils; + QList<quint16> m_holdingRegisters; +}; + +#endif // WRITEREGISTERMODEL_H |