diff --git a/debian/control b/debian/control
index eadbad7..59c08b1 100644
--- a/debian/control
+++ b/debian/control
@@ -94,6 +94,22 @@ Description: nymea.io plugin for my-pv heating rods
This package will install the nymea.io plugin for my-pv
+Package: nymea-plugin-mtec
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+ nymea-plugins-modbus-translations
+Description: nymea.io plugin for M-TEC 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 M-TEC heat pumps
+
+
Package: nymea-plugin-sunspec
Architecture: any
Depends: ${shlibs:Depends},
diff --git a/debian/nymea-plugin-mtec.install.in b/debian/nymea-plugin-mtec.install.in
new file mode 100644
index 0000000..c5ec7ef
--- /dev/null
+++ b/debian/nymea-plugin-mtec.install.in
@@ -0,0 +1 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginmtec.so
diff --git a/modbus/modbustcpmaster.h b/modbus/modbustcpmaster.h
index 7857922..12f051c 100644
--- a/modbus/modbustcpmaster.h
+++ b/modbus/modbustcpmaster.h
@@ -59,6 +59,7 @@ public:
int timeout() const;
void setTimeout(int timeout);
+ int timeout();
QString errorString() const;
QModbusDevice::Error error() const;
@@ -104,6 +105,7 @@ signals:
void writeRequestExecuted(const QUuid &requestId, bool success);
void writeRequestError(const QUuid &requestId, const QString &error);
+ void readRequestError(const QUuid &requestId, const QString &error);
void readRequestExecuted(const QUuid &requestId, bool success);
void readRequestError(const QUuid &requestId, const QString &error);
diff --git a/mtec/integrationpluginmtec.cpp b/mtec/integrationpluginmtec.cpp
new file mode 100644
index 0000000..1c94e2e
--- /dev/null
+++ b/mtec/integrationpluginmtec.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 "integrationpluginmtec.h"
+#include "plugininfo.h"
+
+IntegrationPluginMTec::IntegrationPluginMTec()
+{
+
+}
+
+void IntegrationPluginMTec::discoverThings(ThingDiscoveryInfo *info)
+{
+ qCDebug(dcMTec()) << "Discover M-Tec heat pumps";
+
+ if (info->thingClassId() == mtecThingClassId) {
+ QString description = "Heatpump";
+ ThingDescriptor descriptor(info->thingClassId(), "M-Tec", description);
+ info->addThingDescriptor(descriptor);
+
+ // TODO Find out, if a discovery is possible/needed
+ // Otherwise, just report no error for now
+ info->finish(Thing::ThingErrorNoError);
+ }
+}
+
+void IntegrationPluginMTec::setupThing(ThingSetupInfo *info)
+{
+ qCDebug(dcMTec()) << "Setup" << info->thing();
+
+ Thing *thing = info->thing();
+
+ if (thing->thingClassId() == mtecThingClassId) {
+ QHostAddress hostAddress = QHostAddress(thing->paramValue(mtecThingIpAddressParamTypeId).toString());
+
+ if (hostAddress.isNull()) {
+ info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("No IP address given"));
+ return;
+ }
+
+ qCDebug(dcMTec()) << "User entered address: " << hostAddress.toString();
+
+ /* Check, if address is already in use for another device */
+ /* for (QHash::iterator item=m_mtecConnections.begin(); item != m_mtecConnections.end(); item++) { */
+ /* if (hostAddress.isEqual(item.value()->getHostAddress())) { */
+ /* qCDebug(dcMTec()) << "Address of thing: " << item.value()->getHostAddress().toString(); */
+
+ /* qCDebug(dcMTec()) << "Address in use already"; */
+ /* } else { */
+ /* qCDebug(dcMTec()) << "Different address of other thing: " << item.value()->getHostAddress().toString(); */
+
+ /* } */
+ /* } */
+
+ foreach (MTec *mtecConnection, m_mtecConnections.values()) {
+ if (mtecConnection->getHostAddress().isEqual(hostAddress)) {
+ qCWarning(dcMTec()) << "Address" << hostAddress.toString() << "already in use by" << m_mtecConnections.key(mtecConnection);
+ info->finish(Thing::ThingErrorThingInUse, QT_TR_NOOP("IP address already in use by another thing."));
+ return;
+ }
+ }
+
+ qCDebug(dcMTec()) << "Creating M-Tec object";
+
+ /* Create new MTec object and store it in hash table */
+ MTec *mtec = new MTec(hostAddress, this);
+ m_mtecConnections.insert(thing, mtec);
+
+ info->thing()->setStateValue(mtecConnectedStateTypeId, true);
+ info->finish(Thing::ThingErrorNoError);
+ }
+}
+
+void IntegrationPluginMTec::postSetupThing(Thing *thing)
+{
+ qCDebug(dcMTec()) << "PostSetup called for" << thing;
+
+ if (thing->thingClassId() == mtecThingClassId) {
+ MTec *mtec = m_mtecConnections.value(thing);
+
+ if (mtec) {
+ connect(mtec, &MTec::statusUpdated, this, &IntegrationPluginMTec::onStatusUpdated);
+ connect(mtec, &MTec::connectedChanged, this, &IntegrationPluginMTec::onConnectedChanged);
+
+ qCDebug(dcMTec()) << "Thing set up, calling update";
+ update(thing);
+
+ thing->setStateValue(mtecConnectedStateTypeId, true);
+ }
+ }
+}
+
+void IntegrationPluginMTec::thingRemoved(Thing *thing)
+{
+ if (m_mtecConnections.contains(thing)) {
+ m_mtecConnections.take(thing)->deleteLater();
+ }
+}
+
+void IntegrationPluginMTec::executeAction(ThingActionInfo *info)
+{
+ Thing *thing = info->thing();
+ Action action = info->action();
+
+ if (thing->thingClassId() == mtecThingClassId) {
+ /* if (action.actionTypeId() == mtecPowerActionTypeId) { */
+
+ /* } 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 IntegrationPluginMTec::update(Thing *thing)
+{
+ if (thing->thingClassId() == mtecThingClassId) {
+ qCDebug(dcMTec()) << "Updating thing" << thing;
+
+ MTec *mtec = m_mtecConnections.value(thing);
+
+ if (mtec) {
+ mtec->onRequestStatus();
+ }
+ }
+}
+
+void IntegrationPluginMTec::onConnectedChanged(bool connected)
+{
+ MTec *mtec = qobject_cast(sender());
+ Thing *thing = m_mtecConnections.key(mtec);
+
+ qCDebug(dcMTec()) << "Received connection change event from heat pump" << thing;
+
+ if (!thing)
+ return;
+
+ thing->setStateValue(mtecConnectedStateTypeId, connected);
+}
+
+void IntegrationPluginMTec::onStatusUpdated(const MTecInfo &info)
+{
+ MTec *mtec = qobject_cast(sender());
+ Thing *thing = m_mtecConnections.key(mtec);
+
+ qCDebug(dcMTec()) << "Received status from heat pump" << thing;
+
+ if (!thing)
+ return;
+
+ /* Received a structure holding the status info of the
+ * heat pump. Update the thing states with the individual fields. */
+ thing->setStateValue(mtecStatusStateTypeId, info.status);
+ thing->setStateValue(mtecActualPowerConsumptionStateTypeId, info.actualPowerConsumption);
+ thing->setStateValue(mtecActualExcessEnergySmartHomeStateTypeId, info.actualExcessEnergySmartHome);
+ thing->setStateValue(mtecActualExcessEnergyElectricityMeterStateTypeId, info.actualExcessEnergyElectricityMeter);
+ thing->setStateValue(mtecExternalSetValueScalingStateTypeId, info.externalSetValueScaling);
+ thing->setStateValue(mtecRequestExternalHeatSourceStateTypeId, info.requestExternalHeatSource);
+}
+
+void IntegrationPluginMTec::onRefreshTimer()
+{
+ qCDebug(dcMTec()) << "onRefreshTimer called";
+
+ foreach (Thing *thing, myThings().filterByThingClassId(mtecThingClassId)) {
+ update(thing);
+ }
+}
+
+
diff --git a/mtec/integrationpluginmtec.h b/mtec/integrationpluginmtec.h
new file mode 100644
index 0000000..f7fe9ea
--- /dev/null
+++ b/mtec/integrationpluginmtec.h
@@ -0,0 +1,73 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 INTEGRATIONPLUGINMTEC_H
+#define INTEGRATIONPLUGINMTEC_H
+
+#include "integrations/integrationplugin.h"
+#include "plugintimer.h"
+
+#include "mtec.h"
+
+#include
+
+class IntegrationPluginMTec: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginmtec.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ /** Constructor */
+ explicit IntegrationPluginMTec();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+
+
+private:
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+ void update(Thing *thing);
+
+ QHash m_mtecConnections;
+ QHash m_asyncActions;
+
+private slots:
+ void onConnectedChanged(bool connected);
+ void onRefreshTimer();
+ void onStatusUpdated(const MTecInfo &info);
+
+};
+
+#endif // INTEGRATIONPLUGINMTEC_H
+
+
diff --git a/mtec/integrationpluginmtec.json b/mtec/integrationpluginmtec.json
new file mode 100644
index 0000000..9f91d44
--- /dev/null
+++ b/mtec/integrationpluginmtec.json
@@ -0,0 +1,106 @@
+{
+ "name": "MTec",
+ "displayName": "M-Tec",
+ "id": "07cd316b-1e2c-40cf-8358-88d7407506ae",
+ "vendors": [
+ {
+ "name": "MTec",
+ "displayName": "M-Tec",
+ "id": "04d3fa7c-e469-4a79-a119-155426e5a846",
+ "thingClasses": [
+ {
+ "name": "mtec",
+ "displayName": "MTec",
+ "id": "451e38d8-50d5-4ae9-8d9f-21af9347128d",
+ "createMethods": ["user"],
+ "interfaces": ["connectable"],
+ "paramTypes": [
+ {
+ "id": "f1c43b1e-cffe-4d30-bda0-c23ed648dd71",
+ "name": "ipAddress",
+ "displayName": "IP address",
+ "type": "QString"
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "8d64954a-855d-44ea-8bc9-88a71ab47b6b",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "9bf5f8d6-116a-4399-a728-51470a3a5620",
+ "name": "status",
+ "displayName": "Status",
+ "displayNameEvent": "Status changed",
+ "type": "QString",
+ "defaultValue": "Off",
+ "possibleValues": [
+ "Off",
+ "Connecting",
+ "Connected",
+ "Error"
+ ]
+ },
+ {
+ "id": "c67c79cf-7369-409f-b170-16c4ece9d25a",
+ "name": "actualPowerConsumption",
+ "displayName": "Actual power consumption",
+ "displayNameEvent": "Actual power consumption changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0
+ },
+ {
+ "id": "663718fa-807e-4d85-bd78-61a65f8c0b5e",
+ "name": "actualExcessEnergySmartHome",
+ "displayName": "Actual excess energy from Smart home System",
+ "displayNameEvent": "Actual excess energy from Smart home System changed",
+ "displayNameAction": "Change actual excess energy from Smart home System",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0
+ },
+ {
+ "id": "fd94d39c-0db6-497f-a0a5-6c5452cbcaaf",
+ "name": "actualExcessEnergyElectricityMeter",
+ "displayName": "Actual excess energy from Electricity Meter",
+ "displayNameEvent": "Actual excess energy from Electricity Meter changed",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0
+ },
+ {
+ "id": "087c0296-705b-483a-b1e9-7ce08202c035",
+ "name": "externalSetValueScaling",
+ "displayName": "Control of the heat source by an external control [100%]",
+ "displayNameEvent": "Control of the heat source by an external control [100%] changed",
+ "type": "double",
+ "unit": "Percentage",
+ "defaultValue": 100
+ },
+ {
+ "id": "90b17788-ce63-47e3-b97d-1b025a41ce35",
+ "name": "requestExternalHeatSource",
+ "displayName": "Request external heat source",
+ "displayNameEvent": "Request external heat source changed",
+ "type": "QString",
+ "defaultValue": "No request, external heat source must be turned off",
+ "possibleValues": [
+ "No request, external heat source must be turned off",
+ "External heat source is released and can be switched on",
+ "External heat source is required and must be turned on"
+ ]
+ }
+ ],
+ "actionTypes": [
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/mtec/mtec.cpp b/mtec/mtec.cpp
new file mode 100644
index 0000000..7a027fd
--- /dev/null
+++ b/mtec/mtec.cpp
@@ -0,0 +1,143 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "mtec.h"
+#include "extern-plugininfo.h"
+#include "mtechelpers.h"
+
+#include
+
+MTec::MTec(const QHostAddress &address, QObject *parent) :
+ QObject(parent),
+ m_hostAddress(address)
+{
+ m_modbusMaster = new ModbusTCPMaster(address, 502, this);
+
+ qCDebug(dcMTec()) << "created ModbusTCPMaster";
+ m_connected = m_modbusMaster->connectDevice();
+
+ connect(m_modbusMaster, &ModbusTCPMaster::receivedHoldingRegister, this, &MTec::onReceivedHoldingRegister);
+ connect(m_modbusMaster, &ModbusTCPMaster::readRequestError, this, &MTec::onModbusError);
+ connect(m_modbusMaster, &ModbusTCPMaster::writeRequestError, this, &MTec::onModbusError);
+}
+
+MTec::~MTec()
+{
+}
+
+void MTec::onModbusError()
+{
+ qCWarning(dcMTec()) << "MTec: Received modbus error";
+
+ emit connectedChanged(false);
+}
+
+void MTec::onRequestStatus()
+{
+ if ((m_connected) && (!m_firstTimeout.isValid())) {
+ /* No timestamp for timeout of first telegram defined,
+ * -> first request has not yet been sent
+ * -> do it now */
+
+ /* Note: m_firstRequest is set to false in
+ * the onReceivedHoldingRegister function */
+ if (m_firstRequest == true) {
+ m_firstTimeout = QDateTime::currentDateTime();
+ m_firstTimeout = m_firstTimeout.addSecs(MTec::FirstConnectionTimeout);
+
+ /* Save original modbus timeout, will be set again
+ * after first response is received */
+ m_modbusTimeout = m_modbusMaster->timeout();
+
+ /* The M-Tec heatpump requires a longer timeout to
+ * start-up the modbus communication */
+ m_modbusMaster->setTimeout(MTec::FirstConnectionTimeout);
+ } else {
+ /* Set back original modbus timeout */
+ m_modbusMaster->setTimeout(m_modbusTimeout);
+ }
+ } else {
+ QDateTime now = QDateTime::currentDateTime();
+
+ if (m_firstTimeout.msecsTo(now) < MTec::FirstConnectionTimeout) {
+ /* Timeout of first request not yet reached
+ * -> return without sending another request */
+ return;
+ }
+ }
+
+ m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, MTec::ActualPowerConsumption, 1);
+}
+
+void MTec::onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector &value)
+{
+ Q_UNUSED(slaveAddress);
+
+ /* Some response was received, so the modbus communication is
+ * established. */
+ m_firstRequest = false;
+
+ switch (modbusRegister) {
+ case ActualPowerConsumption:
+ if (value.length() == 1) {
+ m_info.actualPowerConsumption = MTecHelpers::convertRegisterToFloat(value[0]);
+ }
+ m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, MTec::ActualExcessEnergySmartHome, 1);
+ break;
+ case ActualExcessEnergySmartHome:
+ if (value.length() == 1) {
+ m_info.actualExcessEnergySmartHome = MTecHelpers::convertRegisterToFloat(value[0]);
+ }
+ m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, MTec::ActualExcessEnergyElectricityMeter, 1);
+ break;
+ case ActualExcessEnergyElectricityMeter:
+ if (value.length() == 1) {
+ m_info.actualExcessEnergyElectricityMeter = MTecHelpers::convertRegisterToFloat(value[0]);
+ }
+ m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, MTec::ExternalSetValueScaling, 1);
+ break;
+ case ExternalSetValueScaling:
+ if (value.length() == 1) {
+ m_info.externalSetValueScaling = MTecHelpers::convertRegisterToFloat(value[0]);
+ }
+ m_modbusMaster->readHoldingRegister(MTec::ModbusUnitID, MTec::RequestExternalHeatSource, 1);
+ break;
+ case RequestExternalHeatSource:
+ if (value.length() == 1) {
+ m_info.requestExternalHeatSource = MTecHelpers::externalHeatSourceRequestToString(value[0]);
+
+ /* Everything read without errors
+ * -> update status in plugin */
+ emit statusUpdated(m_info);
+ }
+ break;
+ }
+}
+
diff --git a/mtec/mtec.h b/mtec/mtec.h
new file mode 100644
index 0000000..d725b8a
--- /dev/null
+++ b/mtec/mtec.h
@@ -0,0 +1,130 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 MTEC_H
+#define MTEC_H
+
+#include
+#include
+
+#include "../modbus/modbustcpmaster.h"
+
+#include "mtecinfo.h"
+
+class MTec : public QObject
+{
+ Q_OBJECT
+public:
+ /** Constructor */
+ explicit MTec(const QHostAddress &address, QObject *parent = nullptr);
+
+ /** Destructor */
+ ~MTec();
+
+ inline QHostAddress getHostAddress() const { return m_hostAddress; };
+
+private:
+ /**
+ * The first response of the M-Tec heatpump may be delayed
+ * by 10 s. The plugin will wait for the defined time in milliseconds
+ * for a response before sending an additional request or setting
+ * an error code.
+ */
+ static const int FirstConnectionTimeout = 15000;
+
+ /** Modbus Unit ID (undocumented, guessing 1 for now) */
+ static const quint16 ModbusUnitID = 1;
+
+ /** The following modbus addresses can be read: */
+ enum RegisterList {
+ /**
+ * APPL.CtrlAppl.sParam.heatpump[0].ElectricEnergyMeter.values.power
+ * Actual power consumtion [W]
+ */
+ ActualPowerConsumption = 707,
+
+ /**
+ * APPL.CtrlAppl.sIOModule.Virt[0].param.sensor[0]
+ * Acutal excess energy given from Smart home System [W]
+ */
+ ActualExcessEnergySmartHome = 1000,
+
+ /**
+ * APPL.CtrlAppl.sParam.photovoltaics.ElectricEnergyMeter.values.power
+ * Acutal excess energy given from Electricity Meter [W]
+ */
+ ActualExcessEnergyElectricityMeter = 1002,
+
+ /**
+ * APPL.CtrlAppl.sParam.extHeatSource[0].param.externalSetvalueScaled
+ * Control of the heat source by an external control [100%]
+ */
+ ExternalSetValueScaling = 1600,
+
+ /**
+ * APPL.CtrlAppl.sParam.extHeatSource[0].values.requestExtHeatsource
+ * 0…no request, external heat source must be turned off.
+ * 1…external heat source is released and can be switched on.
+ * 2…external heat source is required and must be turned on.
+ */
+ RequestExternalHeatSource = 1601
+ };
+
+ /* Note: This class only requires one IP address and one
+ * TCP Modbus connection. Multiple devices are managed
+ * within the IntegrationPluginMTec class. */
+ QHostAddress m_hostAddress;
+
+ /** Pointer to ModbusTCPMaster object, responseible for low-level communicaiton */
+ ModbusTCPMaster *m_modbusMaster = nullptr;
+
+ /** This structure is filled by the receivedStatus... functions */
+ MTecInfo m_info ;
+
+ bool m_firstRequest = true;
+ bool m_connected = false;
+
+ QDateTime m_firstTimeout;
+
+ int m_modbusTimeout;
+
+signals:
+ void connectedChanged(bool connected);
+ void statusUpdated(const MTecInfo &info);
+
+public slots:
+ void onModbusError();
+ void onRequestStatus();
+ void onReceivedHoldingRegister(int slaveAddress, int modbusRegister, const QVector &value);
+
+};
+
+#endif // MTEC_H
+
diff --git a/mtec/mtec.pro b/mtec/mtec.pro
new file mode 100644
index 0000000..eaaf6a0
--- /dev/null
+++ b/mtec/mtec.pro
@@ -0,0 +1,19 @@
+include(../plugins.pri)
+
+QT += \
+ network \
+ serialbus \
+
+SOURCES += \
+ mtec.cpp \
+ mtechelpers.cpp \
+ integrationpluginmtec.cpp \
+ ../modbus/modbustcpmaster.cpp
+
+HEADERS += \
+ mtec.h \
+ mtechelpers.h \
+ mtecinfo.h \
+ integrationpluginmtec.h \
+ ../modbus/modbustcpmaster.h \
+
diff --git a/mtec/mtechelpers.cpp b/mtec/mtechelpers.cpp
new file mode 100644
index 0000000..27cb7a0
--- /dev/null
+++ b/mtec/mtechelpers.cpp
@@ -0,0 +1,65 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "mtechelpers.h"
+
+float MTecHelpers::convertRegisterToFloat(const quint16 reg)
+{
+ float value = 0.0;
+
+ value = reg/100.0;
+
+ return value;
+}
+
+void MTecHelpers::convertFloatToRegister(quint16 ®, float value)
+{
+ reg = qRound(value * 100.0);
+}
+
+QString MTecHelpers::externalHeatSourceRequestToString(quint16 value)
+{
+ QString str{};
+
+ switch (value) {
+ case 0:
+ str = "No request, external heat source must be turned off";
+ break;
+ case 1:
+ str = "External heat source is released and can be switched on";
+ break;
+ case 2:
+ str = "External heat source is required and must be turned on";
+ break;
+ }
+
+ return str;
+}
+
diff --git a/mtec/mtechelpers.h b/mtec/mtechelpers.h
new file mode 100644
index 0000000..452ba51
--- /dev/null
+++ b/mtec/mtechelpers.h
@@ -0,0 +1,46 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 MTECHELPERS_H
+#define MTECHELPERS_H
+
+#include
+#include
+
+class MTecHelpers {
+public:
+ static float convertRegisterToFloat(quint16 reg);
+ static void convertFloatToRegister(quint16 ®, float value);
+
+ static QString externalHeatSourceRequestToString(quint16 value);
+};
+
+#endif
+
diff --git a/mtec/mtecinfo.h b/mtec/mtecinfo.h
new file mode 100644
index 0000000..fe87854
--- /dev/null
+++ b/mtec/mtecinfo.h
@@ -0,0 +1,59 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 MTECINFO_H
+#define MTECINFO_H
+
+#include
+#include
+
+/** This struct holds the status information that is read from the MTec device
+ * and passed to the nymea framework within this plugin.
+ */
+struct MTecInfo {
+ /** Contains more detailed info on the status
+ * (Off, Connecting, Connected, Error) */
+ QString status;
+
+ double actualPowerConsumption;
+
+ double actualExcessEnergySmartHome;
+
+ double actualExcessEnergyElectricityMeter;
+
+ double externalSetValueScaling;
+
+ QString requestExternalHeatSource;
+};
+
+Q_DECLARE_METATYPE(MTecInfo);
+
+#endif
+
diff --git a/mtec/translations/07cd316b-1e2c-40cf-8358-88d7407506ae-en_US.ts b/mtec/translations/07cd316b-1e2c-40cf-8358-88d7407506ae-en_US.ts
new file mode 100644
index 0000000..d4adf0f
--- /dev/null
+++ b/mtec/translations/07cd316b-1e2c-40cf-8358-88d7407506ae-en_US.ts
@@ -0,0 +1,146 @@
+
+
+
+
+ IntegrationPluginMTec
+
+
+ No IP address given
+
+
+
+
+ IP address already in use by another thing.
+
+
+
+
+ MTec
+
+
+
+ Actual excess energy from Electricity Meter
+ The name of the ParamType (ThingClass: mtec, EventType: actualExcessEnergyElectricityMeter, ID: {fd94d39c-0db6-497f-a0a5-6c5452cbcaaf})
+----------
+The name of the StateType ({fd94d39c-0db6-497f-a0a5-6c5452cbcaaf}) of ThingClass mtec
+
+
+
+
+ Actual excess energy from Electricity Meter changed
+ The name of the EventType ({fd94d39c-0db6-497f-a0a5-6c5452cbcaaf}) of ThingClass mtec
+
+
+
+
+
+ Actual excess energy from Smart home System
+ The name of the ParamType (ThingClass: mtec, EventType: actualExcessEnergySmartHome, ID: {663718fa-807e-4d85-bd78-61a65f8c0b5e})
+----------
+The name of the StateType ({663718fa-807e-4d85-bd78-61a65f8c0b5e}) of ThingClass mtec
+
+
+
+
+ Actual excess energy from Smart home System changed
+ The name of the EventType ({663718fa-807e-4d85-bd78-61a65f8c0b5e}) of ThingClass mtec
+
+
+
+
+
+ Actual power consumption
+ The name of the ParamType (ThingClass: mtec, EventType: actualPowerConsumption, ID: {c67c79cf-7369-409f-b170-16c4ece9d25a})
+----------
+The name of the StateType ({c67c79cf-7369-409f-b170-16c4ece9d25a}) of ThingClass mtec
+
+
+
+
+ Actual power consumption changed
+ The name of the EventType ({c67c79cf-7369-409f-b170-16c4ece9d25a}) of ThingClass mtec
+
+
+
+
+
+ Connected
+ The name of the ParamType (ThingClass: mtec, EventType: connected, ID: {8d64954a-855d-44ea-8bc9-88a71ab47b6b})
+----------
+The name of the StateType ({8d64954a-855d-44ea-8bc9-88a71ab47b6b}) of ThingClass mtec
+
+
+
+
+ Connected changed
+ The name of the EventType ({8d64954a-855d-44ea-8bc9-88a71ab47b6b}) of ThingClass mtec
+
+
+
+
+
+ Control of the heat source by an external control [100%]
+ The name of the ParamType (ThingClass: mtec, EventType: externalSetValueScaling, ID: {087c0296-705b-483a-b1e9-7ce08202c035})
+----------
+The name of the StateType ({087c0296-705b-483a-b1e9-7ce08202c035}) of ThingClass mtec
+
+
+
+
+ Control of the heat source by an external control [100%] changed
+ The name of the EventType ({087c0296-705b-483a-b1e9-7ce08202c035}) of ThingClass mtec
+
+
+
+
+ IP address
+ The name of the ParamType (ThingClass: mtec, Type: thing, ID: {f1c43b1e-cffe-4d30-bda0-c23ed648dd71})
+
+
+
+
+
+ M-Tec
+ The name of the vendor ({04d3fa7c-e469-4a79-a119-155426e5a846})
+----------
+The name of the plugin MTec ({07cd316b-1e2c-40cf-8358-88d7407506ae})
+
+
+
+
+ MTec
+ The name of the ThingClass ({451e38d8-50d5-4ae9-8d9f-21af9347128d})
+
+
+
+
+
+ Request external heat source
+ The name of the ParamType (ThingClass: mtec, EventType: requestExternalHeatSource, ID: {90b17788-ce63-47e3-b97d-1b025a41ce35})
+----------
+The name of the StateType ({90b17788-ce63-47e3-b97d-1b025a41ce35}) of ThingClass mtec
+
+
+
+
+ Request external heat source changed
+ The name of the EventType ({90b17788-ce63-47e3-b97d-1b025a41ce35}) of ThingClass mtec
+
+
+
+
+
+ Status
+ The name of the ParamType (ThingClass: mtec, EventType: status, ID: {9bf5f8d6-116a-4399-a728-51470a3a5620})
+----------
+The name of the StateType ({9bf5f8d6-116a-4399-a728-51470a3a5620}) of ThingClass mtec
+
+
+
+
+ Status changed
+ The name of the EventType ({9bf5f8d6-116a-4399-a728-51470a3a5620}) of ThingClass mtec
+
+
+
+
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index 2515df5..ebee60d 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -4,6 +4,7 @@ PLUGIN_DIRS = \
drexelundweiss \
energymeters \
modbuscommander \
+ mtec \
mypv \
sunspec \
unipi \