From eb56d176d365c5c44176a1268a5b610a0648e517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 2 Feb 2022 11:11:49 +0100 Subject: [PATCH] Add initial plugin structure for stiebel eltron --- nymea-plugins-modbus.pro | 1 + .../integrationpluginstiebeleltron.cpp | 154 ++++++++++++++++++ .../integrationpluginstiebeleltron.h | 65 ++++++++ .../integrationpluginstiebeleltron.json | 112 +++++++++++++ stiebeleltron/stiebel-eltron-registers.json | 19 +++ stiebeleltron/stiebeleltron.pro | 16 ++ .../stiebeleltronmodbusconnection.cpp | 113 +++++++++++++ stiebeleltron/stiebeleltronmodbusconnection.h | 79 +++++++++ 8 files changed, 559 insertions(+) create mode 100644 stiebeleltron/integrationpluginstiebeleltron.cpp create mode 100644 stiebeleltron/integrationpluginstiebeleltron.h create mode 100644 stiebeleltron/integrationpluginstiebeleltron.json create mode 100644 stiebeleltron/stiebel-eltron-registers.json create mode 100644 stiebeleltron/stiebeleltron.pro create mode 100644 stiebeleltron/stiebeleltronmodbusconnection.cpp create mode 100644 stiebeleltron/stiebeleltronmodbusconnection.h 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