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