blob: 213c034e3a301feaff6cefa7c00d6fc879371b82 [file] [log] [blame]
/****************************************************************************
**
** 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)
{
ui->setupUi(this);
setupWidgetContainers();
#if QT_CONFIG(modbus_serialport)
ui->connectType->setCurrentIndex(0);
onCurrentConnectTypeChanged(0);
#else
// lock out the serial port option
ui->connectType->setCurrentIndex(1);
onCurrentConnectTypeChanged(1);
ui->connectType->setEnabled(false);
#endif
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->connectButton, &QPushButton::clicked,
this, &MainWindow::onConnectButtonClicked);
connect(ui->actionConnect, &QAction::triggered,
this, &MainWindow::onConnectButtonClicked);
connect(ui->actionDisconnect, &QAction::triggered,
this, &MainWindow::onConnectButtonClicked);
connect(ui->connectType, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::onCurrentConnectTypeChanged);
connect(ui->actionExit, &QAction::triggered, this, &QMainWindow::close);
connect(ui->actionOptions, &QAction::triggered, m_settingsDialog, &QDialog::show);
}
void MainWindow::onCurrentConnectTypeChanged(int index)
{
if (modbusDevice) {
modbusDevice->disconnect();
delete modbusDevice;
modbusDevice = nullptr;
}
auto type = static_cast<ModbusConnection>(index);
if (type == Serial) {
#if QT_CONFIG(modbus_serialport)
modbusDevice = new QModbusRtuSerialSlave(this);
#endif
} else if (type == Tcp) {
modbusDevice = new QModbusTcpServer(this);
if (ui->portEdit->text().isEmpty())
ui->portEdit->setText(QLatin1String("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::onConnectButtonClicked()
{
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());
#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->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, quint16(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 quint16 id = quint16(QObject::sender()->property("ID").toUInt());
if (objectName.startsWith(QStringLiteral("inReg")))
ok = modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16));
else if (objectName.startsWith(QStringLiteral("holdReg")))
ok = modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&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, quint16(address + i), &value);
coilButtons.button(address + i)->setChecked(value);
break;
case QModbusDataUnit::HoldingRegisters:
modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(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 (quint16 i = 0; i < coilButtons.buttons().count(); ++i)
modbusDevice->setData(QModbusDataUnit::Coils, i, coilButtons.button(i)->isChecked());
for (quint16 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, quint16(widget->property("ID").toUInt()),
widget->text().toUShort(&ok, 16));
} else if (widget->objectName().startsWith(QStringLiteral("holdReg"))) {
modbusDevice->setData(QModbusDataUnit::HoldingRegisters, quint16(widget->property("ID").toUInt()),
widget->text().toUShort(&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);
}
}