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 \