This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
powersync-core/libnymea-core/hardware/modbus/modbusrtumasterimpl.cpp
2025-11-17 16:42:41 +01:00

517 lines
18 KiB
C++

// SPDX-License-Identifier: LGPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea.
*
* nymea is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* nymea is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "modbusrtumasterimpl.h"
#include "modbusrtureplyimpl.h"
#include <QLoggingCategory>
#ifdef WITH_QTSERIALBUS
#include <QtSerialBus/QModbusReply>
#include <QtSerialBus/QModbusDataUnit>
#endif
Q_DECLARE_LOGGING_CATEGORY(dcModbusRtu)
namespace nymeaserver {
ModbusRtuMasterImpl::ModbusRtuMasterImpl(const QUuid &modbusUuid, const QString &serialPort, qint32 baudrate, QSerialPort::Parity parity, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, int numberOfRetries, int timeout, QObject *parent) :
ModbusRtuMaster(parent),
m_modbusUuid(modbusUuid),
m_serialPort(serialPort),
m_baudrate(baudrate),
m_parity(parity),
m_dataBits(dataBits),
m_stopBits(stopBits),
m_numberOfRetries(numberOfRetries),
m_timeout(timeout)
{
#ifdef WITH_QTSERIALBUS
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
m_modbus = new QModbusRtuSerialClient(this);
#else
m_modbus = new QModbusRtuSerialMaster(this);
#endif
m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_serialPort);
m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baudrate);
m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_dataBits);
m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_stopBits);
m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, m_parity);
m_modbus->setNumberOfRetries(m_numberOfRetries);
m_modbus->setTimeout(m_timeout);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(m_modbus, &QModbusRtuSerialClient::stateChanged, this, [=](QModbusDevice::State state){
#else
connect(m_modbus, &QModbusRtuSerialMaster::stateChanged, this, [=](QModbusDevice::State state){
#endif
qCDebug(dcModbusRtu()) << "Connection state changed" << m_modbusUuid.toString() << m_serialPort << state;
if (state == QModbusDevice::ConnectedState) {
if (m_connected != true) {
m_connected = true;
emit connectedChanged(m_connected);
}
} else {
if (m_connected != false) {
m_connected = false;
emit connectedChanged(m_connected);
}
}
});
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(m_modbus, &QModbusRtuSerialClient::errorOccurred, this, [=](QModbusDevice::Error error){
#else
connect(m_modbus, &QModbusRtuSerialMaster::errorOccurred, this, [=](QModbusDevice::Error error){
#endif
qCWarning(dcModbusRtu()) << "Error occurred for modbus RTU master" << m_modbusUuid.toString() << m_serialPort << error << m_modbus->errorString();
if (error != QModbusDevice::NoError) {
disconnectDevice();
}
});
#endif
}
QUuid ModbusRtuMasterImpl::modbusUuid() const
{
return m_modbusUuid;
}
QString ModbusRtuMasterImpl::serialPort() const
{
return m_serialPort;
}
void ModbusRtuMasterImpl::setSerialPort(const QString &serialPort)
{
if (m_serialPort == serialPort)
return;
m_serialPort = serialPort;
emit serialPortChanged(m_serialPort);
}
qint32 ModbusRtuMasterImpl::baudrate() const
{
return m_baudrate;
}
void ModbusRtuMasterImpl::setBaudrate(qint32 baudrate)
{
if (m_baudrate == baudrate)
return;
m_baudrate = baudrate;
emit baudrateChanged(m_baudrate);
}
QSerialPort::Parity ModbusRtuMasterImpl::parity() const
{
return m_parity;
}
void ModbusRtuMasterImpl::setParity(QSerialPort::Parity parity)
{
if (m_parity == parity)
return;
m_parity = parity;
emit parityChanged(m_parity);
}
QSerialPort::DataBits ModbusRtuMasterImpl::dataBits() const
{
return m_dataBits;
}
void ModbusRtuMasterImpl::setDataBits(QSerialPort::DataBits dataBits)
{
if (m_dataBits == dataBits)
return;
m_dataBits = dataBits;
emit dataBitsChanged(m_dataBits);
}
QSerialPort::StopBits ModbusRtuMasterImpl::stopBits()
{
return m_stopBits;
}
void ModbusRtuMasterImpl::setStopBits(QSerialPort::StopBits stopBits)
{
if (m_stopBits == stopBits)
return;
m_stopBits = stopBits;
emit stopBitsChanged(m_stopBits);
}
bool ModbusRtuMasterImpl::connected() const
{
return m_connected;
}
void ModbusRtuMasterImpl::requestReconnect()
{
disconnectDevice();
connectDevice();
}
bool ModbusRtuMasterImpl::connectDevice()
{
#ifdef WITH_QTSERIALBUS
m_modbus->setConnectionParameter(QModbusDevice::SerialPortNameParameter, m_serialPort);
m_modbus->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, m_baudrate);
m_modbus->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, m_dataBits);
m_modbus->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, m_stopBits);
m_modbus->setConnectionParameter(QModbusDevice::SerialParityParameter, m_parity);
m_modbus->setNumberOfRetries(m_numberOfRetries);
m_modbus->setTimeout(m_timeout);
return m_modbus->connectDevice();
#else
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return false;
#endif
}
void ModbusRtuMasterImpl::disconnectDevice()
{
#ifdef WITH_QTSERIALBUS
m_modbus->disconnectDevice();
#else
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
#endif
}
int ModbusRtuMasterImpl::numberOfRetries() const
{
return m_numberOfRetries;
}
void ModbusRtuMasterImpl::setNumberOfRetries(int numberOfRetries)
{
if (m_numberOfRetries == numberOfRetries)
return;
m_numberOfRetries = numberOfRetries;
emit numberOfRetriesChanged(m_numberOfRetries);
#ifdef WITH_QTSERIALBUS
m_modbus->setNumberOfRetries(m_numberOfRetries);
#endif
}
int ModbusRtuMasterImpl::timeout() const
{
return m_timeout;
}
void ModbusRtuMasterImpl::setTimeout(int timeout)
{
if (m_timeout == timeout)
return;
m_timeout = timeout;
emit timeoutChanged(m_timeout);
#ifdef WITH_QTSERIALBUS
m_modbus->setTimeout(m_timeout);
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::readCoil(int slaveAddress, int registerAddress, quint16 size)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, size);
QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Read coil request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(size)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::readDiscreteInput(int slaveAddress, int registerAddress, quint16 size)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, size);
QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Read descrete inputs request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(size)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::readInputRegister(int slaveAddress, int registerAddress, quint16 size)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, size);
QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Read input registers request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(size)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::readHoldingRegister(int slaveAddress, int registerAddress, quint16 size)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size);
QModbusReply *modbusReply = m_modbus->sendReadRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Read holding registers request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(size)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::writeCoils(int slaveAddress, int registerAddress, const QVector<quint16> &values)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, values.length());
request.setValues(values);
QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Write coil request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(values)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
ModbusRtuReply *ModbusRtuMasterImpl::writeHoldingRegisters(int slaveAddress, int registerAddress, const QVector<quint16> &values)
{
#ifdef WITH_QTSERIALBUS
// Create the reply for the plugin
ModbusRtuReplyImpl *reply = new ModbusRtuReplyImpl(slaveAddress, registerAddress, this);
connect(reply, &ModbusRtuReplyImpl::finished, reply, &ModbusRtuReplyImpl::deleteLater);
// Create the actual modbus lib reply
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length());
request.setValues(values);
QModbusReply *modbusReply = m_modbus->sendWriteRequest(request, slaveAddress);
// Cleaning up modbusReply when our reply finishes, regardless if that happened because modbusReply finished or reply timeouted
connect(reply, &ModbusRtuReply::finished, modbusReply, &QModbusReply::deleteLater);
connect(modbusReply, &QModbusReply::finished, reply, [=](){
// Fill common reply data
reply->setFinished(true);
reply->setError(static_cast<ModbusRtuReply::Error>(modbusReply->error()));
reply->setErrorString(modbusReply->errorString());
// Check if the reply finished with an error
if (modbusReply->error() != QModbusDevice::NoError) {
qCWarning(dcModbusRtu()) << "Write holding register request finished with error" << modbusReply->error() << modbusReply->errorString();
emit reply->errorOccurred(reply->error());
emit reply->finished();
return;
}
// Parse the data unit and set reply result
const QModbusDataUnit unit = modbusReply->result();
reply->setResult(unit.values());
emit reply->finished();
});
return qobject_cast<ModbusRtuReply *>(reply);
#else
Q_UNUSED(slaveAddress)
Q_UNUSED(registerAddress)
Q_UNUSED(values)
qCWarning(dcModbusRtu()) << "Modbus is not available on this platform.";
return nullptr;
#endif
}
}