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 "settingsdialog.h"
53#include "ui_mainwindow.h"
54
55#include <QModbusRtuSerialSlave>
56#include <QModbusTcpServer>
57#include <QRegularExpression>
58#include <QRegularExpressionValidator>
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 setupWidgetContainers();
73
74#if QT_CONFIG(modbus_serialport)
75 ui->connectType->setCurrentIndex(0);
76 onCurrentConnectTypeChanged(0);
77#else
78 // lock out the serial port option
79 ui->connectType->setCurrentIndex(1);
80 onCurrentConnectTypeChanged(1);
81 ui->connectType->setEnabled(false);
82#endif
83
84 m_settingsDialog = new SettingsDialog(this);
85 initActions();
86}
87
88MainWindow::~MainWindow()
89{
90 if (modbusDevice)
91 modbusDevice->disconnectDevice();
92 delete modbusDevice;
93
94 delete ui;
95}
96
97void MainWindow::initActions()
98{
99 ui->actionConnect->setEnabled(true);
100 ui->actionDisconnect->setEnabled(false);
101 ui->actionExit->setEnabled(true);
102 ui->actionOptions->setEnabled(true);
103
104 connect(ui->connectButton, &QPushButton::clicked,
105 this, &MainWindow::onConnectButtonClicked);
106 connect(ui->actionConnect, &QAction::triggered,
107 this, &MainWindow::onConnectButtonClicked);
108 connect(ui->actionDisconnect, &QAction::triggered,
109 this, &MainWindow::onConnectButtonClicked);
110 connect(ui->connectType, QOverload<int>::of(&QComboBox::currentIndexChanged),
111 this, &MainWindow::onCurrentConnectTypeChanged);
112
113 connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
114 connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
115}
116
117void MainWindow::onCurrentConnectTypeChanged(int index)
118{
119 if (modbusDevice) {
120 modbusDevice->disconnect();
121 delete modbusDevice;
122 modbusDevice = nullptr;
123 }
124
125 auto type = static_cast<ModbusConnection>(index);
126 if (type == Serial) {
127#if QT_CONFIG(modbus_serialport)
128 modbusDevice = new QModbusRtuSerialSlave(this);
129#endif
130 } else if (type == Tcp) {
131 modbusDevice = new QModbusTcpServer(this);
132 if (ui->portEdit->text().isEmpty())
133 ui->portEdit->setText(QLatin1String("127.0.0.1:502"));
134 }
135 ui->listenOnlyBox->setEnabled(type == Serial);
136
137 if (!modbusDevice) {
138 ui->connectButton->setDisabled(true);
139 if (type == Serial)
140 statusBar()->showMessage(tr("Could not create Modbus slave."), 5000);
141 else
142 statusBar()->showMessage(tr("Could not create Modbus server."), 5000);
143 } else {
144 QModbusDataUnitMap reg;
145 reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });
146 reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });
147 reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });
148 reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 });
149
150 modbusDevice->setMap(reg);
151
152 connect(modbusDevice, &QModbusServer::dataWritten,
153 this, &MainWindow::updateWidgets);
154 connect(modbusDevice, &QModbusServer::stateChanged,
155 this, &MainWindow::onStateChanged);
156 connect(modbusDevice, &QModbusServer::errorOccurred,
157 this, &MainWindow::handleDeviceError);
158
159 connect(ui->listenOnlyBox, &QCheckBox::toggled, this, [this](bool toggled) {
160 if (modbusDevice)
161 modbusDevice->setValue(QModbusServer::ListenOnlyMode, toggled);
162 });
163 emit ui->listenOnlyBox->toggled(ui->listenOnlyBox->isChecked());
164 connect(ui->setBusyBox, &QCheckBox::toggled, this, [this](bool toggled) {
165 if (modbusDevice)
166 modbusDevice->setValue(QModbusServer::DeviceBusy, toggled ? 0xffff : 0x0000);
167 });
168 emit ui->setBusyBox->toggled(ui->setBusyBox->isChecked());
169
170 setupDeviceData();
171 }
172}
173
174void MainWindow::handleDeviceError(QModbusDevice::Error newError)
175{
176 if (newError == QModbusDevice::NoError || !modbusDevice)
177 return;
178
179 statusBar()->showMessage(modbusDevice->errorString(), 5000);
180}
181
182void MainWindow::onConnectButtonClicked()
183{
184 bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);
185
186 statusBar()->clearMessage();
187
188 if (intendToConnect) {
189 if (static_cast<ModbusConnection>(ui->connectType->currentIndex()) == Serial) {
190 modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,
191 ui->portEdit->text());
192#if QT_CONFIG(modbus_serialport)
193 modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,
194 m_settingsDialog->settings().parity);
195 modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,
196 m_settingsDialog->settings().baud);
197 modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,
198 m_settingsDialog->settings().dataBits);
199 modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,
200 m_settingsDialog->settings().stopBits);
201#endif
202 } else {
203 const QUrl url = QUrl::fromUserInput(ui->portEdit->text());
204 modbusDevice->setConnectionParameter(QModbusDevice::NetworkPortParameter, url.port());
205 modbusDevice->setConnectionParameter(QModbusDevice::NetworkAddressParameter, url.host());
206 }
207 modbusDevice->setServerAddress(ui->serverEdit->text().toInt());
208 if (!modbusDevice->connectDevice()) {
209 statusBar()->showMessage(tr("Connect failed: ") + modbusDevice->errorString(), 5000);
210 } else {
211 ui->actionConnect->setEnabled(false);
212 ui->actionDisconnect->setEnabled(true);
213 }
214 } else {
215 modbusDevice->disconnectDevice();
216 ui->actionConnect->setEnabled(true);
217 ui->actionDisconnect->setEnabled(false);
218 }
219}
220
221void MainWindow::onStateChanged(int state)
222{
223 bool connected = (state != QModbusDevice::UnconnectedState);
224 ui->actionConnect->setEnabled(!connected);
225 ui->actionDisconnect->setEnabled(connected);
226
227 if (state == QModbusDevice::UnconnectedState)
228 ui->connectButton->setText(tr("Connect"));
229 else if (state == QModbusDevice::ConnectedState)
230 ui->connectButton->setText(tr("Disconnect"));
231}
232
233void MainWindow::coilChanged(int id)
234{
235 QAbstractButton *button = coilButtons.button(id);
236 bitChanged(id, QModbusDataUnit::Coils, button->isChecked());
237}
238
239void MainWindow::discreteInputChanged(int id)
240{
241 QAbstractButton *button = discreteButtons.button(id);
242 bitChanged(id, QModbusDataUnit::DiscreteInputs, button->isChecked());
243}
244
245void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool value)
246{
247 if (!modbusDevice)
248 return;
249
250 if (!modbusDevice->setData(table, quint16(id), value))
251 statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);
252}
253
254void MainWindow::setRegister(const QString &value)
255{
256 if (!modbusDevice)
257 return;
258
259 const QString objectName = QObject::sender()->objectName();
260 if (registers.contains(objectName)) {
261 bool ok = true;
262 const quint16 id = quint16(QObject::sender()->property("ID").toUInt());
263 if (objectName.startsWith(QStringLiteral("inReg")))
264 ok = modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16));
265 else if (objectName.startsWith(QStringLiteral("holdReg")))
266 ok = modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&ok, 16));
267
268 if (!ok)
269 statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),
270 5000);
271 }
272}
273
274void MainWindow::updateWidgets(QModbusDataUnit::RegisterType table, int address, int size)
275{
276 for (int i = 0; i < size; ++i) {
277 quint16 value;
278 QString text;
279 switch (table) {
280 case QModbusDataUnit::Coils:
281 modbusDevice->data(QModbusDataUnit::Coils, quint16(address + i), &value);
282 coilButtons.button(address + i)->setChecked(value);
283 break;
284 case QModbusDataUnit::HoldingRegisters:
285 modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(address + i), &value);
286 registers.value(QStringLiteral("holdReg_%1").arg(address + i))->setText(text
287 .setNum(value, 16));
288 break;
289 default:
290 break;
291 }
292 }
293}
294
295// -- private
296
297void MainWindow::setupDeviceData()
298{
299 if (!modbusDevice)
300 return;
301
302 for (quint16 i = 0; i < coilButtons.buttons().count(); ++i)
303 modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());
304
305 for (quint16 i = 0; i < discreteButtons.buttons().count(); ++i) {
306 modbusDevice->setData(QModbusDataUnit::DiscreteInputs, i,
307 discreteButtons.button(i)->isChecked());
308 }
309
310 bool ok;
311 for (QLineEdit *widget : qAsConst(registers)) {
312 if (widget->objectName().startsWith(QStringLiteral("inReg"))) {
313 modbusDevice->setData(QModbusDataUnit::InputRegisters, quint16(widget->property("ID").toUInt()),
314 widget->text().toUShort(&ok, 16));
315 } else if (widget->objectName().startsWith(QStringLiteral("holdReg"))) {
316 modbusDevice->setData(QModbusDataUnit::HoldingRegisters, quint16(widget->property("ID").toUInt()),
317 widget->text().toUShort(&ok, 16));
318 }
319 }
320}
321
322void MainWindow::setupWidgetContainers()
323{
324 coilButtons.setExclusive(false);
325 discreteButtons.setExclusive(false);
326
327 QRegularExpression regexp(QStringLiteral("coils_(?<ID>\\d+)"));
328 const QList<QCheckBox *> coils = findChildren<QCheckBox *>(regexp);
329 for (QCheckBox *cbx : coils)
330 coilButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
331 connect(&coilButtons, SIGNAL(buttonClicked(int)), this, SLOT(coilChanged(int)));
332
333 regexp.setPattern(QStringLiteral("disc_(?<ID>\\d+)"));
334 const QList<QCheckBox *> discs = findChildren<QCheckBox *>(regexp);
335 for (QCheckBox *cbx : discs)
336 discreteButtons.addButton(cbx, regexp.match(cbx->objectName()).captured("ID").toInt());
337 connect(&discreteButtons, SIGNAL(buttonClicked(int)), this, SLOT(discreteInputChanged(int)));
338
339 regexp.setPattern(QLatin1String("(in|hold)Reg_(?<ID>\\d+)"));
340 const QList<QLineEdit *> qle = findChildren<QLineEdit *>(regexp);
341 for (QLineEdit *lineEdit : qle) {
342 registers.insert(lineEdit->objectName(), lineEdit);
343 lineEdit->setProperty("ID", regexp.match(lineEdit->objectName()).captured("ID").toInt());
344 lineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9a-f]{0,4}"),
345 QRegularExpression::CaseInsensitiveOption), this));
346 connect(lineEdit, &QLineEdit::textChanged, this, &MainWindow::setRegister);
347 }
348}
349