diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index c389e0a..636d847 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -15,6 +15,7 @@ PLUGIN_DIRS = \
mtec \
mypv \
schrack \
+ stiebeleltron \
sunspec \
unipi \
wallbe \
diff --git a/stiebeleltron/integrationpluginstiebeleltron.cpp b/stiebeleltron/integrationpluginstiebeleltron.cpp
new file mode 100644
index 0000000..e772758
--- /dev/null
+++ b/stiebeleltron/integrationpluginstiebeleltron.cpp
@@ -0,0 +1,154 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 "integrationpluginstiebeleltron.h"
+
+#include "network/networkdevicediscovery.h"
+#include "hardwaremanager.h"
+#include "plugininfo.h"
+
+IntegrationPluginStiebelEltron::IntegrationPluginStiebelEltron()
+{
+
+}
+
+void IntegrationPluginStiebelEltron::discoverThings(ThingDiscoveryInfo *info)
+{
+ if (!hardwareManager()->networkDeviceDiscovery()->available()) {
+ qCWarning(dcStiebelEltron()) << "The network discovery is not available on this platform.";
+ info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
+ return;
+ }
+
+ NetworkDeviceDiscoveryReply *discoveryReply = hardwareManager()->networkDeviceDiscovery()->discover();
+ connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
+ foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
+
+ qCDebug(dcStiebelEltron()) << "Found" << networkDeviceInfo;
+
+ QString title;
+ if (networkDeviceInfo.hostName().isEmpty()) {
+ title = networkDeviceInfo.address().toString();
+ } else {
+ title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
+ }
+
+ QString description;
+ if (networkDeviceInfo.macAddressManufacturer().isEmpty()) {
+ description = networkDeviceInfo.macAddress();
+ } else {
+ description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")";
+ }
+
+ ThingDescriptor descriptor(stiebelEltronThingClassId, title, description);
+ ParamList params;
+ params << Param(stiebelEltronThingIpAddressParamTypeId, networkDeviceInfo.address().toString());
+ params << Param(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
+ descriptor.setParams(params);
+
+ // Check if we already have set up this device
+ Things existingThings = myThings().filterByParam(stiebelEltronThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
+ if (existingThings.count() == 1) {
+ qCDebug(dcStiebelEltron()) << "This connection already exists in the system:" << networkDeviceInfo;
+ descriptor.setThingId(existingThings.first()->id());
+ }
+
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+}
+
+void IntegrationPluginStiebelEltron::startMonitoringAutoThings()
+{
+
+}
+
+void IntegrationPluginStiebelEltron::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcStiebelEltron()) << "Setup" << thing << thing->params();
+
+ if (thing->thingClassId() == stiebelEltronThingClassId) {
+
+ QHostAddress address(thing->paramValue(stiebelEltronThingIpAddressParamTypeId).toString());
+ quint16 port = thing->paramValue(stiebelEltronThingPortParamTypeId).toUInt();
+ quint16 slaveId = thing->paramValue(stiebelEltronThingSlaveIdParamTypeId).toUInt();
+
+ StiebelEltronModbusConnection *connection = new StiebelEltronModbusConnection(address, port, slaveId, this);
+
+ connection->connectDevice();
+
+
+ m_connections.insert(thing, connection);
+ info->finish(Thing::ThingErrorNoError);
+ }
+
+
+}
+
+void IntegrationPluginStiebelEltron::postSetupThing(Thing *thing)
+{
+ if (thing->thingClassId() == stiebelEltronThingClassId) {
+ if (!m_pluginTimer) {
+ qCDebug(dcStiebelEltron()) << "Starting plugin timer...";
+ m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(10);
+ connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
+ foreach (StiebelEltronModbusConnection *connection, m_connections) {
+ if (connection->connected()) {
+ connection->update();
+ }
+ }
+ });
+
+ m_pluginTimer->start();
+ }
+ }
+}
+
+void IntegrationPluginStiebelEltron::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == stiebelEltronThingClassId && m_connections.contains(thing)) {
+ m_connections.take(thing)->deleteLater();
+ }
+
+ if (myThings().isEmpty() && m_pluginTimer) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+ m_pluginTimer = nullptr;
+ }
+}
+
+void IntegrationPluginStiebelEltron::executeAction(ThingActionInfo *info)
+{
+ info->finish(Thing::ThingErrorNoError);
+}
+
+
diff --git a/stiebeleltron/integrationpluginstiebeleltron.h b/stiebeleltron/integrationpluginstiebeleltron.h
new file mode 100644
index 0000000..0b81459
--- /dev/null
+++ b/stiebeleltron/integrationpluginstiebeleltron.h
@@ -0,0 +1,65 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* 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 INTEGRATIONPLUGINSTIEBELELTRON_H
+#define INTEGRATIONPLUGINSTIEBELELTRON_H
+
+#include "plugintimer.h"
+#include "integrations/integrationplugin.h"
+#include "stiebeleltronmodbusconnection.h"
+
+class IntegrationPluginStiebelEltron: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginstiebeleltron.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginStiebelEltron();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void startMonitoringAutoThings() override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+ void executeAction(ThingActionInfo *info) override;
+
+private:
+ PluginTimer *m_pluginTimer = nullptr;
+
+ QHash m_connections;
+
+
+};
+
+#endif // INTEGRATIONPLUGINSTIEBELELTRON_H
+
+
diff --git a/stiebeleltron/integrationpluginstiebeleltron.json b/stiebeleltron/integrationpluginstiebeleltron.json
new file mode 100644
index 0000000..d7ae4e2
--- /dev/null
+++ b/stiebeleltron/integrationpluginstiebeleltron.json
@@ -0,0 +1,112 @@
+{
+ "name": "StiebelEltron",
+ "displayName": "Stiebel Eltron",
+ "id": "956c848b-b538-4b8f-8cdb-7bbecfc9d361",
+ "vendors": [
+ {
+ "name": "stiebelEltron",
+ "displayName": "Stiebel Eltron",
+ "id": "c8607f85-a81e-40e0-bc95-1b7199cd2d99",
+ "thingClasses": [
+ {
+ "name": "stiebelEltron",
+ "displayName": "Stiebel Eltron Heatpump",
+ "id": "e02ecf61-7d28-43c2-b87e-e7e98a48fbfd",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["smartgridheatpump", "connectable"],
+ "paramTypes": [
+ {
+ "id": "47d221fa-f6d2-400e-b80f-bb90abccb72c",
+ "name": "ipAddress",
+ "displayName": "IP address",
+ "type": "QString",
+ "inputType": "IPv4Address",
+ "defaultValue": "127.0.0.1"
+ },
+ {
+ "id": "05cd59b8-3068-460f-b0d2-6d49f27458df",
+ "name":"macAddress",
+ "displayName": "MAC address",
+ "type": "QString",
+ "inputType": "MacAddress",
+ "defaultValue": ""
+ },
+ {
+ "id": "6842321f-1f1a-47e2-b12d-59ee322eb8a6",
+ "name":"port",
+ "displayName": "Port",
+ "type": "int",
+ "defaultValue": 502
+ },
+ {
+ "id": "732de6da-bd0a-4215-b320-602117ebc75c",
+ "name":"slaveId",
+ "displayName": "Modbus slave ID",
+ "type": "int",
+ "defaultValue": 1
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "8d952a5e-87bd-492e-a213-277948521652",
+ "name": "connected",
+ "displayName": "Connected",
+ "displayNameEvent": "Connected changed",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "d6475acb-3a15-401b-8bad-8610eb056bf7",
+ "name": "flowTemperature",
+ "displayName": "Flow temperature",
+ "displayNameEvent": "Flow temperature changed",
+ "unit": "DegreeCelsius",
+ "type": "double",
+ "defaultValue": 0,
+ "suggestLogging": true
+ },
+ {
+ "id": "ce25e3fd-6544-40e9-bd39-032306553e32",
+ "name": "returnTemperature",
+ "displayName": "Return temperature",
+ "displayNameEvent": "Return temperature changed",
+ "unit": "DegreeCelsius",
+ "type": "double",
+ "defaultValue": 0,
+ "suggestLogging": true
+ },
+ {
+ "id": "7d474fb5-aa37-4f21-8166-b20f5bf84fb4",
+ "name": "sgReadyMode",
+ "displayName": "Smart grid mode",
+ "displayNameEvent": "Smart grid mode changed",
+ "displayNameAction": "Set smart grid mode",
+ "type": "QString",
+ "possibleValues": [
+ "Off",
+ "Low",
+ "Standard",
+ "High"
+ ],
+ "writable": true,
+ "defaultValue": "Standard",
+ "suggestLogging": true
+ },
+ {
+ "id": "f4abbd8d-14d6-4294-9b63-411a9721f946",
+ "name": "totalEnergy",
+ "displayName": "Total energy",
+ "displayNameEvent": "Total energy changed",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0,
+ "suggestLogging": true
+ }
+ ],
+ "actionTypes": [ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/stiebeleltron/stiebel-eltron-registers.json b/stiebeleltron/stiebel-eltron-registers.json
new file mode 100644
index 0000000..cdcc2ed
--- /dev/null
+++ b/stiebeleltron/stiebel-eltron-registers.json
@@ -0,0 +1,19 @@
+{
+ "protocol": "TCP",
+ "endianness": "BigEndian",
+ "registers": [
+ {
+ "id": "outdoorTemperature",
+ "address": 507,
+ "size": 1,
+ "type": "int16",
+ "registerType": "inputRegister",
+ "readSchedule": "update",
+ "description": "Outdoor temperature",
+ "staticScaleFactor": -1,
+ "defaultValue": "0",
+ "unit": "°C",
+ "access": "RO"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/stiebeleltron/stiebeleltron.pro b/stiebeleltron/stiebeleltron.pro
new file mode 100644
index 0000000..78483b0
--- /dev/null
+++ b/stiebeleltron/stiebeleltron.pro
@@ -0,0 +1,16 @@
+include(../plugins.pri)
+
+QT += network serialbus
+
+HEADERS += \
+ integrationpluginstiebeleltron.h \
+ stiebeleltronmodbusconnection.h \
+ ../modbus/modbustcpmaster.h \
+ ../modbus/modbusdatautils.h
+
+SOURCES += \
+ integrationpluginstiebeleltron.cpp \
+ stiebeleltronmodbusconnection.cpp \
+ ../modbus/modbustcpmaster.cpp \
+ ../modbus/modbusdatautils.cpp
+
diff --git a/stiebeleltron/stiebeleltronmodbusconnection.cpp b/stiebeleltron/stiebeleltronmodbusconnection.cpp
new file mode 100644
index 0000000..15bd8e5
--- /dev/null
+++ b/stiebeleltron/stiebeleltronmodbusconnection.cpp
@@ -0,0 +1,113 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This fileDescriptor 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 "stiebeleltronmodbusconnection.h"
+#include "loggingcategories.h"
+
+NYMEA_LOGGING_CATEGORY(dcStiebelEltronModbusConnection, "StiebelEltronModbusConnection")
+
+StiebelEltronModbusConnection::StiebelEltronModbusConnection(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) :
+ ModbusTCPMaster(hostAddress, port, parent),
+ m_slaveId(slaveId)
+{
+
+}
+
+float StiebelEltronModbusConnection::outdoorTemperature() const
+{
+ return m_outdoorTemperature;
+}
+
+void StiebelEltronModbusConnection::initialize()
+{
+ // No init registers defined. Nothing to be done and we are finished.
+ emit initializationFinished();
+}
+
+void StiebelEltronModbusConnection::update()
+{
+ updateOutdoorTemperature();
+}
+
+void StiebelEltronModbusConnection::updateOutdoorTemperature()
+{
+ // Update registers from Flow
+ qCDebug(dcStiebelEltronModbusConnection()) << "--> Read \"Flow\" register:" << 507 << "size:" << 1;
+ QModbusReply *reply = readOutdoorTemperature();
+ if (reply) {
+ if (!reply->isFinished()) {
+ connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
+ connect(reply, &QModbusReply::finished, this, [this, reply](){
+ if (reply->error() == QModbusDevice::NoError) {
+ const QModbusDataUnit unit = reply->result();
+ const QVector values = unit.values();
+ qCDebug(dcStiebelEltronModbusConnection()) << "<-- Response from \"Flow\" register" << 507 << "size:" << 1 << values;
+ float receivedOutdoorTemperature = ModbusDataUtils::convertToInt16(values) * 1.0 * pow(10, -1);
+ if (m_outdoorTemperature != receivedOutdoorTemperature) {
+ m_outdoorTemperature = receivedOutdoorTemperature;
+ emit outdoorTemperatureChanged(m_outdoorTemperature);
+ }
+ }
+ });
+
+ connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
+ qCWarning(dcStiebelEltronModbusConnection()) << "Modbus reply error occurred while updating \"Flow\" registers from" << hostAddress().toString() << error << reply->errorString();
+ emit reply->finished(); // To make sure it will be deleted
+ });
+ } else {
+ delete reply; // Broadcast reply returns immediatly
+ }
+ } else {
+ qCWarning(dcStiebelEltronModbusConnection()) << "Error occurred while reading \"Flow\" registers from" << hostAddress().toString() << errorString();
+ }
+}
+
+QModbusReply *StiebelEltronModbusConnection::readOutdoorTemperature()
+{
+ QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, 507, 1);
+ return sendReadRequest(request, m_slaveId);
+}
+
+void StiebelEltronModbusConnection::verifyInitFinished()
+{
+ if (m_pendingInitReplies.isEmpty()) {
+ qCDebug(dcStiebelEltronModbusConnection()) << "Initialization finished of StiebelEltronModbusConnection" << hostAddress().toString();
+ emit initializationFinished();
+ }
+}
+
+QDebug operator<<(QDebug debug, StiebelEltronModbusConnection *stiebelEltronModbusConnection)
+{
+ debug.nospace().noquote() << "StiebelEltronModbusConnection(" << stiebelEltronModbusConnection->hostAddress().toString() << ":" << stiebelEltronModbusConnection->port() << ")" << "\n";
+ debug.nospace().noquote() << " - Flow:" << stiebelEltronModbusConnection->outdoorTemperature() << " [°C]" << "\n";
+ return debug.quote().space();
+}
+
diff --git a/stiebeleltron/stiebeleltronmodbusconnection.h b/stiebeleltron/stiebeleltronmodbusconnection.h
new file mode 100644
index 0000000..942beb6
--- /dev/null
+++ b/stiebeleltron/stiebeleltronmodbusconnection.h
@@ -0,0 +1,79 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This fileDescriptor 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 STIEBELELTRONMODBUSCONNECTION_H
+#define STIEBELELTRONMODBUSCONNECTION_H
+
+#include
+
+#include "../modbus/modbusdatautils.h"
+#include "../modbus/modbustcpmaster.h"
+
+class StiebelEltronModbusConnection : public ModbusTCPMaster
+{
+ Q_OBJECT
+public:
+ enum Registers {
+ RegisterOutdoorTemperature = 507
+ };
+ Q_ENUM(Registers)
+
+ explicit StiebelEltronModbusConnection(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);
+ ~StiebelEltronModbusConnection() = default;
+
+ /* Flow [°C] - Address: 507, Size: 1 */
+ float outdoorTemperature() const;
+
+ virtual void initialize();
+ virtual void update();
+
+ void updateOutdoorTemperature();
+
+signals:
+ void initializationFinished();
+
+ void outdoorTemperatureChanged(float outdoorTemperature);
+
+protected:
+ QModbusReply *readOutdoorTemperature();
+
+ float m_outdoorTemperature = 0;
+
+private:
+ quint16 m_slaveId = 1;
+ QVector m_pendingInitReplies;
+
+ void verifyInitFinished();
+
+};
+
+QDebug operator<<(QDebug debug, StiebelEltronModbusConnection *stiebelEltronModbusConnection);
+
+#endif // STIEBELELTRONMODBUSCONNECTION_H