diff --git a/debian/control b/debian/control
index 35e0a9e..0e628d8 100644
--- a/debian/control
+++ b/debian/control
@@ -29,6 +29,22 @@ Description: nymea.io plugin for Drexel & Weiss heat pumps
This package will install the nymea.io plugin for Drexel & Weiss heat pumps
+Package: nymea-plugin-idm
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-modbus-translations,
+Description: nymea.io plugin for iDM heat pumps
+ 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 plugin for iDM heat pumps
+
+
Package: nymea-plugin-modbuscommander
Architecture: any
Section: libs
diff --git a/debian/nymea-plugin-idm.install.in b/debian/nymea-plugin-idm.install.in
new file mode 100644
index 0000000..f215b60
--- /dev/null
+++ b/debian/nymea-plugin-idm.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginidm.so
diff --git a/idm/README.md b/idm/README.md
new file mode 100644
index 0000000..bbc94ec
--- /dev/null
+++ b/idm/README.md
@@ -0,0 +1,18 @@
+# iDM
+
+Connect nymea to iDM heat pumps.
+
+## Supported Things
+
+* Navigator 2.0 based heat pump models
+
+## Requirements
+
+* The package 'nymea-plugin-idm' must be installed
+* Navigator 2.0 settings
+ * "Modbus TCP" must be selected in the "Building Management System" menu?
+* Both devices must be in the same local area network.
+
+## More
+
+https://www.idm-energie.at/en/
diff --git a/idm/idm.cpp b/idm/idm.cpp
new file mode 100644
index 0000000..6890cae
--- /dev/null
+++ b/idm/idm.cpp
@@ -0,0 +1,198 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 "idm.h"
+#include "extern-plugininfo.h"
+#include "../modbus/modbushelpers.h"
+
+#include
+
+Idm::Idm(const QHostAddress &address, QObject *parent) :
+ QObject(parent),
+ m_hostAddress(address)
+{
+ qCDebug(dcIdm()) << "iDM: Creating iDM connection" << m_hostAddress.toString();
+ m_modbusMaster = new ModbusTCPMaster(address, 502, this);
+
+ if (m_modbusMaster) {
+ qCDebug(dcIdm()) << "iDM: Created ModbusTCPMaster";
+ connect(m_modbusMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &Idm::onReceivedHoldingRegister);
+ connect(m_modbusMaster, &ModbusTCPMaster::readRequestError, this, &Idm::onModbusError);
+ connect(m_modbusMaster, &ModbusTCPMaster::writeRequestError, this, &Idm::onModbusError);
+ connect(m_modbusMaster, &ModbusTCPMaster::writeRequestExecuted, this, &Idm::writeRequestExecuted);
+ }
+}
+
+Idm::~Idm()
+{
+ qCDebug(dcIdm()) << "iDM: Deleting iDM connection" << m_hostAddress.toString();
+}
+
+bool Idm::connectDevice()
+{
+ qCDebug(dcIdm()) << "iDM: Connecting device";
+ return m_modbusMaster->connectDevice();
+}
+
+QHostAddress Idm::getIdmAddress() const
+{
+ return m_hostAddress;
+}
+
+void Idm::getStatus()
+{
+ //this request starts an update cycle
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::OutsideTemperature, 2);
+}
+
+QUuid Idm::setTargetTemperature(double targetTemperature)
+{
+ QVector value = ModbusHelpers::convertFloatToRegister(targetTemperature);
+ return m_modbusMaster->writeHoldingRegisters(Idm::modbusUnitID, Idm::RegisterList::RoomTemperatureTargetHeatingEcoHKA, value);
+}
+
+void Idm::onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector &value)
+{
+ Q_UNUSED(slaveAddress);
+ /* qCDebug(dcIdm()) << "receivedHoldingRegister " << modbusRegister << "(length: " << value.length() << ")"; */
+
+ switch (modbusRegister) {
+ case Idm::OutsideTemperature:
+ /* qCDebug(dcIdm()) << "received outside temperature"; */
+ if (value.length() == 2) {
+ m_idmInfo.outsideTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::OutsideTemperature - modbusRegister]);
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::CurrentFaultNumber, 1);
+ });
+ break;
+ case Idm::CurrentFaultNumber:
+ /* qCDebug(dcIdm()) << "current fault number"; */
+ if (value.length() == 1) {
+ if (value[0] > 0) {
+ m_idmInfo.error = true;
+ } else {
+ m_idmInfo.error = false;
+ }
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::HeatStorageTemperature, 2);
+ });
+ break;
+ case Idm::HeatStorageTemperature:
+ /* qCDebug(dcIdm()) << "received storage temperature"; */
+ if (value.length() == 2) {
+ m_idmInfo.waterTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::HeatStorageTemperature - modbusRegister]);
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::TargetHotWaterTemperature, 1);
+ });
+ break;
+ case Idm::TargetHotWaterTemperature:
+ /* qCDebug(dcIdm()) << "received target hot water temperature"; */
+ if (value.length() == 1) {
+ /* The hot water target temperature is stored as UCHAR (manual p. 13) */
+ m_idmInfo.targetWaterTemperature = (double)value[RegisterList::TargetHotWaterTemperature - modbusRegister];
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::HeatPumpOperatingMode, 1);
+ });
+ break;
+ case Idm::HeatPumpOperatingMode:
+ /* qCDebug(dcIdm()) << "received heat pump operating mode"; */
+ if (value.length() == 1) {
+ m_idmInfo.mode = heatPumpOperationModeToString((Idm::IdmHeatPumpMode)value[RegisterList::HeatPumpOperatingMode-modbusRegister]);
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::RoomTemperatureHKA, 2);
+ });
+ break;
+ case Idm::RoomTemperatureHKA:
+ /* qCDebug(dcIdm()) << "received room temperature hka"; */
+ if (value.length() == 2) {
+ m_idmInfo.roomTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::RoomTemperatureHKA - modbusRegister]);
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::RoomTemperatureTargetHeatingEcoHKA, 2);
+ });
+ break;
+ case Idm::RoomTemperatureTargetHeatingEcoHKA:
+ /* qCDebug(dcIdm()) << "received room temprature hka eco"; */
+ if (value.length() == 2) {
+ m_idmInfo.targetRoomTemperature = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::RoomTemperatureTargetHeatingEcoHKA - modbusRegister]);
+ }
+ QTimer::singleShot(200, this, [this] {
+ m_modbusMaster->readHoldingRegister(Idm::modbusUnitID, Idm::CurrentPowerConsumptionHeatPump, 2);
+ });
+ break;
+ case Idm::CurrentPowerConsumptionHeatPump:
+ /* qCDebug(dcIdm()) << "received power consumption heat pump"; */
+ if (value.length() == 2) {
+ m_idmInfo.powerConsumptionHeatPump = ModbusHelpers::convertRegisterToFloat(&value[RegisterList::CurrentPowerConsumptionHeatPump - modbusRegister]);
+ }
+
+ /* Everything read without an error
+ * -> set connected to true */
+ m_idmInfo.connected = true;
+ emit statusUpdated(m_idmInfo);
+ break;
+ }
+}
+
+void Idm::onModbusError()
+{
+ qCDebug(dcIdm()) << "iDM: Received modbus error" << m_modbusMaster->errorString();
+ m_idmInfo.connected = false;
+ emit statusUpdated(m_idmInfo);
+}
+
+QString Idm::heatPumpOperationModeToString(IdmHeatPumpMode mode)
+{
+ QString result{};
+ /* Operation modes according to table of manual p. 14 */
+ switch (mode) {
+ case IdmHeatPumpModeOff:
+ result = "Off";
+ break;
+ case IdmHeatPumpModeHeating:
+ result = "Heating";
+ break;
+ case IdmHeatPumpModeCooling:
+ result = "Cooling";
+ break;
+ case IdmHeatPumpModeHotWater:
+ result = "Hot water";
+ break;
+ case IdmHeatPumpModeDefrost:
+ result = "Defrost";
+ break;
+ }
+ return result;
+}
diff --git a/idm/idm.h b/idm/idm.h
new file mode 100644
index 0000000..7a70efc
--- /dev/null
+++ b/idm/idm.h
@@ -0,0 +1,183 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 IDM_H
+#define IDM_H
+
+#include
+
+#include "../modbus/modbustcpmaster.h"
+
+#include "idminfo.h"
+
+/*
+ * Functionality:
+ * The current version allows read access to selected
+ * modbus registers:
+ * + Room temperature (HK A)
+ * + Water temperature
+ * + Outside air temperature
+ * + Target room temperature eco mode (HK A)
+ * + Target water temperature
+ * + Current power consumption
+ * + Operation mode
+ * + Error
+ *
+ * At present there is no write access for target
+ * room temperature and target water temperature. These
+ * result in an "invalid data access" error from the
+ * device.
+ */
+
+/* Note: It would be desirable to read the modbus registers
+ * of the Idm heat pump in groups to minimize the number
+ * of read requests. However, a maximum of 6 registers
+ * can be read simultaneously. With the given set of
+ * addresses it is not possible to reasonably group the
+ * registers, therefore they are read individually.
+ */
+
+class Idm : public QObject
+{
+ Q_OBJECT
+public:
+ explicit Idm(const QHostAddress &address, QObject *parent = nullptr);
+ ~Idm();
+
+ bool connectDevice();
+ QHostAddress getIdmAddress() const;
+ QUuid setTargetTemperature(double targetTemperature);
+ void getStatus();
+
+private:
+ /** Modbus Unit ID of Idm device */
+ static const quint16 modbusUnitID = 1;
+
+ enum IscModus {
+ KeineAbwarme = 0,
+ Heizung = 1,
+ Warmwasser = 4,
+ Warmequelle = 8,
+ };
+
+ /** System operation modes according to manual p. 13 */
+ enum IdmSysMode {
+ IdmSysModeStandby = 0,
+ IdmSysModeAutomatic = 1,
+ IdmSysModeAway = 2,
+ IdmSysModeOnlyHotwater = 4,
+ IdmSysModeOnlyRoomHeating = 5
+ };
+
+ /** Heat pump operation modes according to manual p. 14 */
+ enum IdmHeatPumpMode {
+ IdmHeatPumpModeOff = 0,
+ IdmHeatPumpModeHeating = 1,
+ IdmHeatPumpModeCooling = 2,
+ IdmHeatPumpModeHotWater = 4,
+ IdmHeatPumpModeDefrost = 8
+ };
+
+ /** The following modbus addresses are according to the manual
+ * Modbus TCP Navigatorregelung 2.0 pages 13-31.
+ * Comments at the end of each line give their original name
+ * in the German manual. */
+ enum RegisterList {
+ OutsideTemperature = 1000, // Außentemperatur (B31)
+ MeanOutsideTemperature = 1002, // Gemittelte Außentemperature
+ CurrentFaultNumber = 1004, // Aktuelle Störungsnummer
+ OperationModeSystem = 1005, // Betriebsart System
+ SmartGridStatus = 1006, // Smart Grid Status
+ HeatStorageTemperature = 1008, // Wärmespeichertemperatur (B38)
+ ColdStorageTemperature = 1010, // Kältespeichertemperatur (B40)
+ DrinkingWaterHeaterTempBottom = 1012, // Trinkwassererwärmertmp. unten (B41)
+ DrinkingWaterHeaterTempTop = 1014, // Trinkwassererwärmertmp. oben (B48)
+ HotWaterTapTemperature = 1030, // Warmwasserzapftemperatur (B42)
+ TargetHotWaterTemperature = 1032, // Warmwasser-Solltemperatur
+ HeatPumpOperatingMode = 1090, // Betriebsart Wärmepumpe
+ SummationFaultHeatPump = 1099, // Summenstörung Wärepumpe
+ RoomTemperatureHKA = 1364, // Heizkreis A Raumtemperature (B61)
+ HumiditySensor = 1392, // Feuchtesensor
+ RoomTemperatureTargetHeatingHKA = 1401, // Raumsolltemperatur Heizen Normal HK A
+ RoomTemperatureTargetHeatingEcoHKA = 1415, // Raumsolltemperatur Heizen Eco HK A
+ ExternalOutsideTemperature = 1690, // Externe Außentemperatur
+ ExternalHumidity = 1692, // Externe Feuchte
+ ExternalRequestTemperatureHeating = 1694, // Externe Anforderungstemperatur Heizen
+ ExternalRequestTemperatureCooling = 1695, // Externe Anforderungstemperatur Kühlen
+ HeatingRequirement = 1710, // Anforderung Heizen
+ CoolingRequirement = 1711, // Anforderung Kühlen
+ HotWaterChargingRequirement = 1712, // Anforderung Warmwasserladung
+ HeatQuantityHeating = 1750, // Wärmemenge Heizen
+ HeatQuantityCooling = 1752, // Wärmemenge Kühlen
+ HeatQuantityHotWater = 1754, // Wärmemenge Warmwasser
+ HeatQuantityDefrosting = 1756, // Wärmemenge Abtauung
+ HeatQuantityPassiveCooling = 1758, // Wärmemenge Passive Kühlung,
+ HeatQuantityPhotovolatics = 1760, // Wärmemenge Solar
+ HeatQuantityHeatingElemetn = 1762, // Wärmemenge Elektroheizeinsatz,
+ CurrentPower = 1790, // Momentanleistung
+ CurrentPowerSolar = 1792, // MomentanleistungSolar
+ SolarCollectorTemperature = 1850, // SolarKollektortemperatur (B73)
+ SolarCollectorReturnTemperature = 1852, // SolarKollektorruecklauftemperatur (B75)
+ SolarChargeTemperature = 1854, // SolarLadetemperatur (B74)
+ SolarOperatingMode = 1856, // Betriebsart Solar
+ ISCMode = 1875, // ISCModus
+ AcknowledgeFaultMessages = 1999, // Störmeldungen quittieren
+ TargetRoomTemperatureZ1R1 = 2004, // Zonenmodul 1 Raumsolltemperatur Raum 1
+ CurrentPhotovoltaicsSurplus = 74, // Aktueller PV-Überschuss
+ CurrentPhotovoltaicsProduction = 78, // Aktueller PV Produktion
+ CurrentPowerConsumptionHeatPump = 4122, // Aktuelle Leistungsaufnahme Wärmepumpe
+ };
+
+ /* Note: This class only requires one IP address and one
+ * TCP Modbus connection. Multiple devices are managed
+ * within the IntegrationPluginIdm class. */
+ QHostAddress m_hostAddress;
+
+ /** Pointer to ModbusTCPMaster object, responsible for low-level communicaiton */
+ ModbusTCPMaster *m_modbusMaster = nullptr;
+
+ /** This structure is allocated within onRequestStatus and filled
+ * by the receivedStatusGroupx functions */
+ IdmInfo m_idmInfo;
+
+ /** Converts a heat pump operation mode code to a string (according to manual p. 14) */
+ QString heatPumpOperationModeToString(IdmHeatPumpMode mode);
+
+signals:
+ void statusUpdated(const IdmInfo &info);
+ void targetRoomTemperatureChanged();
+ void writeRequestExecuted(const QUuid &requestId, bool success);
+
+private slots:
+ void onModbusError();
+ void onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector &value);
+};
+
+#endif // IDM_H
diff --git a/idm/idm.png b/idm/idm.png
new file mode 100644
index 0000000..1306b1f
Binary files /dev/null and b/idm/idm.png differ
diff --git a/idm/idm.pro b/idm/idm.pro
new file mode 100644
index 0000000..6bc6551
--- /dev/null
+++ b/idm/idm.pro
@@ -0,0 +1,19 @@
+include(../plugins.pri)
+
+QT += \
+ network \
+ serialbus \
+
+SOURCES += \
+ idm.cpp \
+ integrationpluginidm.cpp \
+ ../modbus/modbustcpmaster.cpp \
+ ../modbus/modbushelpers.cpp \
+
+HEADERS += \
+ idm.h \
+ idminfo.h \
+ integrationpluginidm.h \
+ ../modbus/modbustcpmaster.h \
+ ../modbus/modbushelpers.h \
+
diff --git a/idm/idminfo.h b/idm/idminfo.h
new file mode 100644
index 0000000..6e8d7f1
--- /dev/null
+++ b/idm/idminfo.h
@@ -0,0 +1,79 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 IDMINFO_H
+#define IDMINFO_H
+
+#include
+#include
+
+/** This struct holds the status information that is read from the IDM device
+ * and passed to the nymea framework within this plugin.
+ */
+struct IdmInfo {
+ /** Set to true, if register values can be read,
+ * false in case of communication problems */
+ bool connected;
+
+ bool power;
+
+ /** RegisterList::OutsideTemperature */
+ double roomTemperature;
+
+ /** RegisterList::ExternalOutsideTemperature */
+ double outsideTemperature;
+
+ /** RegisterList::HeatStorageTemperature */
+ double waterTemperature;
+
+ /** RegisterList::TargetRoomTemperatureZ1R1 (zone 1, room 1) */
+ double targetRoomTemperature;
+
+ /** RegisterList::TargetHotWaterTemperature */
+ double targetWaterTemperature;
+
+ /** RegisterList::HumiditySensor */
+ double humidity;
+
+ /** RegisterList::CurrentPowerConsumptionHeatPump */
+ double powerConsumptionHeatPump;
+
+ /** RegisterList::OperationModeSystem */
+ QString mode;
+
+ /** True if there is an error code set
+ * (RegisterList::CurrentFaultNumber != 0) */
+ bool error;
+};
+
+Q_DECLARE_METATYPE(IdmInfo);
+
+#endif
+
diff --git a/idm/integrationpluginidm.cpp b/idm/integrationpluginidm.cpp
new file mode 100644
index 0000000..3fac78d
--- /dev/null
+++ b/idm/integrationpluginidm.cpp
@@ -0,0 +1,214 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 "integrationpluginidm.h"
+#include "plugininfo.h"
+
+IntegrationPluginIdm::IntegrationPluginIdm()
+{
+
+}
+
+void IntegrationPluginIdm::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcIdm()) << "setupThing called" << thing->name();
+
+ if (thing->thingClassId() == navigator2ThingClassId) {
+ QHostAddress hostAddress = QHostAddress(thing->paramValue(navigator2ThingIpAddressParamTypeId).toString());
+ if (hostAddress.isNull()) {
+ qCWarning(dcIdm()) << "Setup failed, IP address not valid";
+ info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given"));
+ return;
+ }
+
+ if (m_idmConnections.contains(thing)) {
+ qCDebug(dcIdm()) << "Cleaning up after reconfiguration";
+ m_idmConnections.take(thing)->deleteLater();
+ }
+
+ qCDebug(dcIdm()) << "User entered address: " << hostAddress.toString();
+
+ /* Check, if address is already in use for another device */
+ Q_FOREACH (Idm *idm, m_idmConnections) {
+ if (hostAddress.isEqual(idm->getIdmAddress())) {
+ qCWarning(dcIdm()) << "Address already in use";
+ info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("IP address already in use"));
+ return;
+ }
+ }
+
+ qCDebug(dcIdm()) << "Creating Idm object";
+ /* Create new Idm object and store it in hash table */
+ Idm *idm = new Idm(hostAddress, this);
+ if (idm->connectDevice()) {
+ qCWarning(dcIdm()) << "Could not connect to thing";
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ connect(idm, &Idm::statusUpdated, info, [info, thing, idm, this] (const IdmInfo &idmInfo) {
+ if (idmInfo.connected) {
+ m_idmConnections.insert(thing, idm);
+ connect(idm, &Idm::statusUpdated, this, &IntegrationPluginIdm::onStatusUpdated);
+ connect(idm, &Idm::writeRequestExecuted, this, &IntegrationPluginIdm::onWriteRequestExecuted);
+ info->finish(Thing::ThingErrorNoError);
+ }
+ });
+ connect(idm, &Idm::destroyed, this, [thing, this] {m_idmConnections.remove(thing);});
+ connect(info, &ThingSetupInfo::aborted, idm, &Idm::deleteLater);
+
+
+ } else {
+ Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginIdm::postSetupThing(Thing *thing)
+{
+ qCDebug(dcIdm()) << "postSetupThing called" << thing->name();
+
+ if (!m_refreshTimer) {
+ qCDebug(dcIdm()) << "Starting refresh timer";
+ m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
+ connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginIdm::onRefreshTimer);
+ }
+
+ if (thing->thingClassId() == navigator2ThingClassId) {
+ Idm *idm = m_idmConnections.value(thing);
+ if (!idm) {
+ qCWarning(dcIdm()) << "Could not find any iDM connection for" << thing->name();
+ return;
+ }
+
+ thing->setStateValue(navigator2ConnectedStateTypeId, true);
+ update(thing);
+ }
+}
+
+void IntegrationPluginIdm::thingRemoved(Thing *thing)
+{
+ qCDebug(dcIdm()) << "thingRemoved called" << thing->name();
+
+ if (thing->thingClassId() == navigator2ThingClassId) {
+ if (m_idmConnections.contains(thing)) {
+ m_idmConnections.take(thing)->deleteLater();
+ }
+ }
+
+ if (myThings().isEmpty()) {
+ qCDebug(dcIdm()) << "Stopping refresh timer";
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
+ m_refreshTimer = nullptr;
+ }
+}
+
+void IntegrationPluginIdm::executeAction(ThingActionInfo *info)
+{
+ Thing *thing = info->thing();
+ Action action = info->action();
+
+ if (thing->thingClassId() == navigator2ThingClassId) {
+ Idm *idm = m_idmConnections.value(thing);
+ if (!idm) {
+ return info->finish(Thing::ThingErrorHardwareFailure);
+ }
+ if (action.actionTypeId() == navigator2TargetTemperatureActionTypeId) {
+ double targetTemperature = thing->stateValue(navigator2TargetTemperatureStateTypeId).toDouble();
+ QUuid requestId = idm->setTargetTemperature(targetTemperature);
+ m_asyncActions.insert(requestId, info);
+ connect(info, &ThingActionInfo::aborted, [requestId, this] {m_asyncActions.remove(requestId);});
+
+ } else {
+ Q_ASSERT_X(false, "executeAction", QString("Unhandled action: %1").arg(action.actionTypeId().toString()).toUtf8());
+ }
+ } else {
+ Q_ASSERT_X(false, "executeAction", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
+ }
+}
+
+void IntegrationPluginIdm::update(Thing *thing)
+{
+ if (thing->thingClassId() == navigator2ThingClassId) {
+ qCDebug(dcIdm()) << "Updating thing" << thing->name();
+
+ Idm *idm = m_idmConnections.value(thing);
+ if (!idm) {
+ return;
+ }
+ idm->getStatus();
+ }
+}
+
+void IntegrationPluginIdm::onStatusUpdated(const IdmInfo &info)
+{
+ qCDebug(dcIdm()) << "Received status from heat pump";
+
+ Idm *idm = qobject_cast(sender());
+ Thing *thing = m_idmConnections.key(idm);
+
+ if (!thing)
+ return;
+
+ /* Received a structure holding the status info of the
+ * heat pump. Update the thing states with the individual fields. */
+ thing->setStateValue(navigator2ConnectedStateTypeId, info.connected);
+ thing->setStateValue(navigator2PowerStateTypeId, info.power);
+ thing->setStateValue(navigator2TemperatureStateTypeId, info.roomTemperature);
+ thing->setStateValue(navigator2OutsideAirTemperatureStateTypeId, info.outsideTemperature);
+ thing->setStateValue(navigator2WaterTemperatureStateTypeId, info.waterTemperature);
+ thing->setStateValue(navigator2TargetTemperatureStateTypeId, info.targetRoomTemperature);
+ thing->setStateValue(navigator2TargetWaterTemperatureStateTypeId, info.targetWaterTemperature);
+ thing->setStateValue(navigator2CurrentPowerConsumptionHeatPumpStateTypeId, info.powerConsumptionHeatPump);
+ thing->setStateValue(navigator2ModeStateTypeId, info.mode);
+ thing->setStateValue(navigator2ErrorStateTypeId, info.error);
+}
+
+void IntegrationPluginIdm::onWriteRequestExecuted(const QUuid &requestId, bool success)
+{
+ if (m_asyncActions.contains(requestId)) {
+ ThingActionInfo *info = m_asyncActions.value(requestId);
+ if (success) {
+ info->finish(Thing::ThingErrorNoError);
+ } else {
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ }
+ }
+}
+
+void IntegrationPluginIdm::onRefreshTimer()
+{
+ qCDebug(dcIdm()) << "onRefreshTimer called";
+
+ foreach (Thing *thing, myThings().filterByThingClassId(navigator2ThingClassId)) {
+ update(thing);
+ }
+}
+
diff --git a/idm/integrationpluginidm.h b/idm/integrationpluginidm.h
new file mode 100644
index 0000000..808edb8
--- /dev/null
+++ b/idm/integrationpluginidm.h
@@ -0,0 +1,71 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 INTEGRATIONPLUGINIDM_H
+#define INTEGRATIONPLUGINIDM_H
+
+#include "integrations/integrationplugin.h"
+#include "plugintimer.h"
+
+#include "idm.h"
+
+#include
+
+class IntegrationPluginIdm: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginidm.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginIdm();
+
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+
+private:
+ PluginTimer *m_refreshTimer = nullptr;
+ QHash m_idmConnections;
+ QHash m_asyncActions;
+
+ void update(Thing *thing);
+ void onRefreshTimer();
+
+private slots:
+ void onStatusUpdated(const IdmInfo &info);
+ void onWriteRequestExecuted(const QUuid &requestId, bool success);
+
+};
+
+#endif // INTEGRATIONPLUGINIDM_H
+
diff --git a/idm/integrationpluginidm.json b/idm/integrationpluginidm.json
new file mode 100644
index 0000000..e66b23e
--- /dev/null
+++ b/idm/integrationpluginidm.json
@@ -0,0 +1,135 @@
+{
+ "name": "Idm",
+ "displayName": "iDM",
+ "id": "3968d86d-d51a-4ad1-a185-91faa017e38f",
+ "vendors": [
+ {
+ "name": "Idm",
+ "displayName": "iDM",
+ "id": "6f54e4b0-1057-4004-87a9-97fdf4581625",
+ "thingClasses": [
+ {
+ "name": "navigator2",
+ "displayName": "Navigator 2.0",
+ "id": "1c95ac91-4eca-4cbf-b0f4-d60d35d069ed",
+ "createMethods": ["user"],
+ "interfaces": ["thermostat", "connectable"],
+ "paramTypes": [
+ {
+ "id": "05714e5c-d66a-4095-bbff-a0eb96fb035b",
+ "name":"ipAddress",
+ "displayName": "IP address",
+ "type": "QString"
+ }
+ ],
+ "stateTypes":[
+ {
+ "id": "cfd71e64-b666-45ef-8db0-8213acd82c5f",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "33c27167-8e24-4cc5-943c-d17cd03e0f68",
+ "name": "power",
+ "displayName": "Power",
+ "displayNameEvent": "Power changed",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "f0f596bf-7e45-43ea-b3d4-767b82dd422a",
+ "name": "temperature",
+ "displayName": "Room temperature",
+ "displayNameEvent": "Room temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0
+ },
+ {
+ "id": "fcf8e97f-a672-407f-94ae-30df15b310f4",
+ "name": "waterTemperature",
+ "displayName": "Water temperature",
+ "displayNameEvent": "Water temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0
+ },
+ {
+ "id": "9f3462c2-7c42-4eeb-afc4-092e1e41a25d",
+ "name": "outsideAirTemperature",
+ "displayName": "Outside air temperature",
+ "displayNameEvent": "Outside air temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0
+ },
+ {
+ "id": "efae7493-68c3-4cb9-853c-81011bdf09ca",
+ "name": "targetTemperature",
+ "displayName": "Target room temperature",
+ "displayNameAction": "Set target room temperature",
+ "displayNameEvent": "Target room temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "minValue": "18",
+ "maxValue": "28",
+ "defaultValue": 22.00,
+ "writable": true
+ },
+ {
+ "id": "746244d6-dd37-4af8-b2ae-a7d8463e51e2",
+ "name": "targetWaterTemperature",
+ "displayName": "Target water temperature",
+ "displayNameEvent": "Target water temperature changed",
+ "type": "double",
+ "unit": "DegreeCelsius",
+ "defaultValue": 0.00
+ },
+ {
+
+ "id": "b98fb325-100d-4eae-bf8d-97e8f7e1eb00",
+ "name": "currentPowerConsumptionHeatPump",
+ "displayName": "Current power consumption",
+ "displayNameEvent": "Current power consumption heat pump changed",
+ "displayNameAction": "Change current power consumption het pump",
+ "type": "double",
+ "unit": "KiloWatt",
+ "defaultValue": 0.00
+ },
+ {
+ "id": "e539366b-44da-4119-b11b-497bcdb1f522",
+ "name": "mode",
+ "displayName": "Mode",
+ "displayNameEvent": "Mode changed",
+ "type": "QString",
+ "defaultValue": "Off",
+ "possibleValues": [
+ "Off",
+ "Heating",
+ "Cooling",
+ "Hot water",
+ "Defrost"
+ ]
+ },
+ {
+ "id": "49fd83ee-ddf3-4477-9ee4-e01c53283b43",
+ "name": "error",
+ "displayName": "Error",
+ "displayNameEvent": "Error changed",
+ "type": "bool",
+ "defaultValue": false
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+
+
+
+
diff --git a/idm/meta.json b/idm/meta.json
new file mode 100644
index 0000000..71b2812
--- /dev/null
+++ b/idm/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "iDM",
+ "tagline": "Control iDM network enabled heat pumps.",
+ "icon": "idm.png",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "heating"
+ ]
+}
diff --git a/idm/translations/3968d86d-d51a-4ad1-a185-91faa017e38f-en_US.ts b/idm/translations/3968d86d-d51a-4ad1-a185-91faa017e38f-en_US.ts
new file mode 100644
index 0000000..3511917
--- /dev/null
+++ b/idm/translations/3968d86d-d51a-4ad1-a185-91faa017e38f-en_US.ts
@@ -0,0 +1,200 @@
+
+
+
+
+ Idm
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: navigator2, EventType: connected, ID: {cfd71e64-b666-45ef-8db0-8213acd82c5f})
+----------
+The name of the StateType ({cfd71e64-b666-45ef-8db0-8213acd82c5f}) of ThingClass navigator2
+
+
+
+
+ Connected changed
+ The name of the EventType ({cfd71e64-b666-45ef-8db0-8213acd82c5f}) of ThingClass navigator2
+
+
+
+
+
+ Current power consumption
+ The name of the ParamType (ThingClass: navigator2, EventType: currentPowerConsumptionHeatPump, ID: {b98fb325-100d-4eae-bf8d-97e8f7e1eb00})
+----------
+The name of the StateType ({b98fb325-100d-4eae-bf8d-97e8f7e1eb00}) of ThingClass navigator2
+
+
+
+
+ Current power consumption heat pump changed
+ The name of the EventType ({b98fb325-100d-4eae-bf8d-97e8f7e1eb00}) of ThingClass navigator2
+
+
+
+
+
+ Error
+ The name of the ParamType (ThingClass: navigator2, EventType: error, ID: {49fd83ee-ddf3-4477-9ee4-e01c53283b43})
+----------
+The name of the StateType ({49fd83ee-ddf3-4477-9ee4-e01c53283b43}) of ThingClass navigator2
+
+
+
+
+ Error changed
+ The name of the EventType ({49fd83ee-ddf3-4477-9ee4-e01c53283b43}) of ThingClass navigator2
+
+
+
+
+ IP address
+ The name of the ParamType (ThingClass: navigator2, Type: thing, ID: {05714e5c-d66a-4095-bbff-a0eb96fb035b})
+
+
+
+
+
+ Mode
+ The name of the ParamType (ThingClass: navigator2, EventType: mode, ID: {e539366b-44da-4119-b11b-497bcdb1f522})
+----------
+The name of the StateType ({e539366b-44da-4119-b11b-497bcdb1f522}) of ThingClass navigator2
+
+
+
+
+ Mode changed
+ The name of the EventType ({e539366b-44da-4119-b11b-497bcdb1f522}) of ThingClass navigator2
+
+
+
+
+ Navigator 2.0
+ The name of the ThingClass ({1c95ac91-4eca-4cbf-b0f4-d60d35d069ed})
+
+
+
+
+
+ Outside air temperature
+ The name of the ParamType (ThingClass: navigator2, EventType: outsideAirTemperature, ID: {9f3462c2-7c42-4eeb-afc4-092e1e41a25d})
+----------
+The name of the StateType ({9f3462c2-7c42-4eeb-afc4-092e1e41a25d}) of ThingClass navigator2
+
+
+
+
+ Outside air temperature changed
+ The name of the EventType ({9f3462c2-7c42-4eeb-afc4-092e1e41a25d}) of ThingClass navigator2
+
+
+
+
+
+ Power
+ The name of the ParamType (ThingClass: navigator2, EventType: power, ID: {33c27167-8e24-4cc5-943c-d17cd03e0f68})
+----------
+The name of the StateType ({33c27167-8e24-4cc5-943c-d17cd03e0f68}) of ThingClass navigator2
+
+
+
+
+ Power changed
+ The name of the EventType ({33c27167-8e24-4cc5-943c-d17cd03e0f68}) of ThingClass navigator2
+
+
+
+
+
+ Room temperature
+ The name of the ParamType (ThingClass: navigator2, EventType: temperature, ID: {f0f596bf-7e45-43ea-b3d4-767b82dd422a})
+----------
+The name of the StateType ({f0f596bf-7e45-43ea-b3d4-767b82dd422a}) of ThingClass navigator2
+
+
+
+
+ Room temperature changed
+ The name of the EventType ({f0f596bf-7e45-43ea-b3d4-767b82dd422a}) of ThingClass navigator2
+
+
+
+
+ Set target room temperature
+ The name of the ActionType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2
+
+
+
+
+
+
+ Target room temperature
+ The name of the ParamType (ThingClass: navigator2, ActionType: targetTemperature, ID: {efae7493-68c3-4cb9-853c-81011bdf09ca})
+----------
+The name of the ParamType (ThingClass: navigator2, EventType: targetTemperature, ID: {efae7493-68c3-4cb9-853c-81011bdf09ca})
+----------
+The name of the StateType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2
+
+
+
+
+ Target room temperature changed
+ The name of the EventType ({efae7493-68c3-4cb9-853c-81011bdf09ca}) of ThingClass navigator2
+
+
+
+
+
+ Target water temperature
+ The name of the ParamType (ThingClass: navigator2, EventType: targetWaterTemperature, ID: {746244d6-dd37-4af8-b2ae-a7d8463e51e2})
+----------
+The name of the StateType ({746244d6-dd37-4af8-b2ae-a7d8463e51e2}) of ThingClass navigator2
+
+
+
+
+ Target water temperature changed
+ The name of the EventType ({746244d6-dd37-4af8-b2ae-a7d8463e51e2}) of ThingClass navigator2
+
+
+
+
+
+ Water temperature
+ The name of the ParamType (ThingClass: navigator2, EventType: waterTemperature, ID: {fcf8e97f-a672-407f-94ae-30df15b310f4})
+----------
+The name of the StateType ({fcf8e97f-a672-407f-94ae-30df15b310f4}) of ThingClass navigator2
+
+
+
+
+ Water temperature changed
+ The name of the EventType ({fcf8e97f-a672-407f-94ae-30df15b310f4}) of ThingClass navigator2
+
+
+
+
+
+ iDM
+ The name of the vendor ({6f54e4b0-1057-4004-87a9-97fdf4581625})
+----------
+The name of the plugin Idm ({3968d86d-d51a-4ad1-a185-91faa017e38f})
+
+
+
+
+ IntegrationPluginIdm
+
+
+ No IP address given
+
+
+
+
+ IP address already in use
+
+
+
+
diff --git a/modbus/modbushelpers.cpp b/modbus/modbushelpers.cpp
new file mode 100644
index 0000000..043b671
--- /dev/null
+++ b/modbus/modbushelpers.cpp
@@ -0,0 +1,61 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 "modbushelpers.h"
+
+float ModbusHelpers::convertRegisterToFloat(const quint16 *reg) {
+
+ float result = 0.0;
+
+ if (reg != nullptr) {
+ /* low-order byte is sent first, so swap order */
+ quint32 tmp = 0;
+
+ tmp |= ((quint32)(reg[1]) << 16) & 0xFFFF0000;
+ tmp |= reg[0];
+
+ /* copy value over to float variable without any conversion */
+ /* needs to be done with char * to avoid pedantic compiler errors */
+ memcpy((char *)&result, (char *)&tmp, sizeof(result));
+ }
+ return result;
+}
+
+QVector ModbusHelpers::convertFloatToRegister(float value)
+{
+ quint32 tmp = 0;
+ memcpy((char *)&tmp, (char *)&value, sizeof(value));
+
+ QVector reg;
+ reg.append((quint16)(tmp));
+ reg.append((quint16)((tmp & 0xFFFF0000) >> 16));
+ return reg;
+}
+
diff --git a/modbus/modbushelpers.h b/modbus/modbushelpers.h
new file mode 100644
index 0000000..bae33f3
--- /dev/null
+++ b/modbus/modbushelpers.h
@@ -0,0 +1,44 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, 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 MODBUSHELPERS_H
+#define MODBUSHELPERS_H
+
+#include
+#include
+
+class ModbusHelpers {
+public:
+ static float convertRegisterToFloat(const quint16 *reg);
+ static QVector convertFloatToRegister(float value);
+};
+
+#endif
+
diff --git a/modbus/modbusrtumaster.cpp b/modbus/modbusrtumaster.cpp
index 57a45af..d702c3b 100644
--- a/modbus/modbusrtumaster.cpp
+++ b/modbus/modbusrtumaster.cpp
@@ -54,7 +54,6 @@ ModbusRTUMaster::ModbusRTUMaster(QString serialPort, uint baudrate, QSerialPort:
connect(m_reconnectTimer, &QTimer::timeout, this, &ModbusRTUMaster::onReconnectTimer);
}
-
ModbusRTUMaster::~ModbusRTUMaster()
{
if (!m_modbusRtuSerialMaster) {
diff --git a/modbus/modbustcpmaster.cpp b/modbus/modbustcpmaster.cpp
index 48bce17..9d37566 100644
--- a/modbus/modbustcpmaster.cpp
+++ b/modbus/modbustcpmaster.cpp
@@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
-* Copyright 2013 - 2020, nymea GmbH
+* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@@ -29,6 +29,7 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "modbustcpmaster.h"
+
#include
NYMEA_LOGGING_CATEGORY(dcModbusTCP, "ModbusTCP")
@@ -62,7 +63,7 @@ ModbusTCPMaster::~ModbusTCPMaster()
}
bool ModbusTCPMaster::connectDevice() {
- // TCP connction to target device
+ // TCP connection to target device
qCDebug(dcModbusTCP()) << "Setting up TCP connecion";
if (!m_modbusTcpClient)
@@ -86,6 +87,16 @@ void ModbusTCPMaster::setTimeout(int timeout)
m_modbusTcpClient->setTimeout(timeout);
}
+QString ModbusTCPMaster::errorString() const
+{
+ return m_modbusTcpClient->errorString();
+}
+
+QModbusDevice::Error ModbusTCPMaster::error() const
+{
+ return m_modbusTcpClient->error();
+}
+
uint ModbusTCPMaster::port()
{
return m_modbusTcpClient->connectionParameter(QModbusDevice::NetworkPortParameter).toUInt();
@@ -130,20 +141,20 @@ QUuid ModbusTCPMaster::readCoil(uint slaveAddress, uint registerAddress, uint si
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
- writeRequestExecuted(requestId, true);
+ emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
} else {
- writeRequestExecuted(requestId, false);
+ emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
- emit writeRequestError(requestId, reply->errorString());
+ emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(200, reply, &QModbusReply::deleteLater);
@@ -217,13 +228,13 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
- writeRequestExecuted(requestId, true);
+ emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedDiscreteInput(reply->serverAddress(), modbusAddress, unit.values());
} else {
- writeRequestExecuted(requestId, false);
+ emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
@@ -231,7 +242,7 @@ QUuid ModbusTCPMaster::readDiscreteInput(uint slaveAddress, uint registerAddress
qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
QModbusReply *reply = qobject_cast(sender());
- emit writeRequestError(requestId, reply->errorString());
+ emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@@ -261,20 +272,20 @@ QUuid ModbusTCPMaster::readInputRegister(uint slaveAddress, uint registerAddress
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
reply->deleteLater();
if (reply->error() == QModbusDevice::NoError) {
- writeRequestExecuted(requestId, true);
+ emit readRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedInputRegister(reply->serverAddress(), modbusAddress, unit.values());
} else {
- writeRequestExecuted(requestId, false);
+ emit readRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
- emit writeRequestError(requestId, reply->errorString());
+ emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@@ -304,21 +315,22 @@ QUuid ModbusTCPMaster::readHoldingRegister(uint slaveAddress, uint registerAddre
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] {
if (reply->error() == QModbusDevice::NoError) {
- writeRequestExecuted(requestId, true);
+ emit writeRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedHoldingRegister(reply->serverAddress(), modbusAddress, unit.values());
} else {
- writeRequestExecuted(requestId, false);
+ emit writeRequestExecuted(requestId, false);
qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
+ emit readRequestError(requestId, reply->errorString());
}
reply->deleteLater();
});
connect(reply, &QModbusReply::errorOccurred, this, [reply, requestId, this] (QModbusDevice::Error error){
- qCWarning(dcModbusTCP()) << "Modbus replay error:" << error;
- emit writeRequestError(requestId, reply->errorString());
+ qCWarning(dcModbusTCP()) << "Modbus reply error:" << error;
+ emit readRequestError(requestId, reply->errorString());
reply->finished(); // To make sure it will be deleted
});
QTimer::singleShot(2000, reply, &QModbusReply::deleteLater);
@@ -339,7 +351,7 @@ QUuid ModbusTCPMaster::writeCoil(uint slaveAddress, uint registerAddress, bool v
}
QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const QVector &values)
- {
+{
if (!m_modbusTcpClient) {
return "";
}
@@ -354,14 +366,14 @@ QUuid ModbusTCPMaster::writeCoils(uint slaveAddress, uint registerAddress, const
connect(reply, &QModbusReply::finished, this, [reply, requestId, this] () {
if (reply->error() == QModbusDevice::NoError) {
- writeRequestExecuted(requestId, true);
+ emit writeRequestExecuted(requestId, true);
const QModbusDataUnit unit = reply->result();
uint modbusAddress = unit.startAddress();
emit receivedCoil(reply->serverAddress(), modbusAddress, unit.values());
} else {
- writeRequestExecuted(requestId, false);
- qCWarning(dcModbusTCP()) << "Read response error:" << reply->error();
+ emit writeRequestExecuted(requestId, false);
+ qCWarning(dcModbusTCP()) << "Write response error:" << reply->error();
}
reply->deleteLater();
});
diff --git a/modbus/modbustcpmaster.h b/modbus/modbustcpmaster.h
index ae3d9b2..4852a74 100644
--- a/modbus/modbustcpmaster.h
+++ b/modbus/modbustcpmaster.h
@@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
-* Copyright 2013 - 2020, nymea GmbH
+* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
@@ -37,6 +37,8 @@
#include
#include
+Q_DECLARE_LOGGING_CATEGORY(dcModbusTcp)
+
class ModbusTCPMaster : public QObject
{
Q_OBJECT
@@ -49,6 +51,9 @@ public:
void setNumberOfRetries(int number);
void setTimeout(int timeout);
+ QString errorString() const;
+ QModbusDevice::Error error() const;
+
QUuid readCoil(uint slaveAddress, uint registerAddress, uint size = 1);
QUuid readDiscreteInput(uint slaveAddress, uint registerAddress, uint size = 1);
QUuid readInputRegister(uint slaveAddress, uint registerAddress, uint size = 1);
@@ -81,6 +86,9 @@ signals:
void writeRequestExecuted(const QUuid &requestId, bool success);
void writeRequestError(const QUuid &requestId, const QString &error);
+ void readRequestExecuted(const QUuid &requestId, bool success);
+ void readRequestError(const QUuid &requestId, const QString &error);
+
void receivedCoil(uint slaveAddress, uint modbusRegister, const QVector &values);
void receivedDiscreteInput(uint slaveAddress, uint modbusRegister, const QVector &values);
void receivedHoldingRegister(uint slaveAddress, uint modbusRegister, const QVector &values);
diff --git a/modbuscommander/integrationpluginmodbuscommander.cpp b/modbuscommander/integrationpluginmodbuscommander.cpp
index 0057354..cae1683 100644
--- a/modbuscommander/integrationpluginmodbuscommander.cpp
+++ b/modbuscommander/integrationpluginmodbuscommander.cpp
@@ -31,6 +31,10 @@
#include "integrationpluginmodbuscommander.h"
#include "plugininfo.h"
+#include "hardwaremanager.h"
+#include "hardware/modbus/modbusrtumaster.h"
+#include "hardware/modbus/modbusrtuhardwareresource.h"
+
#include
IntegrationPluginModbusCommander::IntegrationPluginModbusCommander()
@@ -39,8 +43,6 @@ IntegrationPluginModbusCommander::IntegrationPluginModbusCommander()
void IntegrationPluginModbusCommander::init()
{
- connect(this, &IntegrationPluginModbusCommander::configValueChanged, this, &IntegrationPluginModbusCommander::onPluginConfigurationChanged);
-
m_slaveAddressParamTypeId.insert(coilThingClassId, coilThingSlaveAddressParamTypeId);
m_slaveAddressParamTypeId.insert(inputRegisterThingClassId, inputRegisterThingSlaveAddressParamTypeId);
m_slaveAddressParamTypeId.insert(discreteInputThingClassId, discreteInputThingSlaveAddressParamTypeId);
@@ -62,46 +64,49 @@ void IntegrationPluginModbusCommander::init()
m_valueStateTypeId.insert(inputRegisterThingClassId, inputRegisterValueStateTypeId);
m_valueStateTypeId.insert(discreteInputThingClassId, discreteInputValueStateTypeId);
m_valueStateTypeId.insert(holdingRegisterThingClassId, holdingRegisterValueStateTypeId);
+
+ // Plugin configuration
+ connect(this, &IntegrationPluginModbusCommander::configValueChanged, this, &IntegrationPluginModbusCommander::onPluginConfigurationChanged);
+
+ // Modbus RTU hardware resource
+ connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, this, [=](const QUuid &modbusUuid){
+ qCDebug(dcModbusCommander()) << "Modbus RTU master has been removed" << modbusUuid.toString();
+
+ // Check if there is any device using this resource
+ foreach (Thing *thing, m_modbusRtuMasters.keys()) {
+ if (m_modbusRtuMasters.value(thing)->modbusUuid() == modbusUuid) {
+ qCWarning(dcModbusCommander()) << "Hardware resource removed for" << thing << ". The thing will not be functional any more until a new resource has been configured for it.";
+ m_modbusRtuMasters.remove(thing);
+ thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
+
+ // Set all child things disconnected
+ foreach (Thing *childThing, myThings()) {
+ if (childThing->parentId() == thing->id()) {
+ thing->setStateValue(m_connectedStateTypeId[childThing->thingClassId()], false);
+ }
+ }
+ }
+ }
+ });
}
void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
{
ThingClassId thingClassId = info->thingClassId();
if (thingClassId == modbusRTUClientThingClassId) {
- Q_FOREACH(QSerialPortInfo port, QSerialPortInfo::availablePorts()) {
- qCDebug(dcModbusCommander()) << "Found serial port:" << port.systemLocation() << "manufacturer" << port.manufacturer() << "description" << port.description() << "serial number" << port.serialNumber();
- if (port.isBusy()) {
- qCDebug(dcModbusCommander()) << "Serial port ist busy, skipping.";
- continue;
+ foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) {
+ qCDebug(dcModbusCommander()) << "Found RTU master resource" << modbusMaster;
+ if (modbusMaster->connected()) {
+ ParamList parameters;
+ ThingDescriptor thingDescriptor(thingClassId, "Modbus RTU master", modbusMaster->serialPort());
+ parameters.append(Param(modbusRTUClientThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid()));
+ thingDescriptor.setParams(parameters);
+ info->addThingDescriptor(thingDescriptor);
+ } else {
+ qCWarning(dcModbusCommander()) << "Found configured resource" << modbusMaster << "but it is not connected. Skipping.";
}
- QString manufacturer = port.manufacturer();
- if (manufacturer.isEmpty()) {
- manufacturer = "unknown";
- }
- QString description = port.description()+" Manufacturer: "+port.manufacturer();
- ThingDescriptor thingDescriptor(thingClassId, "Modbus RTU interface", description);
- ParamList parameters;
- QString serialPort = port.systemLocation();
- QString serialnumber = port.serialNumber();
- if (serialnumber.isEmpty()) {
- serialnumber = port.manufacturer()+QString::number(port.productIdentifier(), 16);
-
- }
- qCDebug(dcModbusCommander()) << " - Serial number" << serialnumber;
- Q_FOREACH (Thing *exisingThing, myThings().filterByParam(modbusRTUClientThingClassId)) {
- thingDescriptor.setThingId(exisingThing->id());
- // Rediscovery is broken because of a missing unique device id
- // This is a workaround and doesnt work if multiple uart converters are attached.
- // ThingDiscoveryInfo may be extended to distinquish between discovery and rediscovery
- break;
- }
- parameters.append(Param(modbusRTUClientThingSerialPortParamTypeId, serialPort));
- parameters.append(Param(modbusRTUClientThingSerialnumberParamTypeId, serialnumber));
- thingDescriptor.setParams(parameters);
- info->addThingDescriptor(thingDescriptor);
}
- //FIXME missing info if it is a rediscovery
info->finish(Thing::ThingErrorNoError);
return;
} else if (thingClassId == discreteInputThingClassId) {
@@ -112,7 +117,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
- ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
+ ThingDescriptor descriptor(thingClassId, "Discrete input", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@@ -128,7 +133,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
- ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
+ ThingDescriptor descriptor(thingClassId, "Coil", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@@ -143,7 +148,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
- ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
+ ThingDescriptor descriptor(thingClassId, "Holding register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@@ -159,7 +164,7 @@ void IntegrationPluginModbusCommander::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptor(descriptor);
}
if (clientThing->thingClassId() == modbusRTUClientThingClassId) {
- ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString());
+ ThingDescriptor descriptor(thingClassId, "Input register", clientThing->name() + " " + clientThing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toString());
descriptor.setParentId(clientThing->id());
info->addThingDescriptor(descriptor);
}
@@ -215,74 +220,51 @@ void IntegrationPluginModbusCommander::setupThing(ThingSetupInfo *info)
}
});
connect(thing, &Thing::settingChanged, thing, [thing, modbusTCPMaster] (const ParamTypeId ¶mTypeId, const QVariant &value) {
- if (paramTypeId == modbusTCPClientSettingsNumberOfRetriesParamTypeId) {
- qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
- modbusTCPMaster->setNumberOfRetries(value.toUInt());
- } else if (paramTypeId == modbusTCPClientSettingsTimeoutParamTypeId) {
- qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
- modbusTCPMaster->setTimeout(value.toUInt());
- }
- });
+ if (paramTypeId == modbusTCPClientSettingsNumberOfRetriesParamTypeId) {
+ qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
+ modbusTCPMaster->setNumberOfRetries(value.toUInt());
+ } else if (paramTypeId == modbusTCPClientSettingsTimeoutParamTypeId) {
+ qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
+ modbusTCPMaster->setTimeout(value.toUInt());
+ }
+ });
modbusTCPMaster->connectDevice();
} else if (thing->thingClassId() == modbusRTUClientThingClassId) {
+ QUuid modbusUuid = thing->paramValue(modbusRTUClientThingModbusMasterUuidParamTypeId).toUuid();
- QString serialPort = thing->paramValue(modbusRTUClientThingSerialPortParamTypeId).toString();
- uint baudrate = thing->paramValue(modbusRTUClientThingBaudRateParamTypeId).toUInt();
- uint stopBits = thing->paramValue(modbusRTUClientThingStopBitsParamTypeId).toUInt();
- uint dataBits = thing->paramValue(modbusRTUClientThingDataBitsParamTypeId).toUInt();
- uint numberOfRetries = thing->setting(modbusRTUClientSettingsNumberOfRetriesParamTypeId).toUInt();
- uint timeout = thing->setting(modbusRTUClientSettingsTimeoutParamTypeId).toUInt();
- QSerialPort::Parity parity = QSerialPort::Parity::NoParity;
- QString parityString = thing->paramValue(modbusRTUClientThingParityParamTypeId).toString();
- if (parityString.contains("No")) {
- parity = QSerialPort::Parity::NoParity;
- } else if (parityString.contains("Even")) {
- parity = QSerialPort::Parity::EvenParity;
- } else if (parityString.contains("Odd")) {
- parity = QSerialPort::Parity::OddParity;
- }
- qCDebug(dcModbusCommander()) << "Setting up RTU client" << thing->name();
- qCDebug(dcModbusCommander()) << " baud:" << baudrate;
- qCDebug(dcModbusCommander()) << " stop bits:" << stopBits;
- qCDebug(dcModbusCommander()) << " data bits:" << dataBits;
- qCDebug(dcModbusCommander()) << " parity:" << parityString;
- qCDebug(dcModbusCommander()) << " number of retries:" << numberOfRetries;
- qCDebug(dcModbusCommander()) << " timeout:" << timeout;
-
-
- if (m_modbusRTUMasters.contains(thing)) {
- // In case of a rediscovery
- m_modbusRTUMasters.take(thing)->deleteLater();
+ if (!hardwareManager()->modbusRtuResource()->available()) {
+ qCWarning(dcModbusCommander()) << "Cannot set up thing" << thing << ". The modbus RTU hardware resource is not available.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The modbus RTU hardware resource is not available"));
+ return;
}
- ModbusRTUMaster *modbusRTUMaster = new ModbusRTUMaster(serialPort, baudrate, parity, dataBits, stopBits, this);
- modbusRTUMaster->setTimeout(timeout);
- modbusRTUMaster->setNumberOfRetries(numberOfRetries);
- 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);
- connect(modbusRTUMaster, &ModbusRTUMaster::connectionStateChanged, info, [info, modbusRTUMaster, this] (bool connected) {
- if (connected) {
- info->finish(Thing::ThingErrorNoError);
- m_modbusRTUMasters.insert(info->thing(), modbusRTUMaster);
+ if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(modbusUuid)) {
+ qCWarning(dcModbusCommander()) << "Cannot set up thing" << thing << ". The modbus RTU hardware resource" << modbusUuid.toString() << "does not exist any more. Reconfiguration required.";
+ info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Configured modbus RTU master could not be found. Please reconfigure the client and assign a new valid modbus RTU master."));
+ return;
+ }
+
+ ModbusRtuMaster *modbusMaster = hardwareManager()->modbusRtuResource()->getModbusRtuMaster(modbusUuid);
+ qCDebug(dcModbusCommander()) << "Setting up" << thing << "using" << modbusMaster;
+ m_modbusRtuMasters.insert(thing, modbusMaster);
+
+ connect(modbusMaster, &ModbusRtuMaster::connectedChanged, thing, [=](bool connected){
+ qCDebug(dcModbusCommander()) << "Modbus RTU client" << modbusMaster << "connected changed" << connected;
+ thing->setStateValue(modbusRTUClientConnectedStateTypeId, connected);
+
+ // Note: only set the connected state for the child things if disconnected.
+ // The child things will be evaluated upon read requests if the slave is connected or not.
+ if (!connected) {
+ foreach (Thing *childThing, myThings()) {
+ if (childThing->parentId() == thing->id()) {
+ thing->setStateValue(m_connectedStateTypeId[childThing->thingClassId()], connected);
+ }
+ }
}
});
- connect(thing, &Thing::settingChanged, thing, [thing, modbusRTUMaster] (const ParamTypeId ¶mTypeId, const QVariant &value) {
- if (paramTypeId == modbusRTUClientSettingsNumberOfRetriesParamTypeId) {
- qCDebug(dcModbusCommander()) << "Set number of retries" << thing->name() << value.toUInt();
- modbusRTUMaster->setNumberOfRetries(value.toUInt());
- } else if (paramTypeId == modbusRTUClientSettingsTimeoutParamTypeId) {
- qCDebug(dcModbusCommander()) << "Set timeout " << thing->name() << value.toUInt();
- modbusRTUMaster->setTimeout(value.toUInt());
- }
- });
- modbusRTUMaster->connectDevice();
+ info->finish(Thing::ThingErrorNoError);
} else if ((thing->thingClassId() == coilThingClassId)
|| (thing->thingClassId() == discreteInputThingClassId)
|| (thing->thingClassId() == holdingRegisterThingClassId)
@@ -362,9 +344,6 @@ void IntegrationPluginModbusCommander::thingRemoved(Thing *thing)
if (thing->thingClassId() == modbusTCPClientThingClassId) {
ModbusTCPMaster *modbus = m_modbusTCPMasters.take(thing);
modbus->deleteLater();
- } else if (thing->thingClassId() == modbusRTUClientThingClassId) {
- ModbusRTUMaster *modbus = m_modbusRTUMasters.take(thing);
- modbus->deleteLater();
}
if (myThings().empty()) {
@@ -392,12 +371,7 @@ void IntegrationPluginModbusCommander::onPluginConfigurationChanged(const ParamT
void IntegrationPluginModbusCommander::onConnectionStateChanged(bool status)
{
auto modbus = sender();
-
- if (m_modbusRTUMasters.values().contains(static_cast(modbus))) {
- Thing *thing = m_modbusRTUMasters.key(static_cast(modbus));
- qCDebug(dcModbusCommander()) << "Connections state changed" << thing->name() << status;
- thing->setStateValue(modbusRTUClientConnectedStateTypeId, status);
- } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
+ if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
Thing *thing = m_modbusTCPMasters.key(static_cast(modbus));
qCDebug(dcModbusCommander()) << "Connections state changed" << thing->name() << status;
thing->setStateValue(modbusTCPClientConnectedStateTypeId, status);
@@ -439,20 +413,7 @@ void IntegrationPluginModbusCommander::onRequestError(QUuid requestId, const QSt
void IntegrationPluginModbusCommander::onReceivedCoil(quint32 slaveAddress, quint32 modbusRegister, const QVector &values)
{
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()), values[0]);
- thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
- return;
- }
- }
- }
- } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
+ 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) {
@@ -471,19 +432,7 @@ void IntegrationPluginModbusCommander::onReceivedDiscreteInput(quint32 slaveAddr
{
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()), values[0]);
- thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
- return;
- }
- }
- }
- } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
+ 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) {
@@ -502,19 +451,7 @@ void IntegrationPluginModbusCommander::onReceivedHoldingRegister(uint slaveAddre
{
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()), values[0]);
- thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
- return;
- }
- }
- }
- } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
+ 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) {
@@ -533,19 +470,7 @@ void IntegrationPluginModbusCommander::onReceivedInputRegister(uint slaveAddress
{
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()), values[0]);
- thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
- return;
- }
- }
- }
- } else if (m_modbusTCPMasters.values().contains(static_cast(modbus))) {
+ 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) {
@@ -592,23 +517,76 @@ void IntegrationPluginModbusCommander::readRegister(Thing *thing)
}
} else if (parent->thingClassId() == modbusRTUClientThingClassId) {
- ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent);
- if (!modbus)
+ ModbusRtuMaster *modbusMaster = m_modbusRtuMasters.value(parent);
+ if (!modbusMaster)
return;
- if (!modbus->connected())
+ if (!modbusMaster->connected())
return; // Send requests only if the modbus interface is connected
if (thing->thingClassId() == coilThingClassId) {
- requestId = modbus->readCoil(slaveAddress, registerAddress);
+ ModbusRtuReply *reply = modbusMaster->readCoil(slaveAddress, registerAddress);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to read coil from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
+ thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
+ return;
+ }
+
+ if (!reply->result().isEmpty()) {
+ thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
+ }
+ thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
+ });
} else if (thing->thingClassId() == discreteInputThingClassId) {
- requestId = modbus->readDiscreteInput(slaveAddress, registerAddress);
+ ModbusRtuReply *reply = modbusMaster->readDiscreteInput(slaveAddress, registerAddress);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to read discrete input from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
+ thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
+ return;
+ }
+
+ if (!reply->result().isEmpty()) {
+ thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
+ }
+ thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
+ });
} else if (thing->thingClassId() == holdingRegisterThingClassId) {
- requestId = modbus->readHoldingRegister(slaveAddress, registerAddress);
+ ModbusRtuReply *reply = modbusMaster->readHoldingRegister(slaveAddress, registerAddress);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to read holding register from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
+ thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
+ return;
+ }
+
+ if (!reply->result().isEmpty()) {
+ thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
+ }
+ thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
+ });
} else if (thing->thingClassId() == inputRegisterThingClassId) {
- requestId = modbus->readInputRegister(slaveAddress, registerAddress);
+ ModbusRtuReply *reply = modbusMaster->readInputRegister(slaveAddress, registerAddress);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to read input register from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress;
+ thing->setStateValue(m_connectedStateTypeId[thing->thingClassId()], false);
+ return;
+ }
+
+ if (!reply->result().isEmpty()) {
+ thing->setStateValue(m_valueStateTypeId.value(thing->thingClassId()), reply->result().at(0));
+ }
+ thing->setStateValue(m_connectedStateTypeId.value(thing->thingClassId()), true);
+ });
}
+
+ // Note: we don't want proceed with the method here, since we are not
+ // working with the requestId any more on RTU
+ return;
}
+
if (!requestId.isNull()) {
m_readRequests.insert(requestId, thing);
QTimer::singleShot(5000, this, [requestId, this] {m_readRequests.remove(requestId);});
@@ -623,8 +601,10 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
Thing *parent = myThings().findById(thing->parentId());
if (!parent) {
qCWarning(dcModbusCommander()) << "Could not find parent device" << thing->name();
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
}
+
uint registerAddress = thing->paramValue(m_registerAddressParamTypeId.value(thing->thingClassId())).toUInt();;
uint slaveAddress = thing->paramValue(m_slaveAddressParamTypeId.value(thing->thingClassId())).toUInt();
@@ -633,8 +613,11 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
if (parent->thingClassId() == modbusTCPClientThingClassId) {
ModbusTCPMaster *modbus = m_modbusTCPMasters.value(parent);
- if (!modbus)
+ if (!modbus) {
+ qCWarning(dcModbusCommander()) << "Could not find modbus TCP master for" << thing;
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
+ }
if (thing->thingClassId() == coilThingClassId) {
requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool());
@@ -643,15 +626,48 @@ void IntegrationPluginModbusCommander::writeRegister(Thing *thing, ThingActionIn
}
} else if (parent->thingClassId() == modbusRTUClientThingClassId) {
- ModbusRTUMaster *modbus = m_modbusRTUMasters.value(parent);
- if (!modbus)
+ ModbusRtuMaster *modbusMaster = m_modbusRtuMasters.value(parent);
+ if (!modbusMaster) {
+ qCWarning(dcModbusCommander()) << "Could not find modbus RTU master for" << thing;
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
return;
+ }
if (thing->thingClassId() == coilThingClassId) {
- requestId = modbus->writeCoil(slaveAddress, registerAddress, action.param(coilValueActionValueParamTypeId).value().toBool());
+ QVector values;
+ values.append(static_cast(action.param(coilValueActionValueParamTypeId).value().toBool()));
+
+ ModbusRtuReply *reply = modbusMaster->writeCoils(slaveAddress, registerAddress, values);
+ connect(info, &ThingActionInfo::aborted, reply, &ModbusRtuReply::deleteLater);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to write coils from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress << values << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
} else if (thing->thingClassId() == holdingRegisterThingClassId) {
- requestId = modbus->writeHoldingRegister(slaveAddress, registerAddress, action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt());
+ QVector values;
+ values.append(static_cast(action.param(holdingRegisterValueActionValueParamTypeId).value().toUInt()));
+
+ ModbusRtuReply *reply = modbusMaster->writeHoldingRegisters(slaveAddress, registerAddress, values);
+ connect(info, &ThingActionInfo::aborted, reply, &ModbusRtuReply::deleteLater);
+ connect(reply, &ModbusRtuReply::finished, modbusMaster, [=](){
+ if (reply->error() != ModbusRtuReply::NoError) {
+ qCWarning(dcModbusCommander()) << "Failed to write holding registers from" << modbusMaster << "slave:" << slaveAddress << "register:" << registerAddress << values << reply->errorString();
+ info->finish(Thing::ThingErrorHardwareFailure);
+ return;
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
}
+
+ // Note: we don't want proceed with the method here, since we are not
+ // working with the requestId any more on RTU
+ return;
}
if (requestId.toString().isNull()){
diff --git a/modbuscommander/integrationpluginmodbuscommander.h b/modbuscommander/integrationpluginmodbuscommander.h
index 4c7b5c0..54f6906 100644
--- a/modbuscommander/integrationpluginmodbuscommander.h
+++ b/modbuscommander/integrationpluginmodbuscommander.h
@@ -31,11 +31,11 @@
#ifndef INTEGRATIONPLUGINMODBUSCOMMANDER_H
#define INTEGRATIONPLUGINMODBUSCOMMANDER_H
-#include "integrations/integrationplugin.h"
#include "plugintimer.h"
+#include "integrations/integrationplugin.h"
+#include "hardware/modbus/modbusrtumaster.h"
#include "../modbus/modbustcpmaster.h"
-#include "../modbus/modbusrtumaster.h"
#include
#include
@@ -60,8 +60,9 @@ public:
private:
PluginTimer *m_refreshTimer = nullptr;
- QHash m_modbusRTUMasters;
+ //QHash m_modbusRTUMasters;
QHash m_modbusTCPMasters;
+ QHash m_modbusRtuMasters;
QHash m_asyncActions;
QHash m_readRequests;
diff --git a/modbuscommander/integrationpluginmodbuscommander.json b/modbuscommander/integrationpluginmodbuscommander.json
index 15bfd37..14f3162 100644
--- a/modbuscommander/integrationpluginmodbuscommander.json
+++ b/modbuscommander/integrationpluginmodbuscommander.json
@@ -72,74 +72,15 @@
"id": "776df314-6186-4eb5-b824-f0d916f6d9c3",
"name": "modbusRTUClient",
"displayName": "Modbus RTU client",
- "createMethods": ["discovery", "user"],
+ "createMethods": ["discovery"],
"interfaces": ["connectable"],
- "settingsTypes": [
- {
- "id": "b0af32f0-b8cc-4642-af5a-576732522b2c",
- "name": "timeout",
- "displayName": "Timeout",
- "type": "uint",
- "minValue": 10,
- "defaultValue": 100
- },
- {
- "id": "c4f16d6c-c1f2-4862-b0bd-6fae7193eaa8",
- "name": "numberOfRetries",
- "displayName": "Number of retries",
- "type": "uint",
- "defaultValue": 3
- }
- ],
"paramTypes": [
{
"id": "ed49f7d8-ab18-4c37-9b80-1004b75dcb91",
- "name": "serialPort",
- "displayName": "Serial port",
- "type": "QString",
- "inputType": "TextLine",
- "defaultValue": "ttyAMA0"
- },
- {
- "id": "9908b01f-a76b-4b21-8242-b507c9252254",
- "name": "serialnumber",
- "displayName": "Serial number",
- "type": "QString",
+ "name": "modbusMasterUuid",
+ "displayName": "Modbus RTU master",
+ "type": "QUuid",
"defaultValue": ""
- },
- {
- "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": "No Parity"
}
],
"stateTypes": [
diff --git a/modbuscommander/modbuscommander.pro b/modbuscommander/modbuscommander.pro
index 9c7a28e..ab4dfc1 100644
--- a/modbuscommander/modbuscommander.pro
+++ b/modbuscommander/modbuscommander.pro
@@ -7,11 +7,9 @@ QT += \
SOURCES += \
integrationpluginmodbuscommander.cpp \
- ../modbus/modbustcpmaster.cpp \
- ../modbus/modbusrtumaster.cpp \
+ ../modbus/modbustcpmaster.cpp
HEADERS += \
integrationpluginmodbuscommander.h \
- ../modbus/modbustcpmaster.h \
- ../modbus/modbusrtumaster.h \
+ ../modbus/modbustcpmaster.h
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index 694d2c8..207d8bc 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -6,6 +6,7 @@ PLUGIN_DIRS = \
mypv \
sunspec \
unipi \
+ idm \
wallbe \
webasto \