1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the QtSerialBus module.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "mainwindow.h"
52#include "ui_mainwindow.h"
53#include "settingsdialog.h"
54#include "writeregistermodel.h"
55
56#include <QModbusTcpClient>
57#include <QModbusRtuSerialMaster>
58#include <QStandardItemModel>
59#include <QStatusBar>
60#include <QUrl>
61
62enum ModbusConnection {
63 Serial,
64 Tcp
65};
66
67MainWindow::MainWindow(QWidget *parent)
68 : QMainWindow(parent)
69 , ui(new Ui::MainWindow)
70{
71 ui->setupUi(this);
72
73 m_settingsDialog = new SettingsDialog(this);
74
75 initActions();
76
77 writeModel = new WriteRegisterModel(this);
78 writeModel->setStartAddress(ui->writeAddress->value());
79 writeModel->setNumberOfValues(ui->writeSize->currentText());
80
81 ui->writeValueTable->setModel(writeModel);
82 ui->writeValueTable->hideColumn(column: 2);
83 connect(sender: writeModel, signal: &WriteRegisterModel::updateViewport,
84 receiver: ui->writeValueTable->viewport(), slot: QOverload<>::of(ptr: &QWidget::update));
85
86 ui->writeTable->addItem(atext: tr(s: "Coils"), auserData: QModbusDataUnit::Coils);
87 ui->writeTable->addItem(atext: tr(s: "Discrete Inputs"), auserData: QModbusDataUnit::DiscreteInputs);
88 ui->writeTable->addItem(atext: tr(s: "Input Registers"), auserData: QModbusDataUnit::InputRegisters);
89 ui->writeTable->addItem(atext: tr(s: "Holding Registers"), auserData: QModbusDataUnit::HoldingRegisters);
90
91#if QT_CONFIG(modbus_serialport)
92 ui->connectType->setCurrentIndex(0);
93 onConnectTypeChanged(0);
94#else
95 // lock out the serial port option
96 ui->connectType->setCurrentIndex(1);
97 onConnectTypeChanged(1);
98 ui->connectType->setEnabled(false);
99#endif
100
101 auto model = new QStandardItemModel(10, 1, this);
102 for (int i = 0; i < 10; ++i)
103 model->setItem(arow: i, aitem: new QStandardItem(QStringLiteral("%1").arg(a: i + 1)));
104 ui->writeSize->setModel(model);
105 ui->writeSize->setCurrentText("10");
106 connect(sender: ui->writeSize, signal: &QComboBox::currentTextChanged,
107 receiver: writeModel, slot: &WriteRegisterModel::setNumberOfValues);
108
109 auto valueChanged = QOverload<int>::of(ptr: &QSpinBox::valueChanged);
110 connect(sender: ui->writeAddress, signal: valueChanged, receiver: writeModel, slot: &WriteRegisterModel::setStartAddress);
111 connect(sender: ui->writeAddress, signal: valueChanged, context: this, slot: [this, model](int i) {
112 int lastPossibleIndex = 0;
113 const int currentIndex = ui->writeSize->currentIndex();
114 for (int ii = 0; ii < 10; ++ii) {
115 if (ii < (10 - i)) {
116 lastPossibleIndex = ii;
117 model->item(row: ii)->setEnabled(true);
118 } else {
119 model->item(row: ii)->setEnabled(false);
120 }
121 }
122 if (currentIndex > lastPossibleIndex)
123 ui->writeSize->setCurrentIndex(lastPossibleIndex);
124 });
125}
126
127MainWindow::~MainWindow()
128{
129 if (modbusDevice)
130 modbusDevice->disconnectDevice();
131 delete modbusDevice;
132
133 delete ui;
134}
135
136void MainWindow::initActions()
137{
138 ui->actionConnect->setEnabled(true);
139 ui->actionDisconnect->setEnabled(false);
140 ui->actionExit->setEnabled(true);
141 ui->actionOptions->setEnabled(true);
142
143 connect(sender: ui->connectButton, signal: &QPushButton::clicked,
144 receiver: this, slot: &MainWindow::onConnectButtonClicked);
145 connect(sender: ui->actionConnect, signal: &QAction::triggered,
146 receiver: this, slot: &MainWindow::onConnectButtonClicked);
147 connect(sender: ui->actionDisconnect, signal: &QAction::triggered,
148 receiver: this, slot: &MainWindow::onConnectButtonClicked);
149 connect(sender: ui->readButton, signal: &QPushButton::clicked,
150 receiver: this, slot: &MainWindow::onReadButtonClicked);
151 connect(sender: ui->writeButton, signal: &QPushButton::clicked,
152 receiver: this, slot: &MainWindow::onWriteButtonClicked);
153 connect(sender: ui->readWriteButton, signal: &QPushButton::clicked,
154 receiver: this, slot: &MainWindow::onReadWriteButtonClicked);
155 connect(sender: ui->connectType, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged),
156 receiver: this, slot: &MainWindow::onConnectTypeChanged);
157 connect(sender: ui->writeTable, signal: QOverload<int>::of(ptr: &QComboBox::currentIndexChanged),
158 receiver: this, slot: &MainWindow::onWriteTableChanged);
159
160 connect(sender: ui->actionExit, signal: &QAction::triggered, receiver: this, slot: &QMainWindow::close);
161 connect(sender: ui->actionOptions, signal: &QAction::triggered, receiver: m_settingsDialog, slot: &QDialog::show);
162}
163
164void MainWindow::onConnectTypeChanged(int index)
165{
166 if (modbusDevice) {
167 modbusDevice->disconnectDevice();
168 delete modbusDevice;
169 modbusDevice = nullptr;
170 }
171
172 auto type = static_cast<ModbusConnection>(index);
173 if (type == Serial) {
174#if QT_CONFIG(modbus_serialport)
175 modbusDevice = new QModbusRtuSerialMaster(this);
176#endif
177 } else if (type == Tcp) {
178 modbusDevice = new QModbusTcpClient(this);
179 if (ui->portEdit->text().isEmpty())
180 ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
181 }
182
183 connect(sender: modbusDevice, signal: &QModbusClient::errorOccurred, slot: [this](QModbusDevice::Error) {
184 statusBar()->showMessage(text: modbusDevice->errorString(), timeout: 5000);
185 });
186
187 if (!modbusDevice) {
188 ui->connectButton->setDisabled(true);
189 if (type == Serial)
190 statusBar()->showMessage(text: tr(s: "Could not create Modbus master."), timeout: 5000);
191 else
192 statusBar()->showMessage(text: tr(s: "Could not create Modbus client."), timeout: 5000);
193 } else {
194 connect(sender: modbusDevice, signal: &QModbusClient::stateChanged,
195 receiver: this, slot: &MainWindow::onModbusStateChanged);
196 }
197}
198
199void MainWindow::onConnectButtonClicked()
200{
201 if (!modbusDevice)
202 return;
203
204 statusBar()->clearMessage();
205 if (modbusDevice->state() != QModbusDevice::ConnectedState) {
206 if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
207 modbusDevice->setConnectionParameter(parameter: QModbusDevice::SerialPortNameParameter,
208 value: ui->portEdit->text());
209#if QT_CONFIG(modbus_serialport)
210 modbusDevice->setConnectionParameter(parameter: QModbusDevice::SerialParityParameter,
211 value: m_settingsDialog->settings().parity);
212 modbusDevice->setConnectionParameter(parameter: QModbusDevice::SerialBaudRateParameter,
213 value: m_settingsDialog->settings().baud);
214 modbusDevice->setConnectionParameter(parameter: QModbusDevice::SerialDataBitsParameter,
215 value: m_settingsDialog->settings().dataBits);
216 modbusDevice->setConnectionParameter(parameter: QModbusDevice::SerialStopBitsParameter,
217 value: m_settingsDialog->settings().stopBits);
218#endif
219 } else {
220 const QUrl url = QUrl::fromUserInput(userInput: ui->portEdit->text());
221 modbusDevice->setConnectionParameter(parameter: QModbusDevice::NetworkPortParameter, value: url.port());
222 modbusDevice->setConnectionParameter(parameter: QModbusDevice::NetworkAddressParameter, value: url.host());
223 }
224 modbusDevice->setTimeout(m_settingsDialog->settings().responseTime);
225 modbusDevice->setNumberOfRetries(m_settingsDialog->settings().numberOfRetries);
226 if (!modbusDevice->connectDevice()) {
227 statusBar()->showMessage(text: tr(s: "Connect failed: ") + modbusDevice->errorString(), timeout: 5000);
228 } else {
229 ui->actionConnect->setEnabled(false);
230 ui->actionDisconnect->setEnabled(true);
231 }
232 } else {
233 modbusDevice->disconnectDevice();
234 ui->actionConnect->setEnabled(true);
235 ui->actionDisconnect->setEnabled(false);
236 }
237}
238
239void MainWindow::onModbusStateChanged(int state)
240{
241 bool connected = (state != QModbusDevice::UnconnectedState);
242 ui->actionConnect->setEnabled(!connected);
243 ui->actionDisconnect->setEnabled(connected);
244
245 if (state == QModbusDevice::UnconnectedState)
246 ui->connectButton->setText(tr(s: "Connect"));
247 else if (state == QModbusDevice::ConnectedState)
248 ui->connectButton->setText(tr(s: "Disconnect"));
249}
250
251void MainWindow::onReadButtonClicked()
252{
253 if (!modbusDevice)
254 return;
255 ui->readValue->clear();
256 statusBar()->clearMessage();
257
258 if (auto *reply = modbusDevice->sendReadRequest(read: readRequest(), serverAddress: ui->serverEdit->value())) {
259 if (!reply->isFinished())
260 connect(sender: reply, signal: &QModbusReply::finished, receiver: this, slot: &MainWindow::onReadReady);
261 else
262 delete reply; // broadcast replies return immediately
263 } else {
264 statusBar()->showMessage(text: tr(s: "Read error: ") + modbusDevice->errorString(), timeout: 5000);
265 }
266}
267
268void MainWindow::onReadReady()
269{
270 auto reply = qobject_cast<QModbusReply *>(object: sender());
271 if (!reply)
272 return;
273
274 if (reply->error() == QModbusDevice::NoError) {
275 const QModbusDataUnit unit = reply->result();
276 for (int i = 0, total = int(unit.valueCount()); i < total; ++i) {
277 const QString entry = tr(s: "Address: %1, Value: %2").arg(a: unit.startAddress() + i)
278 .arg(a: QString::number(unit.value(index: i),
279 base: unit.registerType() <= QModbusDataUnit::Coils ? 10 : 16));
280 ui->readValue->addItem(label: entry);
281 }
282 } else if (reply->error() == QModbusDevice::ProtocolError) {
283 statusBar()->showMessage(text: tr(s: "Read response error: %1 (Mobus exception: 0x%2)").
284 arg(a: reply->errorString()).
285 arg(a: reply->rawResult().exceptionCode(), fieldWidth: -1, base: 16), timeout: 5000);
286 } else {
287 statusBar()->showMessage(text: tr(s: "Read response error: %1 (code: 0x%2)").
288 arg(a: reply->errorString()).
289 arg(a: reply->error(), fieldWidth: -1, base: 16), timeout: 5000);
290 }
291
292 reply->deleteLater();
293}
294
295void MainWindow::onWriteButtonClicked()
296{
297 if (!modbusDevice)
298 return;
299 statusBar()->clearMessage();
300
301 QModbusDataUnit writeUnit = writeRequest();
302 QModbusDataUnit::RegisterType table = writeUnit.registerType();
303 for (int i = 0, total = int(writeUnit.valueCount()); i < total; ++i) {
304 if (table == QModbusDataUnit::Coils)
305 writeUnit.setValue(index: i, newValue: writeModel->m_coils[i + writeUnit.startAddress()]);
306 else
307 writeUnit.setValue(index: i, newValue: writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
308 }
309
310 if (auto *reply = modbusDevice->sendWriteRequest(write: writeUnit, serverAddress: ui->serverEdit->value())) {
311 if (!reply->isFinished()) {
312 connect(sender: reply, signal: &QModbusReply::finished, context: this, slot: [this, reply]() {
313 if (reply->error() == QModbusDevice::ProtocolError) {
314 statusBar()->showMessage(text: tr(s: "Write response error: %1 (Mobus exception: 0x%2)")
315 .arg(a: reply->errorString()).arg(a: reply->rawResult().exceptionCode(), fieldWidth: -1, base: 16),
316 timeout: 5000);
317 } else if (reply->error() != QModbusDevice::NoError) {
318 statusBar()->showMessage(text: tr(s: "Write response error: %1 (code: 0x%2)").
319 arg(a: reply->errorString()).arg(a: reply->error(), fieldWidth: -1, base: 16), timeout: 5000);
320 }
321 reply->deleteLater();
322 });
323 } else {
324 // broadcast replies return immediately
325 reply->deleteLater();
326 }
327 } else {
328 statusBar()->showMessage(text: tr(s: "Write error: ") + modbusDevice->errorString(), timeout: 5000);
329 }
330}
331
332void MainWindow::onReadWriteButtonClicked()
333{
334 if (!modbusDevice)
335 return;
336 ui->readValue->clear();
337 statusBar()->clearMessage();
338
339 QModbusDataUnit writeUnit = writeRequest();
340 QModbusDataUnit::RegisterType table = writeUnit.registerType();
341 for (int i = 0, total = int(writeUnit.valueCount()); i < total; ++i) {
342 if (table == QModbusDataUnit::Coils)
343 writeUnit.setValue(index: i, newValue: writeModel->m_coils[i + writeUnit.startAddress()]);
344 else
345 writeUnit.setValue(index: i, newValue: writeModel->m_holdingRegisters[i + writeUnit.startAddress()]);
346 }
347
348 if (auto *reply = modbusDevice->sendReadWriteRequest(read: readRequest(), write: writeUnit,
349 serverAddress: ui->serverEdit->value())) {
350 if (!reply->isFinished())
351 connect(sender: reply, signal: &QModbusReply::finished, receiver: this, slot: &MainWindow::onReadReady);
352 else
353 delete reply; // broadcast replies return immediately
354 } else {
355 statusBar()->showMessage(text: tr(s: "Read error: ") + modbusDevice->errorString(), timeout: 5000);
356 }
357}
358
359void MainWindow::onWriteTableChanged(int index)
360{
361 const bool coilsOrHolding = index == 0 || index == 3;
362 if (coilsOrHolding) {
363 ui->writeValueTable->setColumnHidden(column: 1, hide: index != 0);
364 ui->writeValueTable->setColumnHidden(column: 2, hide: index != 3);
365 ui->writeValueTable->resizeColumnToContents(column: 0);
366 }
367
368 ui->readWriteButton->setEnabled(index == 3);
369 ui->writeButton->setEnabled(coilsOrHolding);
370 ui->writeGroupBox->setEnabled(coilsOrHolding);
371}
372
373QModbusDataUnit MainWindow::readRequest() const
374{
375 const auto table =
376 static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
377
378 int startAddress = ui->readAddress->value();
379 Q_ASSERT(startAddress >= 0 && startAddress < 10);
380
381 // do not go beyond 10 entries
382 quint16 numberOfEntries = qMin(a: ui->readSize->currentText().toUShort(), b: quint16(10 - startAddress));
383 return QModbusDataUnit(table, startAddress, numberOfEntries);
384}
385
386QModbusDataUnit MainWindow::writeRequest() const
387{
388 const auto table =
389 static_cast<QModbusDataUnit::RegisterType>(ui->writeTable->currentData().toInt());
390
391 int startAddress = ui->writeAddress->value();
392 Q_ASSERT(startAddress >= 0 && startAddress < 10);
393
394 // do not go beyond 10 entries
395 quint16 numberOfEntries = qMin(a: ui->writeSize->currentText().toUShort(), b: quint16(10 - startAddress));
396 return QModbusDataUnit(table, startAddress, numberOfEntries);
397}
398

source code of qtserialbus/examples/serialbus/modbus/master/mainwindow.cpp