mainwindow.cpp Example File
modbus/slave/mainwindow.cpp
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the QtSerialBus module.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "mainwindow.h"
#include "settingsdialog.h"
#include "ui_mainwindow.h"
#include <QModbusRtuSerialSlave>
#include <QModbusTcpServer>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QStatusBar>
#include <QUrl>
enum ModbusConnection {
Serial,
Tcp
};
MainWindow:: MainWindow(QWidget * parent)
: QMainWindow (parent)
, ui(new Ui:: MainWindow)
, modbusDevice(nullptr)
{
ui- > setupUi(this );
setupWidgetContainers();
ui- > connectType- > setCurrentIndex(0 );
on_connectType_currentIndexChanged(0 );
m_settingsDialog = new SettingsDialog(this );
initActions();
}
MainWindow:: ~ MainWindow()
{
if (modbusDevice)
modbusDevice- > disconnectDevice();
delete modbusDevice;
delete ui;
}
void MainWindow:: initActions()
{
ui- > actionConnect- > setEnabled(true );
ui- > actionDisconnect- > setEnabled(false );
ui- > actionExit- > setEnabled(true );
ui- > actionOptions- > setEnabled(true );
connect(ui- > actionConnect, & QAction :: triggered,
this , & MainWindow:: on_connectButton_clicked);
connect(ui- > actionDisconnect, & QAction :: triggered,
this , & MainWindow:: on_connectButton_clicked);
connect(ui- > actionExit, & QAction :: triggered, this , & QMainWindow :: close);
connect(ui- > actionOptions, & QAction :: triggered, m_settingsDialog, & QDialog :: show);
}
void MainWindow:: on_connectType_currentIndexChanged(int index)
{
if (modbusDevice) {
modbusDevice- > disconnect();
delete modbusDevice;
modbusDevice = nullptr;
}
ModbusConnection type = static_cast < ModbusConnection> (index);
if (type = = Serial) {
modbusDevice = new QModbusRtuSerialSlave (this );
} else if (type = = Tcp) {
modbusDevice = new QModbusTcpServer (this );
if (ui- > portEdit- > text(). isEmpty())
ui- > portEdit- > setText(QLatin1Literal("127.0.0.1:502" ));
}
ui- > listenOnlyBox- > setEnabled(type = = Serial);
if (! modbusDevice) {
ui- > connectButton- > setDisabled(true );
if (type = = Serial)
statusBar()- > showMessage(tr("Could not create Modbus slave." ), 5000 );
else
statusBar()- > showMessage(tr("Could not create Modbus server." ), 5000 );
} else {
QModbusDataUnitMap reg;
reg. insert(QModbusDataUnit :: Coils, { QModbusDataUnit :: Coils, 0 , 10 });
reg. insert(QModbusDataUnit :: DiscreteInputs, { QModbusDataUnit :: DiscreteInputs, 0 , 10 });
reg. insert(QModbusDataUnit :: InputRegisters, { QModbusDataUnit :: InputRegisters, 0 , 10 });
reg. insert(QModbusDataUnit :: HoldingRegisters, { QModbusDataUnit :: HoldingRegisters, 0 , 10 });
modbusDevice- > setMap(reg);
connect(modbusDevice, & QModbusServer :: dataWritten,
this , & MainWindow:: updateWidgets);
connect(modbusDevice, & QModbusServer :: stateChanged,
this , & MainWindow:: onStateChanged);
connect(modbusDevice, & QModbusServer :: errorOccurred,
this , & MainWindow:: handleDeviceError);
connect(ui- > listenOnlyBox, & QCheckBox :: toggled, this , [ this ] (bool toggled) {
if (modbusDevice)
modbusDevice- > setValue(QModbusServer :: ListenOnlyMode, toggled);
});
emit ui- > listenOnlyBox- > toggled(ui- > listenOnlyBox- > isChecked());
connect(ui- > setBusyBox, & QCheckBox :: toggled, this , [ this ] (bool toggled) {
if (modbusDevice)
modbusDevice- > setValue(QModbusServer :: DeviceBusy, toggled ? 0xffff : 0x0000 );
});
emit ui- > setBusyBox- > toggled(ui- > setBusyBox- > isChecked());
setupDeviceData();
}
}
void MainWindow:: handleDeviceError(QModbusDevice :: Error newError)
{
if (newError = = QModbusDevice :: NoError | | ! modbusDevice)
return ;
statusBar()- > showMessage(modbusDevice- > errorString(), 5000 );
}
void MainWindow:: on_connectButton_clicked()
{
bool intendToConnect = (modbusDevice- > state() = = QModbusDevice :: UnconnectedState);
statusBar()- > clearMessage();
if (intendToConnect) {
if (static_cast < ModbusConnection> (ui- > connectType- > currentIndex()) = = Serial) {
modbusDevice- > setConnectionParameter(QModbusDevice :: SerialPortNameParameter,
ui- > portEdit- > text());
modbusDevice- > setConnectionParameter(QModbusDevice :: SerialParityParameter,
m_settingsDialog- > settings(). parity);
modbusDevice- > setConnectionParameter(QModbusDevice :: SerialBaudRateParameter,
m_settingsDialog- > settings(). baud);
modbusDevice- > setConnectionParameter(QModbusDevice :: SerialDataBitsParameter,
m_settingsDialog- > settings(). dataBits);
modbusDevice- > setConnectionParameter(QModbusDevice :: SerialStopBitsParameter,
m_settingsDialog- > settings(). stopBits);
} else {
const QUrl url = QUrl :: fromUserInput(ui- > portEdit- > text());
modbusDevice- > setConnectionParameter(QModbusDevice :: NetworkPortParameter, url. port());
modbusDevice- > setConnectionParameter(QModbusDevice :: NetworkAddressParameter, url. host());
}
modbusDevice- > setServerAddress(ui- > serverEdit- > text(). toInt());
if (! modbusDevice- > connectDevice()) {
statusBar()- > showMessage(tr("Connect failed: " ) + modbusDevice- > errorString(), 5000 );
} else {
ui- > actionConnect- > setEnabled(false );
ui- > actionDisconnect- > setEnabled(true );
}
} else {
modbusDevice- > disconnectDevice();
ui- > actionConnect- > setEnabled(true );
ui- > actionDisconnect- > setEnabled(false );
}
}
void MainWindow:: onStateChanged(int state)
{
bool connected = (state ! = QModbusDevice :: UnconnectedState);
ui- > actionConnect- > setEnabled(! connected);
ui- > actionDisconnect- > setEnabled(connected);
if (state = = QModbusDevice :: UnconnectedState)
ui- > connectButton- > setText(tr("Connect" ));
else if (state = = QModbusDevice :: ConnectedState)
ui- > connectButton- > setText(tr("Disconnect" ));
}
void MainWindow:: coilChanged(int id)
{
QAbstractButton * button = coilButtons. button(id);
bitChanged(id, QModbusDataUnit :: Coils, button- > isChecked());
}
void MainWindow:: discreteInputChanged(int id)
{
QAbstractButton * button = discreteButtons. button(id);
bitChanged(id, QModbusDataUnit :: DiscreteInputs, button- > isChecked());
}
void MainWindow:: bitChanged(int id, QModbusDataUnit :: RegisterType table, bool value)
{
if (! modbusDevice)
return ;
if (! modbusDevice- > setData(table, id, value))
statusBar()- > showMessage(tr("Could not set data: " ) + modbusDevice- > errorString(), 5000 );
}
void MainWindow:: setRegister(const QString & value)
{
if (! modbusDevice)
return ;
const QString objectName = QObject :: sender()- > objectName();
if (registers. contains(objectName)) {
bool ok = true ;
const int id = QObject :: sender()- > property("ID" ). toInt();
if (objectName. startsWith(QStringLiteral ("inReg" )))
ok = modbusDevice- > setData(QModbusDataUnit :: InputRegisters, id, value. toInt(& ok, 16 ));
else if (objectName. startsWith(QStringLiteral ("holdReg" )))
ok = modbusDevice- > setData(QModbusDataUnit :: HoldingRegisters, id, value. toInt(& ok, 16 ));
if (! ok)
statusBar()- > showMessage(tr("Could not set register: " ) + modbusDevice- > errorString(),
5000 );
}
}
void MainWindow:: updateWidgets(QModbusDataUnit :: RegisterType table, int address, int size)
{
for (int i = 0 ; i < size; + + i) {
quint16 value;
QString text;
switch (table) {
case QModbusDataUnit :: Coils:
modbusDevice- > data(QModbusDataUnit :: Coils, address + i, & value);
coilButtons. button(address + i)- > setChecked(value);
break ;
case QModbusDataUnit :: HoldingRegisters:
modbusDevice- > data(QModbusDataUnit :: HoldingRegisters, address + i, & value);
registers. value(QStringLiteral ("holdReg_%1" ). arg(address + i))- > setText(text
. setNum(value, 16 ));
break ;
default :
break ;
}
}
}
// -- private
void MainWindow:: setupDeviceData()
{
if (! modbusDevice)
return ;
for (int i = 0 ; i < coilButtons. buttons(). count(); + + i)
modbusDevice- > setData(QModbusDataUnit :: Coils, i, coilButtons. button(i)- > isChecked());
for (int i = 0 ; i < discreteButtons. buttons(). count(); + + i) {
modbusDevice- > setData(QModbusDataUnit :: DiscreteInputs, i,
discreteButtons. button(i)- > isChecked());
}
bool ok;
for (QLineEdit * widget : qAsConst (registers)) {
if (widget- > objectName(). startsWith(QStringLiteral ("inReg" ))) {
modbusDevice- > setData(QModbusDataUnit :: InputRegisters, widget- > property("ID" ). toInt(),
widget- > text(). toInt(& ok, 16 ));
} else if (widget- > objectName(). startsWith(QStringLiteral ("holdReg" ))) {
modbusDevice- > setData(QModbusDataUnit :: HoldingRegisters, widget- > property("ID" ). toInt(),
widget- > text(). toInt(& ok, 16 ));
}
}
}
void MainWindow:: setupWidgetContainers()
{
coilButtons. setExclusive(false );
discreteButtons. setExclusive(false );
QRegularExpression regexp(QStringLiteral ("coils_(?<ID>\\d+)" ));
const QList < QCheckBox * > coils = findChildren< QCheckBox * > (regexp);
for (QCheckBox * cbx : coils)
coilButtons. addButton(cbx, regexp. match(cbx- > objectName()). captured("ID" ). toInt());
connect(& coilButtons, SIGNAL(buttonClicked(int )), this , SLOT(coilChanged(int )));
regexp. setPattern(QStringLiteral ("disc_(?<ID>\\d+)" ));
const QList < QCheckBox * > discs = findChildren< QCheckBox * > (regexp);
for (QCheckBox * cbx : discs)
discreteButtons. addButton(cbx, regexp. match(cbx- > objectName()). captured("ID" ). toInt());
connect(& discreteButtons, SIGNAL(buttonClicked(int )), this , SLOT(discreteInputChanged(int )));
regexp. setPattern(QLatin1String("(in|hold)Reg_(?<ID>\\d+)" ));
const QList < QLineEdit * > qle = findChildren< QLineEdit * > (regexp);
for (QLineEdit * lineEdit : qle) {
registers. insert(lineEdit- > objectName(), lineEdit);
lineEdit- > setProperty("ID" , regexp. match(lineEdit- > objectName()). captured("ID" ). toInt());
lineEdit- > setValidator(new QRegularExpressionValidator (QRegularExpression (QStringLiteral ("[0-9a-f]{0,4}" ),
QRegularExpression :: CaseInsensitiveOption), this ));
connect(lineEdit, & QLineEdit :: textChanged, this , & MainWindow:: setRegister);
}
}