diff --git a/debian/control b/debian/control index 5273345..b0d6e2e 100644 --- a/debian/control +++ b/debian/control @@ -5,6 +5,7 @@ Maintainer: Bernhard Trinnes Build-depends: debhelper (>= 0.0.0), libnymea1-dev (>= 0.17), libnymea-mqtt-dev, + libqt5serialbus5-dev, libqt5serialport5-dev, nymea-dev-tools:native, pkg-config, @@ -46,6 +47,22 @@ Description: nymea.io plugin for Fronius PV inverters This package will install the nymea.io plugin for Fronius +Package: nymea-plugin-modbuscommander +Architecture: any +Multi-Arch: same +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Description: nymea.io plugin to send and receive generic modbus commands + The nymea daemon is a plugin based IoT (Internet of Things) server. The + server works like a translator for devices, things and services and + allows them to interact. + With the powerful rule engine you are able to connect any device available + in the system and create individual scenes and behaviors for your environment. + . + This package will install the nymea.io modbus commander plug-in + + Package: nymea-plugin-mypv Architecture: any Depends: ${shlibs:Depends}, @@ -97,6 +114,7 @@ Section: libs Architecture: all Depends: nymea-plugin-drexelundweiss, nymea-plugin-fronius, + nymea-plugin-modbuscommander, nymea-plugin-mypv nymea-plugin-wallbe, Description: Plugins for nymea IoT server - the modbus plugin collection diff --git a/debian/nymea-plugin-modbuscommander.install.in b/debian/nymea-plugin-modbuscommander.install.in new file mode 100644 index 0000000..ac09435 --- /dev/null +++ b/debian/nymea-plugin-modbuscommander.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_devicepluginmodbuscommander.so diff --git a/modbuscommander/README.md b/modbuscommander/README.md new file mode 100644 index 0000000..ed84d53 --- /dev/null +++ b/modbuscommander/README.md @@ -0,0 +1,28 @@ +# Modbus Commander + +A nymea plugin to send Modbus commands. + +## Supported Things + +* Modbus TCP client + * Gateway +* Modbus RTU client + * Gateway + * Discovery setup +* Coil + * Writes and reads a single Modbus Coil +* Discrete input + * Reads a single Modbus discrete input +* Input register + * Reads a single Modbus input register +* Holding register + * Writes and reads a single Modbus holding register + +## Requirements + +* The package 'nymea-plugin-modbuscommander' must be installed. +* For Modbus RTU a serial port must be available. + +## More + +http://www.modbus.org/ diff --git a/modbuscommander/integrationpluginmodbuscommander.cpp b/modbuscommander/integrationpluginmodbuscommander.cpp new file mode 100644 index 0000000..b88d92d --- /dev/null +++ b/modbuscommander/integrationpluginmodbuscommander.cpp @@ -0,0 +1,580 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "integrationpluginmodbuscommander.h" +#include "plugininfo.h" + +#include + +IntegrationPluginModbusCommander::IntegrationPluginModbusCommander() +{ +} + +void IntegrationPluginModbusCommander::init() +{ + connect(this, &IntegrationPluginModbusCommander::configValueChanged, this, &IntegrationPluginModbusCommander::onPluginConfigurationChanged); + //QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = false")); + + m_slaveAddressParamTypeId.insert(coilThingClassId, coilThingSlaveAddressParamTypeId); + m_slaveAddressParamTypeId.insert(inputRegisterThingClassId, inputRegisterThingSlaveAddressParamTypeId); + m_slaveAddressParamTypeId.insert(discreteInputThingClassId, discreteInputThingSlaveAddressParamTypeId); + m_slaveAddressParamTypeId.insert(holdingRegisterThingClassId, holdingRegisterThingSlaveAddressParamTypeId); + + m_registerAddressParamTypeId.insert(coilThingClassId, coilThingRegisterAddressParamTypeId); + m_registerAddressParamTypeId.insert(inputRegisterThingClassId, inputRegisterThingRegisterAddressParamTypeId); + m_registerAddressParamTypeId.insert(discreteInputThingClassId, discreteInputThingRegisterAddressParamTypeId); + m_registerAddressParamTypeId.insert(holdingRegisterThingClassId, holdingRegisterThingRegisterAddressParamTypeId); + + m_connectedStateTypeId.insert(coilThingClassId, coilConnectedStateTypeId); + m_connectedStateTypeId.insert(inputRegisterThingClassId, inputRegisterConnectedStateTypeId); + m_connectedStateTypeId.insert(discreteInputThingClassId, discreteInputConnectedStateTypeId); + m_connectedStateTypeId.insert(holdingRegisterThingClassId, holdingRegisterConnectedStateTypeId); + + m_valueStateTypeId.insert(coilThingClassId, coilValueStateTypeId); + m_valueStateTypeId.insert(inputRegisterThingClassId, inputRegisterValueStateTypeId); + m_valueStateTypeId.insert(discreteInputThingClassId, discreteInputValueStateTypeId); + m_valueStateTypeId.insert(holdingRegisterThingClassId, holdingRegisterValueStateTypeId); +} + + +void IntegrationPluginModbusCommander::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (thing->thingClassId() == modbusTCPClientThingClassId) { + QString ipAddress = thing->paramValue(modbusTCPClientThingIpv4addressParamTypeId).toString(); + uint port = thing->paramValue(modbusTCPClientThingPortParamTypeId).toUInt(); + + foreach (ModbusTCPMaster *modbusTCPMaster, m_modbusTCPMasters.values()) { + if ((modbusTCPMaster->ipv4Address() == ipAddress) && (modbusTCPMaster->port() == port)){ + m_modbusTCPMasters.insert(thing, modbusTCPMaster); + return info->finish(Thing::ThingErrorNoError); + } + } + + ModbusTCPMaster *modbusTCPMaster = new ModbusTCPMaster(ipAddress, port, this); + connect(modbusTCPMaster, &ModbusTCPMaster::connectionStateChanged, this, &IntegrationPluginModbusCommander::onConnectionStateChanged); + connect(modbusTCPMaster, &ModbusTCPMaster::requestExecuted, this, &IntegrationPluginModbusCommander::onRequestExecuted); + connect(modbusTCPMaster, &ModbusTCPMaster::requestError, this, &IntegrationPluginModbusCommander::onRequestError); + connect(modbusTCPMaster, &ModbusTCPMaster::receivedCoil, this, &IntegrationPluginModbusCommander::onReceivedCoil); + connect(modbusTCPMaster, &ModbusTCPMaster::receivedDiscreteInput, this, &IntegrationPluginModbusCommander::onReceivedDiscreteInput); + connect(modbusTCPMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &IntegrationPluginModbusCommander::onReceivedHoldingRegister); + connect(modbusTCPMaster, &ModbusTCPMaster::receivedInputRegister, this, &IntegrationPluginModbusCommander::onReceivedInputRegister); + modbusTCPMaster->connectDevice(); + m_modbusTCPMasters.insert(thing, modbusTCPMaster); + m_asyncTCPSetup.insert(modbusTCPMaster, info); + return; + + } else if (thing->thingClassId() == modbusRTUClientThingClassId) { + + QString serialPort = thing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString(); + uint baudrate = thing->paramValue(modbusRTUClientThingBaudRateParamTypeId).toUInt(); + uint stopBits = thing->paramValue(modbusRTUClientThingStopBitsParamTypeId).toUInt(); + uint dataBits = thing->paramValue(modbusRTUClientThingDataBitsParamTypeId).toUInt(); + QSerialPort::Parity parity = QSerialPort::Parity::NoParity; + if (thing->paramValue(modbusRTUClientThingParityParamTypeId).toString().contains("No")) { + parity = QSerialPort::Parity::NoParity; + } else if (thing->paramValue(modbusRTUClientThingParityParamTypeId).toString().contains("Even")) { + parity = QSerialPort::Parity::EvenParity; + } else if (thing->paramValue(modbusRTUClientThingParityParamTypeId).toString().contains("Odd")) { + parity = QSerialPort::Parity::OddParity; + } + + ModbusRTUMaster *modbusRTUMaster = new ModbusRTUMaster(serialPort, baudrate, parity, dataBits, stopBits, this); + connect(modbusRTUMaster, &ModbusRTUMaster::connectionStateChanged, this, &IntegrationPluginModbusCommander::onConnectionStateChanged); + connect(modbusRTUMaster, &ModbusRTUMaster::requestExecuted, this, &IntegrationPluginModbusCommander::onRequestExecuted); + connect(modbusRTUMaster, &ModbusRTUMaster::requestError, this, &IntegrationPluginModbusCommander::onRequestError); + connect(modbusRTUMaster, &ModbusRTUMaster::receivedCoil, this, &IntegrationPluginModbusCommander::onReceivedCoil); + connect(modbusRTUMaster, &ModbusRTUMaster::receivedDiscreteInput, this, &IntegrationPluginModbusCommander::onReceivedDiscreteInput); + connect(modbusRTUMaster, &ModbusRTUMaster::receivedHoldingRegister, this, &IntegrationPluginModbusCommander::onReceivedHoldingRegister); + connect(modbusRTUMaster, &ModbusRTUMaster::receivedInputRegister, this, &IntegrationPluginModbusCommander::onReceivedInputRegister); + modbusRTUMaster->connectDevice(); + m_modbusRTUMasters.insert(thing, modbusRTUMaster); + m_asyncRTUSetup.insert(modbusRTUMaster, info); + return; + + } else if ((thing->thingClassId() == coilThingClassId) + || (thing->thingClassId() == discreteInputThingClassId) + || (thing->thingClassId() == holdingRegisterThingClassId) + || (thing->thingClassId() == inputRegisterThingClassId)) { + info->finish(Thing::ThingErrorNoError); + return; + } + qCWarning(dcModbusCommander()) << "Unhandled thing class in setupDevice!"; + info->finish(Thing::ThingErrorSetupFailed); +} + +void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info) +{ + ThingClassId thingClassId = info->thingClassId(); + + if (thingClassId == modbusRTUClientThingClassId) { + Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) { + //Serial port is not yet used, create now a new one + qCDebug(dcModbusCommander()) << "Found serial port:" << port.systemLocation(); + QString description = port.manufacturer() + " " + port.description(); + ThingDescriptor thingDescriptor(thingClassId, port.portName(), description); + ParamList parameters; + QString serialPort = port.systemLocation(); + foreach (Thing *existingThing, myThings()) { + if (existingThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString() == serialPort) { + thingDescriptor.setThingId(existingThing->id()); + break; + } + } + parameters.append(Param(modbusRTUClientThingSerialPortParamTypeId, serialPort)); + thingDescriptor.setParams(parameters); + info->addThingDescriptor(thingDescriptor); + } + info->finish(Thing::ThingErrorNoError); + return; + } else if (thingClassId == discreteInputThingClassId) { + Q_FOREACH(Thing *clientThing, myThings()){ + if (clientThing->thingClassId() == modbusTCPClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusTCPClientThingIpv4addressParamTypeId).toString() + " Port: " + clientThing->paramValue(modbusTCPClientThingPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + if (clientThing->thingClassId() == modbusRTUClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + return; + + } else if (thingClassId == coilThingClassId) { + Q_FOREACH(Thing *clientThing, myThings()){ + if (clientThing->thingClassId() == modbusTCPClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusTCPClientThingIpv4addressParamTypeId).toString() + " Port: " + clientThing->paramValue(modbusTCPClientThingPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + if (clientThing->thingClassId() == modbusRTUClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + return; + } else if (thingClassId == holdingRegisterThingClassId) { + Q_FOREACH(Thing *clientThing, myThings()){ + if (clientThing->thingClassId() == modbusTCPClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusTCPClientThingIpv4addressParamTypeId).toString() + " Port: " + clientThing->paramValue(modbusTCPClientThingPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + if (clientThing->thingClassId() == modbusRTUClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + return; + + } else if (thingClassId == inputRegisterThingClassId) { + Q_FOREACH(Thing *clientThing, myThings()){ + if (clientThing->thingClassId() == modbusTCPClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusTCPClientThingIpv4addressParamTypeId).toString() + " Port: " + clientThing->paramValue(modbusTCPClientThingPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + if (clientThing->thingClassId() == modbusRTUClientThingClassId) { + ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString()); + descriptor.setParentId(clientThing->id()); + info->addThingDescriptor(descriptor); + } + } + info->finish(Thing::ThingErrorNoError); + return; + } + info->finish(Thing::ThingErrorThingClassNotFound); + qCWarning(dcModbusCommander()) << "Unhandled device class in discovery!"; +} + +void IntegrationPluginModbusCommander::postSetupThing(Thing *info) +{ + if (!m_refreshTimer) { + // Refresh timer for TCP read + int refreshTime = configValue(modbusCommanderPluginUpdateIntervalParamTypeId).toInt(); + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(refreshTime); + connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginModbusCommander::onRefreshTimer); + } + + if ((info->thingClassId() == coilThingClassId) || + (info->thingClassId() == discreteInputThingClassId) || + (info->thingClassId() == holdingRegisterThingClassId) || + (info->thingClassId() == inputRegisterThingClassId)) { + readRegister(info); + } +} + + +void IntegrationPluginModbusCommander::executeAction(ThingActionInfo *info) +{ + Thing *thing = info->thing(); + + if (thing->thingClassId() == coilThingClassId) { + + if (info->action().actionTypeId() == coilValueActionTypeId) { + writeRegister(thing, info); + return; + } + } else if (thing->thingClassId() == holdingRegisterThingClassId) { + + if (info->action().actionTypeId() == holdingRegisterValueActionTypeId) { + writeRegister(thing, info); + return; + } + } + qCWarning(dcModbusCommander()) << "Unhandled deviceclass/actiontype in executeAction!"; + info->finish(Thing::ThingErrorThingClassNotFound); +} + + +void IntegrationPluginModbusCommander::thingRemoved(Thing *thing) +{ + if (thing->thingClassId() == modbusTCPClientThingClassId) { + ModbusTCPMaster *modbus = m_modbusTCPMasters.take(thing); + modbus->deleteLater(); + } + + if (thing->thingClassId() == modbusRTUClientThingClassId) { + ModbusRTUMaster *modbus = m_modbusRTUMasters.take(thing); + modbus->deleteLater(); + } + + if (myThings().empty()) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; + } +} + +void IntegrationPluginModbusCommander::onRefreshTimer() +{ + foreach (Thing *thing, myThings()) { + if ((thing->thingClassId() == coilThingClassId) || + (thing->thingClassId() == discreteInputThingClassId) || + (thing->thingClassId() == holdingRegisterThingClassId) || + (thing->thingClassId() == inputRegisterThingClassId)) { + readRegister(thing); + } + } +} + +void IntegrationPluginModbusCommander::onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value) +{ + // Check refresh schedule + if (paramTypeId == modbusCommanderPluginUpdateIntervalParamTypeId) {; + if (m_refreshTimer) { + uint refreshTime = value.toUInt(); + m_refreshTimer->stop(); + m_refreshTimer->startTimer(refreshTime); + } + } +} + +void IntegrationPluginModbusCommander::onConnectionStateChanged(bool status) +{ + auto modbus = sender(); + + if (m_asyncRTUSetup.contains(static_cast(modbus))) { + ThingSetupInfo *info = m_asyncRTUSetup.take(static_cast(modbus)); + info->finish(Thing::ThingErrorNoError); + + } else if (m_asyncTCPSetup.contains(static_cast(modbus))) { + ThingSetupInfo *info = m_asyncTCPSetup.take(static_cast(modbus)); + info->finish(Thing::ThingErrorNoError); + } + + if (m_modbusRTUMasters.values().contains(static_cast(modbus))) { + Thing *thing = m_modbusRTUMasters.key(static_cast(modbus)); + thing->setStateValue(modbusRTUClientConnectedStateTypeId, status); + } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) { + Thing *thing = m_modbusTCPMasters.key(static_cast(modbus)); + thing->setStateValue(modbusTCPClientConnectedStateTypeId, status); + } +} + +void IntegrationPluginModbusCommander::onRequestExecuted(QUuid requestId, bool success) +{ + if (m_asyncActions.contains(requestId)){ + ThingActionInfo *info = m_asyncActions.take(requestId); + if (success){ + info->finish(Thing::ThingErrorNoError); + } else { + info->finish(Thing::ThingErrorHardwareNotAvailable); + } + info->thing()->setStateValue(m_connectedStateTypeId.value(info->thing()->thingClassId()), success); + } + + if (m_readRequests.contains(requestId)){ + Thing *thing = m_readRequests.take(requestId); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), success); + } +} + +void IntegrationPluginModbusCommander::onRequestError(QUuid requestId, const QString &error) +{ + if (m_asyncActions.contains(requestId)){ + ThingActionInfo *info = m_asyncActions.take(requestId); + info->finish(Thing::ThingErrorHardwareNotAvailable, error); + info->thing()->setStateValue(m_connectedStateTypeId.value(info->thing()->thingClassId()), false); + } + + if (m_readRequests.contains(requestId)){ + Thing *thing = m_readRequests.take(requestId); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), false); + } +} + +void IntegrationPluginModbusCommander::onReceivedCoil(quint32 slaveAddress, quint32 modbusRegister, bool value) +{ + auto modbus = sender(); + + if (m_modbusRTUMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusRTUMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == coilThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusTCPMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == coilThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } +} + +void IntegrationPluginModbusCommander::onReceivedDiscreteInput(quint32 slaveAddress, quint32 modbusRegister, bool value) +{ + auto modbus = sender(); + + if (m_modbusRTUMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusRTUMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == discreteInputThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusTCPMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == discreteInputThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } +} + +void IntegrationPluginModbusCommander::onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, int value) +{ + auto modbus = sender(); + + if (m_modbusRTUMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusRTUMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == holdingRegisterThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusTCPMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == holdingRegisterThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } +} + +void IntegrationPluginModbusCommander::onReceivedInputRegister(uint slaveAddress, uint modbusRegister, int value) +{ + auto modbus = sender(); + + if (m_modbusRTUMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusRTUMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == inputRegisterThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) { + Thing *parent = m_modbusTCPMasters.key(static_cast(modbus)); + foreach (Thing *thing, myThings().filterByParentId(parent->id())) { + if (thing->thingClassId() == inputRegisterThingClassId) { + if ((thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())) == slaveAddress) + && (thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())) == modbusRegister)) { + thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), value); + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true); + return; + } + } + } + } +} + +void IntegrationPluginModbusCommander::readRegister(Thing *thing) +{ + Thing *parent = myThings().findById(thing->parentId()); + if (!parent) { + qCWarning(dcModbusCommander()) << "Could not find parent device" << thing->name(); + return; + } + + uint registerAddress = thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())).toUInt();; + uint slaveAddress = thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())).toUInt(); + + QUuid requestId; + + if (parent->thingClassId() == modbusTCPClientThingClassId) { + ModbusTCPMaster *modbus = m_modbusTCPMasters.value(parent); + if (!modbus) + return; + + if (thing->thingClassId() == coilThingClassId) { + requestId = modbus->readCoil(slaveAddress, registerAddress); + } else if (thing->thingClassId() == discreteInputThingClassId) { + requestId = modbus->readDiscreteInput(slaveAddress, registerAddress); + } else if (thing->thingClassId() == holdingRegisterThingClassId) { + requestId = modbus->readHoldingRegister(slaveAddress, registerAddress); + } else if (thing->thingClassId() == inputRegisterThingClassId) { + requestId = modbus->readInputRegister(slaveAddress, registerAddress); + } + } else if (parent->thingClassId() == modbusRTUClientThingClassId) { + + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent); + if (!modbus) + return; + + if (thing->thingClassId() == coilThingClassId) { + requestId = modbus->readCoil(slaveAddress, registerAddress); + } else if (thing->thingClassId() == discreteInputThingClassId) { + requestId = modbus->readDiscreteInput(slaveAddress, registerAddress); + } else if (thing->thingClassId() == holdingRegisterThingClassId) { + requestId = modbus->readHoldingRegister(slaveAddress, registerAddress); + } else if (thing->thingClassId() == inputRegisterThingClassId) { + requestId = modbus->readInputRegister(slaveAddress, registerAddress); + } + } + if (!requestId.isNull()) { + m_readRequests.insert(requestId, thing); + QTimer::singleShot(5000, this, [requestId, this] {m_readRequests.remove(requestId);}); + } else { + // Request returned without an id + thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), false); + } +} + +void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionInfo *info) +{ + Thing *parent = myThings().findById(thing->parentId()); + if (!parent) { + qCWarning(dcModbusCommander()) << "Could not find parente device" << thing->name(); + return; + } + uint registerAddress = thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())).toUInt();; + uint slaveAddress = thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())).toUInt(); + + QUuid requestId; + Action action = info->action(); + + if (parent->thingClassId() == modbusTCPClientThingClassId) { + ModbusTCPMaster *modbus = m_modbusTCPMasters.value(parent); + if (!modbus) + return; + + if (thing->thingClassId() == coilThingClassId) { + requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool()); + } else if (thing->thingClassId() == holdingRegisterThingClassId) { + requestId = modbus->writeHoldingRegister(slaveAddress, registerAddress, action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt()); + } + + } else if (parent->thingClassId() == modbusRTUClientThingClassId) { + ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent); + if (!modbus) + return; + + if (thing->thingClassId() == coilThingClassId) { + requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool()); + } else if (thing->thingClassId() == holdingRegisterThingClassId) { + requestId = modbus->writeHoldingRegister(slaveAddress, registerAddress, action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt()); + } + } + + if (requestId.toString().isNull()){ + info->finish(Thing::ThingErrorHardwareNotAvailable); + } else { + m_asyncActions.insert(requestId, info); + connect(info, &ThingActionInfo::aborted, this, [requestId, this] {m_asyncActions.remove(requestId);}); + } +} diff --git a/modbuscommander/integrationpluginmodbuscommander.h b/modbuscommander/integrationpluginmodbuscommander.h new file mode 100644 index 0000000..f343caf --- /dev/null +++ b/modbuscommander/integrationpluginmodbuscommander.h @@ -0,0 +1,92 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 INTEGRATIONPLUGINMODBUSCOMMANDER_H +#define INTEGRATIONPLUGINMODBUSCOMMANDER_H + +#include "integrations/integrationplugin.h" +#include "plugintimer.h" +#include "modbustcpmaster.h" +#include "modbusrtumaster.h" + +#include +#include + +class IntegrationPluginModbusCommander: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmodbuscommander.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginModbusCommander(); + + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *info) override; + void executeAction(ThingActionInfo *info) override; + void thingRemoved(Thing *thing) override; + +private: + PluginTimer *m_refreshTimer = nullptr; + + QHash m_modbusRTUMasters; + QHash m_modbusTCPMasters; + QHash m_asyncActions; + QHash m_readRequests; + + QHash m_asyncRTUSetup; + QHash m_asyncTCPSetup; + + void readRegister(Thing *thing); + void writeRegister(Thing *thing, ThingActionInfo *info); + + QHash m_slaveAddressParamTypeId; + QHash m_registerAddressParamTypeId; + QHash m_connectedStateTypeId; + QHash m_valueStateTypeId; + +private slots: + void onRefreshTimer(); + + void onPluginConfigurationChanged(const ParamTypeId ¶mTypeId, const QVariant &value); + + void onConnectionStateChanged(bool status); + void onRequestExecuted(QUuid requestId, bool success); + void onRequestError(QUuid requestId, const QString &error); + void onReceivedCoil(quint32 slaveAddress, quint32 modbusRegister, bool value); + void onReceivedDiscreteInput(quint32 slaveAddress, quint32 modbusRegister, bool value); + void onReceivedHoldingRegister(quint32 slaveAddress, quint32 modbusRegister, int value); + void onReceivedInputRegister(quint32 slaveAddress, quint32 modbusRegister, int value); +}; + +#endif // INTEGRATIONPLUGINMODBUSCOMMANDER_H diff --git a/modbuscommander/integrationpluginmodbuscommander.json b/modbuscommander/integrationpluginmodbuscommander.json new file mode 100644 index 0000000..41da048 --- /dev/null +++ b/modbuscommander/integrationpluginmodbuscommander.json @@ -0,0 +1,287 @@ +{ + "displayName": "Modbus Commander", + "name": "ModbusCommander", + "id": "7dda1b6d-c37e-4c9f-a696-1666f9de66e6", + "paramTypes":[ + { + "id": "0606c221-b157-4086-885d-7e7b166540a1", + "name": "updateInterval", + "displayName": "Update interval", + "type": "uint", + "unit": "Seconds", + "defaultValue": 1 + } + ], + "vendors": [ + { + "name": "nymea", + "displayName": "nymea", + "id": "2062d64d-3232-433c-88bc-0d33c0ba2ba6", + "thingClasses": [ + { + "id": "35d3e7dc-1f33-4b8c-baa3-eb10b4f157a7", + "name": "modbusTCPClient", + "displayName": "Modbus TCP client", + "createMethods": ["user"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "2a3fcb23-931b-4ba1-b134-c49b656c76f7", + "name": "ipv4address", + "displayName": "IPv4 address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "127.0.0.1" + }, + { + "id": "bee8b151-815a-4159-9d8a-42b76e99b42c", + "name": "port", + "displayName": "Port", + "type": "uint", + "defaultValue": 502 + } + ], + "stateTypes": [ + { + "id": "725b541a-9e0c-4634-81eb-e415c0b8f012", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connection status changed", + "type": "bool", + "defaultValue": false + } + ] + }, + { + "id": "776df314-6186-4eb5-b824-f0d916f6d9c3", + "name": "modbusRTUClient", + "displayName": "Modbus RTU client", + "createMethods": ["discovery", "user"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "ed49f7d8-ab18-4c37-9b80-1004b75dcb91", + "name": "serialPort", + "displayName": "Serial port", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "ttyAMA0" + }, + { + "id": "45dfc828-f238-4263-89a3-9b35cf5dea39", + "name": "baudRate", + "displayName": "Baud rate", + "type": "uint", + "defaultValue": 9600 + }, + { + "id": "a27c664b-9f43-4573-a2cc-f65a8fa1a069", + "name": "dataBits", + "displayName": "Data bits", + "type": "uint", + "defaultValue": 8 + }, + { + "id": "4ea8bcdf-d4c5-45a4-a54f-f10ac3f08a78", + "name": "stopBits", + "displayName": "Stop bits", + "type": "uint", + "defaultValue": 1 + }, + { + "id": "72de1b08-2a27-49c5-90e0-8788c3ea1da3", + "name": "parity", + "displayName": "Parity", + "type": "QString", + "inputType": "TextLine", + "allowedValues": [ + "No Parity", + "Even Parity", + "Odd Parity" + ], + "defaultValue": "Even Parity" + } + ], + "stateTypes": [ + { + "id": "dffc59fe-b230-4345-81d6-0a55f9e16520", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connection status changed", + "type": "bool", + "defaultValue": false + } + ] + }, + { + "id": "f53524ea-1d06-40a9-b7a4-041297b21e84", + "name": "coil", + "displayName": "Coil", + "createMethods": ["discovery"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "d85977a2-4f9c-40f8-9aff-76cea7bd17a3", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "uint", + "defaultValue": 180 + }, + { + "id": "9d40c4ce-d251-43bb-a55e-a8780567bbac", + "name": "registerAddress", + "displayName": "Register address", + "type": "uint", + "defaultValue": 100 + } + ], + "stateTypes": [ + { + "id": "9b3852ac-1518-4417-8a0a-452fcfec8963", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connection status changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "1cd4cd53-3043-4ed9-9ba8-62985000c599", + "name": "value", + "displayName": "Value", + "displayNameAction": "Write value", + "displayNameEvent": "Value changed", + "type": "bool", + "writable": true, + "defaultValue": false + } + ] + }, + { + "id": "d7a15b39-48d3-4591-bdad-ec5e799aa6e5", + "name": "discreteInput", + "displayName": "Discrete input", + "createMethods": ["discovery"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "044d951d-7b58-4099-a9a6-a6dff61746a8", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "uint", + "defaultValue": 180 + }, + { + "id": "d37be0cc-6155-4894-b70f-cbc9adfbe48b", + "name": "registerAddress", + "displayName": "Register address", + "type": "uint", + "defaultValue": 100 + } + ], + "stateTypes": [ + { + "id": "dbe7c801-0888-4e7f-a88b-ba342efb11b6", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "displayNameEvent": "connection status changed" + }, + { + "id": "c772bd7f-6e51-4b28-b182-3b979c1298ce", + "name": "value", + "displayName": "Value", + "type": "bool", + "defaultValue": false, + "displayNameEvent": "value changed" + } + ] + }, + { + "id": "e4c34050-d115-440f-b332-63d36e3e12b8", + "name": "inputRegister", + "displayName": "Input register", + "createMethods": ["discovery"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "f66956ac-07cb-45ab-90e0-61c2a950b85a", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "uint", + "defaultValue": 180 + }, + { + "id": "264e381c-d259-4e11-b4b3-332b518ebba3", + "name": "registerAddress", + "displayName": "Register address", + "type": "uint", + "defaultValue": 100 + } + ], + "stateTypes": [ + { + "id": "0f3768cf-5fb2-4fbf-8614-8389f65f1e9d", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false, + "displayNameEvent": "Connection status changed" + }, + { + "id": "eabe2d1b-abe5-4063-adab-3cdd8500b286", + "name": "Value", + "displayName": "Value", + "type": "int", + "defaultValue": 0, + "displayNameEvent": "Value received" + } + ] + }, + { + "id": "61a2382c-3d9f-41a1-a2fd-27b2af203c56", + "name": "holdingRegister", + "displayName": "Holding register", + "createMethods": ["discovery"], + "interfaces": ["connectable"], + "paramTypes": [ + { + "id": "35879cf9-631c-4fe0-95c0-a4bb2e9039e6", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "uint", + "defaultValue": 180 + }, + { + "id": "c771e09e-15fe-4ea9-9662-c44e2df556a8", + "name": "registerAddress", + "displayName": "Register address", + "type": "uint", + "defaultValue": 100 + } + ], + "stateTypes": [ + { + "id": "1f55b72a-5d13-4ae1-b136-bfd84fd9761f", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connection status changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "585cc4fc-07da-415f-a176-12f3baeef025", + "name": "value", + "displayName": "Value", + "displayNameAction": "Write value", + "displayNameEvent": "Value changed", + "type": "int", + "writable": true, + "defaultValue": false + } + ] + } + ] + } + ] +} diff --git a/modbuscommander/meta.json b/modbuscommander/meta.json new file mode 100644 index 0000000..0f9aa45 --- /dev/null +++ b/modbuscommander/meta.json @@ -0,0 +1,14 @@ +{ + "title": "Modbus Commander", + "tagline": "Write and read Modbus register.", + "icon": "modbus.svg", + "stability": "community", + "offline": true, + "technologies": [ + "serial-port", + "network" + ], + "categories": [ + "tool" + ] +} diff --git a/modbuscommander/modbus.svg b/modbuscommander/modbus.svg new file mode 100644 index 0000000..acc30d9 --- /dev/null +++ b/modbuscommander/modbus.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modbuscommander/modbuscommander.pro b/modbuscommander/modbuscommander.pro new file mode 100644 index 0000000..98c7984 --- /dev/null +++ b/modbuscommander/modbuscommander.pro @@ -0,0 +1,16 @@ +include(../plugin.pri) + +QT += \ + serialport \ + network \ + serialbus \ + +SOURCES += \ + integrationpluginmodbuscommander.cpp \ + modbustcpmaster.cpp \ + modbusrtumaster.cpp \ + +HEADERS += \ + integrationpluginmodbuscommander.h \ + modbustcpmaster.h \ + modbusrtumaster.h \ diff --git a/modbuscommander/modbusrtumaster.cpp b/modbuscommander/modbusrtumaster.cpp new file mode 100644 index 0000000..5742168 --- /dev/null +++ b/modbuscommander/modbusrtumaster.cpp @@ -0,0 +1,371 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "extern-plugininfo.h" + +#include + +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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "Read error: " << m_modbusRtuSerialMaster->errorString(); + return ""; + } + return requestId; +} + + +void ModbusRTUMaster::onModbusErrorOccurred(QModbusDevice::Error error) +{ + qCWarning(dcModbusCommander()) << "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/modbuscommander/modbusrtumaster.h b/modbuscommander/modbusrtumaster.h new file mode 100644 index 0000000..cc8c7fb --- /dev/null +++ b/modbuscommander/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/modbuscommander/modbustcpmaster.cpp b/modbuscommander/modbustcpmaster.cpp new file mode 100644 index 0000000..7d03051 --- /dev/null +++ b/modbuscommander/modbustcpmaster.cpp @@ -0,0 +1,375 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "extern-plugininfo.h" + +ModbusTCPMaster::ModbusTCPMaster(QString IPv4Address, uint port, QObject *parent) : + QObject(parent) +{ + m_modbusTcpClient = new QModbusTcpClient(this); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkPortParameter, port); + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, IPv4Address); + //m_modbusTcpClient->setTimeout(100); + //m_modbusTcpClient->setNumberOfRetries(1); + + 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(dcModbusCommander()) << "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::setIPv4Address(QString ipv4Address) +{ + m_modbusTcpClient->setConnectionParameter(QModbusDevice::NetworkAddressParameter, ipv4Address); + 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); + } +} + +QString ModbusTCPMaster::ipv4Address() +{ + return 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, this, [reply, requestId, this] { + reply->deleteLater(); + + 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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "Modbus reply 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(dcModbusCommander()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::writeHoldingRegister(uint slaveAddress, uint registerAddress, uint value) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + request.setValue(0, static_cast(value)); + + if (QModbusReply *reply = m_modbusTcpClient->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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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, this, [reply, requestId, this] { + reply->deleteLater(); + 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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "Modbus replay error:" << error; + QModbusReply *reply = qobject_cast(sender()); + 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(dcModbusCommander()) << "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, this, [reply, requestId, this] { + reply->deleteLater(); + 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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "Modbus reply 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(dcModbusCommander()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + +QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddress) +{ + if (!m_modbusTcpClient) { + return ""; + } + QUuid requestId = QUuid::createUuid(); + + QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, registerAddress, 1); + + if (QModbusReply *reply = m_modbusTcpClient->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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "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(dcModbusCommander()) << "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, 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(dcModbusCommander()) << "Read response error:" << reply->error(); + } + reply->deleteLater(); + }); + connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){ + + qCWarning(dcModbusCommander()) << "Modbus reply 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(dcModbusCommander()) << "Read error: " << m_modbusTcpClient->errorString(); + return ""; + } + return requestId; +} + + +void ModbusTCPMaster::onModbusErrorOccurred(QModbusDevice::Error error) +{ + qCWarning(dcModbusCommander()) << "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/modbuscommander/modbustcpmaster.h b/modbuscommander/modbustcpmaster.h new file mode 100644 index 0000000..0862c48 --- /dev/null +++ b/modbuscommander/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(QString ipAddress, 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, uint data); + + QString ipv4Address(); + uint port(); + bool setIPv4Address(QString ipAddress); + 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 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 // MODBUSTCPMASTER_H diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index be7bf15..e95bf13 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -3,6 +3,7 @@ TEMPLATE = subdirs PLUGIN_DIRS = \ drexelundweiss \ fronius \ + modbuscommander \ mypv \ wallbe \