From bd6cfd8b029d47c250b615966c899078e3793466 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 16 Jun 2020 16:56:20 +0200 Subject: [PATCH] added modbus abstaction class --- modbus/modbusrtumaster.cpp | 372 ++++++++++++++++++++++++++++++++++++ modbus/modbusrtumaster.h | 81 ++++++++ modbus/modbustcpmaster.cpp | 381 +++++++++++++++++++++++++++++++++++++ modbus/modbustcpmaster.h | 85 +++++++++ 4 files changed, 919 insertions(+) create mode 100644 modbus/modbusrtumaster.cpp create mode 100644 modbus/modbusrtumaster.h create mode 100644 modbus/modbustcpmaster.cpp create mode 100644 modbus/modbustcpmaster.h diff --git a/modbus/modbusrtumaster.cpp b/modbus/modbusrtumaster.cpp new file mode 100644 index 0000000..5ecbf61 --- /dev/null +++ b/modbus/modbusrtumaster.cpp @@ -0,0 +1,372 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "modbusrtumaster.h" + +#include +#include +Q_DECLARE_LOGGING_CATEGORY(dcModbusRTU) + +ModbusRTUMaster::ModbusRTUMaster(QString serialPort, uint baudrate, QSerialPort::Parity parity, uint dataBits, uint stopBits, QObject *parent) : + QObject(parent) +{ + m_modbusRtuSerialMaster = new QModbusRtuSerialMaster(this); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, serialPort); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudrate); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, dataBits); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, stopBits); + m_modbusRtuSerialMaster->setConnectionParameter(QModbusDevice::SerialParityParameter, parity); + //m_modbusRtuSerialMaster->setTimeout(100); + //m_modbusRtuSerialMaster->setNumberOfRetries(1); + connect(m_modbusRtuSerialMaster, &QModbusTcpClient::stateChanged, this, &ModbusRTUMaster::onModbusStateChanged); + connect(m_modbusRtuSerialMaster, &QModbusRtuSerialMaster::errorOccurred, this, &ModbusRTUMaster::onModbusErrorOccurred); + + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer); +} + + +ModbusRTUMaster::~ModbusRTUMaster() +{ + if (!m_modbusRtuSerialMaster) { + m_modbusRtuSerialMaster->disconnectDevice(); + m_modbusRtuSerialMaster->deleteLater(); + } + if (!m_reconnectTimer) { + m_reconnectTimer->stop(); + m_reconnectTimer->deleteLater(); + } +} + +bool ModbusRTUMaster::connectDevice() +{ + qCDebug(dcModbusRTU()) << "Setting up TCP connecion"; + + if (!m_modbusRtuSerialMaster) + return false; + + return m_modbusRtuSerialMaster->connectDevice(); +} + +QString ModbusRTUMaster::serialPort() +{ + return m_modbusRtuSerialMaster->connectionParameter(QModbusDevice::SerialPortNameParameter).toString(); +} + +void ModbusRTUMaster::onReconnectTimer() +{ + if(!m_modbusRtuSerialMaster->connectDevice()) { + m_reconnectTimer->start(10000); + } +} + +QUuid ModbusRTUMaster::readCoil(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusRTUMaster::writeCoil(uint slaveAddress, uint registerAddress, bool value) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusRTUMaster::writeHoldingRegister(uint slaveAddress, uint registerAddress, uint value) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusRTUMaster::readDiscreteInput(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusRTUMaster::readInputRegister(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusRTUMaster::readHoldingRegister(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusRtuSerialMaster) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusRtuSerialMaster->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + requestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + requestExecuted(requestId, false); + qCWarning(dcModbusRTU()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusRTU()) << "Modbus replay error:" << error; + + emit requestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbusRTU()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + + +void ModbusRTUMaster::onModbusErrorOccurred(QModbusDevice::Error error) +{ + qCWarning(dcModbusRTU()) << "An error occured" << error; +} + + +void ModbusRTUMaster::onModbusStateChanged(QModbusDevice::State state) +{ + bool connected = (state != QModbusDevice::UnconnectedState); + if (!connected) { + //try to reconnect in 10 seconds + m_reconnectTimer->start(10000); + } + emit connectionStateChanged(connected); +} diff --git a/modbus/modbusrtumaster.h b/modbus/modbusrtumaster.h new file mode 100644 index 0000000..cc8c7fb --- /dev/null +++ b/modbus/modbusrtumaster.h @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MODBUSRTUMASTER_H +#define MODBUSRTUMASTER_H + +#include +#include +#include +#include +#include + +class ModbusRTUMaster : public QObject +{ + Q_OBJECT +public: + explicit ModbusRTUMaster(QString serialPort, uint baudrate, QSerialPort::Parity parity, uint dataBits, uint stopBits, QObject *parent = nullptr); + ~ModbusRTUMaster(); + + bool connectDevice(); + + QUuid readCoil(uint slaveAddress, uint registerAddress); + QUuid readDiscreteInput(uint slaveAddress, uint registerAddress); + QUuid readInputRegister(uint slaveAddress, uint registerAddress); + QUuid readHoldingRegister(uint slaveAddress, uint registerAddress); + + QUuid writeCoil(uint slaveAddress, uint registerAddress, bool status); + QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, uint data); + + QString serialPort(); + +private: + QModbusRtuSerialMaster *m_modbusRtuSerialMaster; + QTimer *m_reconnectTimer = nullptr; + +private slots: + void onReconnectTimer(); + + void onModbusErrorOccurred(QModbusDevice::Error error); + void onModbusStateChanged(QModbusDevice::State state); + +signals: + void connectionStateChanged(bool status); + + void requestExecuted(QUuid requestId, bool success); + void requestError(QUuid requestId, const QString &error); + + void receivedCoil(uint slaveAddress, uint modbusRegister, bool value); + void receivedDiscreteInput(uint slaveAddress, uint modbusRegister, bool value); + void receivedHoldingRegister(uint slaveAddress, uint modbusRegister, uint value); + void receivedInputRegister(uint slaveAddress, uint modbusRegister, uint value); +}; + +#endif // MODBUSRTUMASTER_H diff --git a/modbus/modbustcpmaster.cpp b/modbus/modbustcpmaster.cpp new file mode 100644 index 0000000..213b50a --- /dev/null +++ b/modbus/modbustcpmaster.cpp @@ -0,0 +1,381 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "modbustcpmaster.h" +#include +Q_DECLARE_LOGGING_CATEGORY(dcModbus) + +ModbusTCPMaster::ModbusTCPMaster(const QHostAddress &hostAddress, uint port, QObject *parent) : + QObject(parent) +{ + m_modbusTcpClient = new QModbusTcpClient(this); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, hostAddress.toString()); + m_modbusTcpClient->setTimeout(100); + m_modbusTcpClient->setNumberOfRetries(3); + + connect(m_modbusTcpClient, &QModbusTcpClient::stateChanged, this, &ModbusTCPMaster::onModbusStateChanged); + connect(m_modbusTcpClient, &QModbusRtuSerialMaster::errorOccurred, this, &ModbusTCPMaster::onModbusErrorOccurred); + + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusTCPMaster::onReconnectTimer); +} + +ModbusTCPMaster::~ModbusTCPMaster() +{ + if (!m_modbusTcpClient) { + m_modbusTcpClient->disconnectDevice(); + m_modbusTcpClient->deleteLater(); + } + if (!m_reconnectTimer) { + m_reconnectTimer->stop(); + m_reconnectTimer->deleteLater(); + } +} + +bool ModbusTCPMaster::connectDevice() { + // TCP connction to target device + qCDebug(dcModbus()) << "Setting up TCP connecion"; + + if (!m_modbusTcpClient) + return false; + + return m_modbusTcpClient->connectDevice(); +} + +uint ModbusTCPMaster::port() +{ + return m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkPortParameter).toUInt(); +} + +bool ModbusTCPMaster::setHostAddress(const QHostAddress &hostAddress) +{ + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, hostAddress.toString()); + return connectDevice(); +} + +bool ModbusTCPMaster::setPort(uint port) +{ + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); + return connectDevice(); +} + +void ModbusTCPMaster::onReconnectTimer() +{ + if(!m_modbusTcpClient->connectDevice()) { + m_reconnectTimer->start(10000); + } +} + +QHostAddress ModbusTCPMaster::hostAddress() +{ + return QHostAddress(m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkAddressParameter).toString()); +} + +QUuid ModbusTCPMaster::readCoil(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus reply error:" << error; + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::writeHoldingRegister(uint slaveAddress, uint registerAddress, const QVector &values) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, values.length()); + request.setValues(values); + + if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values()); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus replay error:" << error; + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, registerAddress, 1); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus replay error:" << error; + QModbusReply *reply = qobject_cast(sender()); + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + reply->deleteLater(); + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus reply error:" << error; + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddress, uint size = 1) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, size); + + if (QModbusReply *reply = m_modbusTcpClient->sendReadRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] { + + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values()); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus replay error:" << error; + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::writeCoil(uint slaveAddress, uint registerAddress, bool value) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusTcpClient->sendWriteRequest(request, slaveAddress)) { + if (!reply->isFinished()) { + connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater); + connect(reply, &QModbusReply::finished, this, [reply, requestId, this] () { + + if (reply->error() == QModbusDevice::NoError) { + writeRequestExecuted(requestId, true); + const QModbusDataUnit unit = reply->result(); + uint modbusAddress = unit.startAddress(); + emit receivedCoil(reply->serverAddress(), modbusAddress, unit.value(0)); + + } else { + writeRequestExecuted(requestId, false); + qCWarning(dcModbus()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbus()) << "Modbus reply error:" << error; + emit writeRequestError(requestId, reply->errorString()); + reply->finished(); // To make sure it will be deleted + }); + QTimer::singleShot(200, reply, &QModbusReply::deleteLater); + } else { + delete reply; // broadcast replies return immediately + return ""; + } + } else { + qCWarning(dcModbus()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + + +void ModbusTCPMaster::onModbusErrorOccurred(QModbusDevice::Error error) +{ + qCWarning(dcModbus()) << "An error occured" << error; +} + + +void ModbusTCPMaster::onModbusStateChanged(QModbusDevice::State state) +{ + bool connected = (state != QModbusDevice::UnconnectedState); + if (!connected) { + //try to reconnect in 10 seconds + m_reconnectTimer->start(10000); + } + emit connectionStateChanged(connected); +} diff --git a/modbus/modbustcpmaster.h b/modbus/modbustcpmaster.h new file mode 100644 index 0000000..af6727e --- /dev/null +++ b/modbus/modbustcpmaster.h @@ -0,0 +1,85 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project 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 this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MODBUSTCPMASTER_H +#define MODBUSTCPMASTER_H + +#include +#include +#include +#include +#include + +class ModbusTCPMaster : public QObject +{ + Q_OBJECT +public: + explicit ModbusTCPMaster(const QHostAddress &hostAddress, uint port, QObject *parent = nullptr); + ~ModbusTCPMaster(); + + bool connectDevice(); + + QUuid readCoil(uint slaveAddress, uint registerAddress); + QUuid readDiscreteInput(uint slaveAddress, uint registerAddress); + QUuid readInputRegister(uint slaveAddress, uint registerAddress); + QUuid readHoldingRegister(uint slaveAddress, uint registerAddress); + + QUuid writeCoil(uint slaveAddress, uint registerAddress, bool status); + QUuid writeHoldingRegister(uint slaveAddress, uint registerAddress, const QVector &values); + + QHostAddress hostAddress(); + uint port(); + bool setHostAddress(const QHostAddress &hostAddress); + bool setPort(uint port); + + +private: + QTimer *m_reconnectTimer = nullptr; + QModbusTcpClient *m_modbusTcpClient; + +private slots: + void onReconnectTimer(); + + void onModbusErrorOccurred(QModbusDevice::Error error); + void onModbusStateChanged(QModbusDevice::State state); + +signals: + void connectionStateChanged(bool status); + + void writeRequestExecuted(const QUuid &requestId, bool success); + void writeRequestError(const QUuid &requestId, const QString &error); + + void receivedCoil(uint slaveAddress, uint modbusRegister, bool value); + void receivedDiscreteInput(uint slaveAddress, uint modbusRegister, bool value); + void receivedHoldingRegister(uint slaveAddress, uint modbusRegister, const QVector &values); + void receivedInputRegister(uint slaveAddress, uint modbusRegister, uint value); +}; + +#endif // MODBUSTCPMASTER_H