From 9027579cda5145f3161338a82bf1ce89620715e0 Mon Sep 17 00:00:00 2001 From: Boernsman Date: Tue, 20 Apr 2021 20:39:58 +0200 Subject: [PATCH] added energy meters intergration plugin --- debian/control | 17 + debian/nymea-plugin-energymeters.install.in | 1 + energymeters/README.md | 9 + energymeters/bg-etechmodbusregister.h | 59 ++++ energymeters/energymeter.cpp | 148 +++++++++ energymeters/energymeter.h | 81 +++++ energymeters/energymeters.pro | 16 + energymeters/inepromodbusregister.h | 45 +++ .../integrationpluginenergymeters.cpp | 291 ++++++++++++++++++ energymeters/integrationpluginenergymeters.h | 89 ++++++ .../integrationpluginenergymeters.json | 244 +++++++++++++++ energymeters/meta.json | 0 energymeters/registerdescriptor.h | 83 +++++ nymea-plugins-modbus.pro | 1 + 14 files changed, 1084 insertions(+) create mode 100644 debian/nymea-plugin-energymeters.install.in create mode 100644 energymeters/README.md create mode 100644 energymeters/bg-etechmodbusregister.h create mode 100644 energymeters/energymeter.cpp create mode 100644 energymeters/energymeter.h create mode 100644 energymeters/energymeters.pro create mode 100644 energymeters/inepromodbusregister.h create mode 100644 energymeters/integrationpluginenergymeters.cpp create mode 100644 energymeters/integrationpluginenergymeters.h create mode 100644 energymeters/integrationpluginenergymeters.json create mode 100644 energymeters/meta.json create mode 100644 energymeters/registerdescriptor.h diff --git a/debian/control b/debian/control index 0e628d8..eadbad7 100644 --- a/debian/control +++ b/debian/control @@ -45,6 +45,23 @@ Description: nymea.io plugin for iDM heat pumps This package will install the nymea.io plugin for iDM heat pumps +Package: nymea-plugin-energymeters +Architecture: any +Multi-Arch: same +Section: libs +Depends: ${shlibs:Depends}, + ${misc:Depends}, + nymea-plugins-modbus-translations +Description: nymea.io plugin for Modbus based energy meters + 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 energy meters + + Package: nymea-plugin-modbuscommander Architecture: any Section: libs diff --git a/debian/nymea-plugin-energymeters.install.in b/debian/nymea-plugin-energymeters.install.in new file mode 100644 index 0000000..2f08f1f --- /dev/null +++ b/debian/nymea-plugin-energymeters.install.in @@ -0,0 +1 @@ +usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginenergymeters.so diff --git a/energymeters/README.md b/energymeters/README.md new file mode 100644 index 0000000..b8b1fb8 --- /dev/null +++ b/energymeters/README.md @@ -0,0 +1,9 @@ +# B+G e-tech + +## Supported Things + + +## Requirements + + +## More diff --git a/energymeters/bg-etechmodbusregister.h b/energymeters/bg-etechmodbusregister.h new file mode 100644 index 0000000..368c5eb --- /dev/null +++ b/energymeters/bg-etechmodbusregister.h @@ -0,0 +1,59 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 BGETECHMODBUSREGISTER_H +#define BGETECHMODBUSREGISTER_H + +#include "registerdescriptor.h" + +class BgEtechModbusRegisers +{ + +private: + BgEtechModbusRegisers() {} + static void init () { + m_registerMap.insert(ModbusRegisterType::Voltage, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + /* m_registerMap.insert(ModbusRegisterType::Current, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + m_registerMap.insert(ModbusRegisterType::ActivePower, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + m_registerMap.insert(ModbusRegisterType::Frequency, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + m_registerMap.insert(ModbusRegisterType::PowerFactor, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + m_registerMap.insert(ModbusRegisterType::EnergyConsumed, ModbusRegisterDescriptor(1, 3, 2, "V", "float")); + m_registerMap.insert(ModbusRegisterType::EnergyProduced, ModbusRegisterDescriptor(1, 3, 2, "V", "float"));*/ + } +protected: + static QHash m_registerMap; +public: + static QHash map() + { BgEtechModbusRegisers(); + init(); + return m_registerMap;} +}; + +#endif // BGETECHMODBUSREGISTER_H diff --git a/energymeters/energymeter.cpp b/energymeters/energymeter.cpp new file mode 100644 index 0000000..65c6f63 --- /dev/null +++ b/energymeters/energymeter.cpp @@ -0,0 +1,148 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "energymeter.h" +#include "hardware/modbus/modbusrtureply.h" + +EnergyMeter::EnergyMeter(ModbusRtuMaster *modbusMaster, int slaveAddress, const QHash &modbusRegisters, QObject *parent) : + QObject(parent), + m_modbusRtuMaster(modbusMaster), + m_slaveAddress(slaveAddress), + m_modbusRegisters(modbusRegisters) +{ + +} + +bool EnergyMeter::init() +{ + return true; +} + +bool EnergyMeter::connected() +{ + return m_connected; +} + +bool EnergyMeter::getVoltage() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::Voltage)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::Voltage); + getRegister(ModbusRegisterType::Voltage, descriptor); + return true; +} + +bool EnergyMeter::getCurrent() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::Current)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::Current); + getRegister(ModbusRegisterType::Current, descriptor); + return true; +} + +bool EnergyMeter::getFrequency() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::Frequency)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::Frequency); + getRegister(ModbusRegisterType::Frequency, descriptor); + return true; +} + +bool EnergyMeter::getPowerFactor() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::PowerFactor)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::PowerFactor); + getRegister(ModbusRegisterType::PowerFactor, descriptor); + return true; +} + +bool EnergyMeter::getActivePower() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::ActivePower)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::ActivePower); + getRegister(ModbusRegisterType::ActivePower, descriptor); + return true; +} + +bool EnergyMeter::getEnergyProduced() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::EnergyProduced)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::EnergyProduced); + getRegister(ModbusRegisterType::EnergyProduced, descriptor); + return true; +} + +bool EnergyMeter::getEnergyConsumed() +{ + if (!m_modbusRegisters.contains(ModbusRegisterType::EnergyConsumed)) + return false; + + ModbusRegisterDescriptor descriptor = m_modbusRegisters.value(ModbusRegisterType::EnergyConsumed); + getRegister(ModbusRegisterType::EnergyConsumed, descriptor); + return true; +} + +void EnergyMeter::getRegister(ModbusRegisterType type, ModbusRegisterDescriptor descriptor) +{ + + ModbusRtuReply *reply; + if (descriptor.functionCode() == 1){ + + } else if (descriptor.functionCode() == 2){ + + } else if (descriptor.functionCode() == 3){ + reply = m_modbusRtuMaster->readHoldingRegister(m_slaveAddress, descriptor.address(), descriptor.length()); + } else if (descriptor.functionCode() == 4){ + } + connect(reply, &ModbusRtuReply::finished, reply, &ModbusRtuReply::deleteLater); + connect(reply, &ModbusRtuReply::finished, this, [reply, type, this] { + if (reply->error() != ModbusRtuReply::NoError) { + return; + } + double value = 0; + if (reply->result().length() == 2) { + value = static_cast(reply->result().at(0) << 16 | reply->result().at(1)); + } + + emit valueReceived(type, value); + }); +} + diff --git a/energymeters/energymeter.h b/energymeters/energymeter.h new file mode 100644 index 0000000..d5e4fe9 --- /dev/null +++ b/energymeters/energymeter.h @@ -0,0 +1,81 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ENERGYMETER_H +#define ENERGYMETER_H + +#include + +#include "registerdescriptor.h" +#include "hardware/modbus/modbusrtumaster.h" + +class EnergyMeter : public QObject +{ + Q_OBJECT +public: + explicit EnergyMeter(ModbusRtuMaster *modbusMaster, int slaveAddress, const QHash &modbusRegisters, QObject *parent = nullptr); + bool init(); + + bool connected(); + bool getVoltage(); + bool getCurrent(); + bool getFrequency(); + bool getPowerFactor(); + bool getActivePower(); + bool getEnergyProduced(); + bool getEnergyConsumed(); + +private: + bool m_connected = false; + + ModbusRtuMaster *m_modbusRtuMaster = nullptr; + int m_slaveAddress; + + QHash m_modbusRegisters; + + void getRegister(ModbusRegisterType type, ModbusRegisterDescriptor descriptor); + +signals: + void connectedChanged(bool connected); + void valueReceived(ModbusRegisterType type, double value); + + void voltageReceived(double voltage); + void currentReceived(double current); + void frequencyReceived(double freqeuncy); + void activePowerReceived(double power); + void powerFactorReceived(double powerFactor); + void producedEnergyReceived(double energy); + void consumedEnergyReceived(double energy); + +//private slot: +// void onRegisterReceived(); +}; + +#endif // ENERGYMETER_H diff --git a/energymeters/energymeters.pro b/energymeters/energymeters.pro new file mode 100644 index 0000000..17d879f --- /dev/null +++ b/energymeters/energymeters.pro @@ -0,0 +1,16 @@ +include(../plugins.pri) + +QT += \ + serialport \ + serialbus \ + +SOURCES += \ + energymeter.cpp \ + integrationpluginenergymeters.cpp + +HEADERS += \ + energymeter.h \ + integrationpluginenergymeters.h \ + inepromodbusregister.h \ + bg-etechmodbusregister.h \ + registerdescriptor.h diff --git a/energymeters/inepromodbusregister.h b/energymeters/inepromodbusregister.h new file mode 100644 index 0000000..370a4ed --- /dev/null +++ b/energymeters/inepromodbusregister.h @@ -0,0 +1,45 @@ +#ifndef INEPROMODBUSREGISTER_H +#define INEPROMODBUSREGISTER_H + +enum InputRegisters { + Phase1ToNeutralVolts = 1, + Phase2ToNeutralVolts = 3, + Phase3ToNeutralVolts = 5, + Phase1Current = 7, + Phase2Current = 9, + Phase3Current = 11, + Phase1Power = 13, + Phase2Power = 15, + Phase3Power = 17, + Phase1ApparentPower = 19, + Phase2ApparentPower = 21, + Phase3ApparentPower = 23, + Phase1ReactivePower = 25, + Phase2ReactivePower = 27, + Phase3ReactivePower = 29, + Phase1PowerFactor = 31, + Phase2PowerFactor = 33, + Phase3PowerFactor = 35, + Phase1Angle = 37, + Phase2Angle = 39, + Phase3Angle = 41, + Frequency = 71, + ImportActiveEnergy = 73, + ExportActiveEnergy = 75, + ImportReactiveEnergy = 77, + ExportReactiveEnergy = 79, + TotalActiveEnergy = 343, + TotalReactiveEnergy = 345 +}; + +enum HoldingRegisters { + RelayPulseWidth = 13, + NetworkParityStop = 19, + NetworkPortNode = 21, + NetworkBaudRate = 29, + Pulse1Output = 87, + Pulse1Constant = 63761, + MeasurementMode = 63776 +}; + +#endif // BGETECHMODBUSREGISTER_H diff --git a/energymeters/integrationpluginenergymeters.cpp b/energymeters/integrationpluginenergymeters.cpp new file mode 100644 index 0000000..e7b5743 --- /dev/null +++ b/energymeters/integrationpluginenergymeters.cpp @@ -0,0 +1,291 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginenergymeters.h" +#include "plugininfo.h" + +#include "bg-etechmodbusregister.h" + +IntegrationPluginEnergyMeters::IntegrationPluginEnergyMeters() +{ + /* + * NOTE: + * To add an new device model, the integrationplugin json file must be extended with the new model and vendor. + * Then add the new states and params to the lists here, also add the modbus register configuration file +*/ + m_slaveIdParamTypeIds.insert(pro380ThingClassId, pro380ThingSlaveAddressParamTypeId); + m_slaveIdParamTypeIds.insert(sdm630ThingClassId, sdm630ThingSlaveAddressParamTypeId); + + m_modbusUuidParamTypeIds.insert(pro380ThingClassId, pro380ThingModbusMasterUuidParamTypeId); + m_modbusUuidParamTypeIds.insert(sdm630ThingClassId, sdm630ThingModbusMasterUuidParamTypeId); + + m_connectionStateTypeIds.insert(pro380ThingClassId, pro380ConnectedStateTypeId); + m_connectionStateTypeIds.insert(sdm630ThingClassId, sdm630ConnectedStateTypeId); + + m_voltageStateTypeIds.insert(pro380ThingClassId, pro380VoltageStateTypeId); + m_voltageStateTypeIds.insert(sdm630ThingClassId, sdm630VoltageStateTypeId); + + m_currentStateTypeIds.insert(pro380ThingClassId, pro380CurrentStateTypeId); + m_currentStateTypeIds.insert(sdm630ThingClassId, sdm630CurrentStateTypeId); + + m_activePowerStateTypeIds.insert(pro380ThingClassId, pro380CurrentPowerEventTypeId); + m_activePowerStateTypeIds.insert(sdm630ThingClassId, sdm630CurrentPowerStateTypeId); + + m_frequencyStateTypeIds.insert(pro380ThingClassId, pro380FrequencyStateTypeId); + m_frequencyStateTypeIds.insert(sdm630ThingClassId, sdm630FrequencyStateTypeId); + + m_powerFactorStateTypeIds.insert(pro380ThingClassId, pro380PowerFactorStateTypeId); + m_powerFactorStateTypeIds.insert(sdm630ThingClassId, sdm630PowerFactorStateTypeId); + + m_discoverySlaveAddressParamTypeIds.insert(pro380ThingClassId, pro380DiscoverySlaveAddressParamTypeId); + m_discoverySlaveAddressParamTypeIds.insert(sdm630ThingClassId, sdm630DiscoverySlaveAddressParamTypeId); + + // Modbus RTU hardware resource + connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, this, [=](const QUuid &modbusUuid){ + qCDebug(dcEnergyMeters()) << "Modbus RTU master has been removed" << modbusUuid.toString(); + + // Check if there is any device using this resource + foreach (Thing *thing, m_modbusRtuMasters.keys()) { + if (m_modbusRtuMasters.value(thing)->modbusUuid() == modbusUuid) { + qCWarning(dcEnergyMeters()) << "Hardware resource removed for" << thing << ". The thing will not be functional any more until a new resource has been configured for it."; + m_modbusRtuMasters.remove(thing); + thing->setStateValue(m_connectionStateTypeIds[thing->thingClassId()], false); + + // Set all child things disconnected + foreach (Thing *childThing, myThings()) { + if (childThing->parentId() == thing->id()) { + thing->setStateValue(m_connectionStateTypeIds[childThing->thingClassId()], false); + } + } + } + } + }); +} + +void IntegrationPluginEnergyMeters::discoverThings(ThingDiscoveryInfo *info) +{ + qCDebug(dcEnergyMeters()) << "Discover things"; + QList thingDescriptors; + + if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) { + info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No Modbus RTU interface available.")); + return; + } + + if (m_connectionStateTypeIds.contains(info->thingClassId())) { + int slaveAddress = info->params().paramValue(m_discoverySlaveAddressParamTypeIds.value(info->thingClassId())).toInt(); + if (slaveAddress > 254 || slaveAddress == 0) { + info->finish(Thing::ThingErrorInvalidParameter, tr("Modbus slave address must be between 1 and 254")); + return; + } + Q_FOREACH(ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcEnergyMeters()) << "Found RTU master resource" << modbusMaster << "connected" << modbusMaster->connected(); + if (!modbusMaster->connected()) { + continue; + } + ThingDescriptor descriptor(info->thingClassId(), "Modbus interface "+modbusMaster->serialPort(), modbusMaster->modbusUuid().toString()); + ParamList params; + params << Param(m_slaveIdParamTypeIds.value(info->thingClassId()), slaveAddress); + params << Param(m_modbusUuidParamTypeIds.value(info->thingClassId()), modbusMaster->modbusUuid()); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + info->finish(Thing::ThingErrorNoError); + return; + } else { + Q_ASSERT_X(false, "discoverThings", QString("Unhandled thingClassId: %1").arg(info->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginEnergyMeters::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + qCDebug(dcEnergyMeters()) << "Setup thing" << thing->name(); + + if (m_connectionStateTypeIds.contains(thing->thingClassId())) { + + if (m_energyMeters.contains(thing)) { + qCDebug(dcEnergyMeters()) << "Setup after rediscovery, cleaning up ..."; + m_energyMeters.take(thing)->deleteLater(); + } + int address = thing->paramValue(m_slaveIdParamTypeIds.value(thing->thingClassId())).toInt(); + if (address > 254 || address == 0) { + qCWarning(dcEnergyMeters()) << "Setup failed, slave address is not valid" << address; + info->finish(Thing::ThingErrorSetupFailed, tr("Slave address not valid, must be between 1 and 254")); + return; + } + QUuid uuid = thing->paramValue(m_modbusUuidParamTypeIds.value(thing->thingClassId())).toString(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcEnergyMeters()) << "Setup failed, hardware manager not available"; + info->finish(Thing::ThingErrorSetupFailed, tr("Modbus RTU resource not available.")); + return; + } + + EnergyMeter *meter = new EnergyMeter(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, BgEtechModbusRegisers::map(), this); + connect(info, &ThingSetupInfo::aborted, meter, &EnergyMeter::deleteLater); + connect(meter, &EnergyMeter::consumedEnergyReceived, info, [this, info, meter] { + qCDebug(dcEnergyMeters()) << "Reply received, setup finished"; + connect(meter, &EnergyMeter::connectedChanged, this, &IntegrationPluginEnergyMeters::onConnectionStateChanged); + connect(meter, &EnergyMeter::voltageReceived, this, &IntegrationPluginEnergyMeters::onVoltageReceived); + connect(meter, &EnergyMeter::currentReceived, this, &IntegrationPluginEnergyMeters::onCurrentReceived); + connect(meter, &EnergyMeter::activePowerReceived, this, &IntegrationPluginEnergyMeters::onActivePowerReceived); + connect(meter, &EnergyMeter::powerFactorReceived, this, &IntegrationPluginEnergyMeters::onPowerFactorReceived); + connect(meter, &EnergyMeter::frequencyReceived, this, &IntegrationPluginEnergyMeters::onActivePowerReceived); + connect(meter, &EnergyMeter::producedEnergyReceived, this, &IntegrationPluginEnergyMeters::onProducedEnergyReceived); + connect(meter, &EnergyMeter::consumedEnergyReceived, this, &IntegrationPluginEnergyMeters::onConsumedEnergyReceived); + + m_energyMeters.insert(info->thing(), meter); + info->finish(Thing::ThingErrorNoError); + }); + return; + } else { + Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8()); + } +} + +void IntegrationPluginEnergyMeters::postSetupThing(Thing *thing) +{ + qCDebug(dcEnergyMeters) << "Post setup thing" << thing->name(); + if (m_connectionStateTypeIds.contains(thing->thingClassId())) { + thing->setStateValue(m_connectionStateTypeIds.value(thing->thingClassId()), true); + } + + if (!m_updateTimer) { + qCDebug(dcEnergyMeters()) << "Creating update timer"; + m_updateTimer = new QTimer(this); + m_updateTimer->start(configValue(energyMetersPluginUpdateIntervalParamTypeId).toInt()); + connect(m_updateTimer, &QTimer::timeout, this, [this] { + foreach (EnergyMeter *meter, m_energyMeters) { + meter->getVoltage(); + } + }); + connect(this, &IntegrationPlugin::configValueChanged, [this] (const ParamTypeId ¶mTypeId, const QVariant value) { + if (m_updateTimer && (paramTypeId == energyMetersPluginUpdateIntervalParamTypeId)) { + qCDebug(dcEnergyMeters()) << "Updating update interval to" << value; + m_updateTimer->setInterval(value.toInt()); + } + }); + } +} + +void IntegrationPluginEnergyMeters::thingRemoved(Thing *thing) +{ + qCDebug(dcEnergyMeters()) << "Thing removed" << thing->name(); + + if (m_energyMeters.contains(thing)) { + m_energyMeters.take(thing)->deleteLater(); + } + + if (myThings().isEmpty() && !m_updateTimer) { + qCDebug(dcEnergyMeters()) << "Deleting update timer"; + m_updateTimer->deleteLater(); + m_updateTimer = nullptr; + } +} + +void IntegrationPluginEnergyMeters::onConnectionStateChanged(bool status) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_connectionStateTypeIds.value(thing->thingClassId()), status); +} + +void IntegrationPluginEnergyMeters::onVoltageReceived(double voltage) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_voltageStateTypeIds.value(thing->thingClassId()), voltage); +} + +void IntegrationPluginEnergyMeters::onCurrentReceived(double current) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_currentStateTypeIds.value(thing->thingClassId()), current); +} + +void IntegrationPluginEnergyMeters::onActivePowerReceived(double power) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_activePowerStateTypeIds.value(thing->thingClassId()), power); +} + +void IntegrationPluginEnergyMeters::onFrequencyReceived(double frequency) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_frequencyStateTypeIds.value(thing->thingClassId()), frequency); +} + +void IntegrationPluginEnergyMeters::onPowerFactorReceived(double powerFactor) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_powerFactorStateTypeIds.value(thing->thingClassId()), powerFactor); +} + +void IntegrationPluginEnergyMeters::onProducedEnergyReceived(double energy) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_totalEnergyProducedStateTypeIds.value(thing->thingClassId()), energy); +} + +void IntegrationPluginEnergyMeters::onConsumedEnergyReceived(double energy) +{ + EnergyMeter *meter = static_cast(sender()); + Thing *thing = m_energyMeters.key(meter); + if (!thing) + return; + + thing->setStateValue(m_totalEnergyConsumedStateTypeIds.value(thing->thingClassId()), energy); +} diff --git a/energymeters/integrationpluginenergymeters.h b/energymeters/integrationpluginenergymeters.h new file mode 100644 index 0000000..9dfbf10 --- /dev/null +++ b/energymeters/integrationpluginenergymeters.h @@ -0,0 +1,89 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 INTEGRATIONPLUGINENERGYMETERS_H +#define INTEGRATIONPLUGINENERGYMETERS_H + +#include "integrations/integrationplugin.h" +#include "hardware/modbus/modbusrtuhardwareresource.h" +#include "plugintimer.h" + +#include "energymeter.h" + +#include +#include + +class IntegrationPluginEnergyMeters : public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginenergymeters.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginEnergyMeters(); + + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + +private: + QTimer *m_updateTimer = nullptr; + QHash m_connectionStateTypeIds; + QHash m_voltageStateTypeIds; + QHash m_currentStateTypeIds; + QHash m_activePowerStateTypeIds; + QHash m_frequencyStateTypeIds; + QHash m_powerFactorStateTypeIds; + QHash m_totalEnergyConsumedStateTypeIds; + QHash m_totalEnergyProducedStateTypeIds; + + QHash m_discoverySlaveAddressParamTypeIds; + QHash m_slaveIdParamTypeIds; + QHash m_modbusUuidParamTypeIds; + + QHash m_energyMeters; + QHash m_modbusRtuMasters; + PluginTimer *m_pluginTimer = nullptr; + QHash m_asyncActions; + +private slots: + void onConnectionStateChanged(bool status); + void onVoltageReceived(double voltage); + void onCurrentReceived(double current); + void onActivePowerReceived(double power); + void onFrequencyReceived(double frequency); + void onPowerFactorReceived(double powerFactor); + void onProducedEnergyReceived(double energy); + void onConsumedEnergyReceived(double energy); +}; + +#endif // INTEGRATIONPLUGINENERGYMETERS_H diff --git a/energymeters/integrationpluginenergymeters.json b/energymeters/integrationpluginenergymeters.json new file mode 100644 index 0000000..56016eb --- /dev/null +++ b/energymeters/integrationpluginenergymeters.json @@ -0,0 +1,244 @@ +{ + "name": "EnergyMeters", + "displayName": "EnergyMeters", + "id": "56e95111-fb6b-4f63-9a0a-a5ee001e89ed", + "paramTypes":[ + { + "id": "eaa84c3c-06b8-4642-a40b-c2efbe6aae66", + "name": "updateInterval", + "displayName": "Update interval", + "type": "uint", + "unit": "MilliSeconds", + "defaultValue": 300, + "minValue": 200 + } + ], + "vendors": [ + { + "name": "ineproMetering", + "displayName": "inepro Metering", + "id": "64f4df0f-18ce-409c-bf32-84a086c691ca", + "thingClasses": [ + { + "name": "pro380", + "displayName": "PRO380-Mod", + "id": "d7c6440b-54f9-4cc0-a96b-9bb7304b3e77", + "createMethods": ["discovery"], + "interfaces": ["extendedsmartmeterconsumer"], + "discoveryParamTypes": [ + { + "id": "a29f37f6-b344-4628-8ab4-8f4c18fada4a", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "c75b2c31-6ec3-49ab-8c8f-5231d0a7e941", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "int", + "defaultValue": 1 + }, + { + "id": "6cdbec8c-21b9-42dc-b1ab-8901ac609482", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "7f9bc504-0882-4b86-83b1-42fa345acfd9", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "04dba21a-7447-46b9-b9ae-095e5769e511", + "name": "voltage", + "displayName": "Voltage", + "displayNameEvent": "Voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "1e077a3b-2dab-4ec4-ae96-ab49a564fe31", + "name": "current", + "displayName": "Current", + "displayNameEvent": "Current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "464eff60-11c2-46b7-98f5-1aa8172e5a2d", + "name": "currentPower", + "displayName": "Active power", + "displayNameEvent": "Active power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "cdb34487-3d9b-492a-8c33-802f32a2e90e", + "name": "powerFactor", + "displayName": "Power factor", + "displayNameEvent": "Power factor changed", + "type": "double", + "defaultValue": 0.00 + }, + { + "id": "bb6fd00c-3bbb-4977-bb8a-96787bb6f5c5", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0.00 + }, + { + "id": "f18fd596-b47f-44be-a0f0-6ca44369ebf5", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "112911c9-14e0-4c83-ac92-f2ceb3bdecdf", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + } + ] + } + ] + }, + { + "name": "bgetech", + "displayName": "B+G e-tech", + "id": "215035fe-95e8-43d8-a52e-0a31b787d902", + "thingClasses": [ + { + "name": "sdm630", + "displayName": "SDM630Modbus", + "id": "f37597bb-35fe-48f2-9617-343dd54c0903", + "createMethods": ["discovery"], + "interfaces": ["extendedsmartmeterconsumer"], + "discoveryParamTypes": [ + { + "id": "6ab43559-53ec-47ba-b8a0-8d3b7f8d90c2", + "name": "slaveAddress", + "displayName": "Slave address", + "type": "int", + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "ac77ea98-b006-486e-a3e8-b30a483f26c1", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "int", + "defaultValue": 1 + }, + { + "id": "d90e9292-d03c-4f2a-957e-5d965018c9c9", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QUuid", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "8050bd0b-1dad-4a7e-b632-c71ead3c9f8b", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "cached": false, + "defaultValue": false + }, + { + "id": "4636ec5c-fcb9-45b7-ad68-2818cb615ce1", + "name": "voltage", + "displayName": "Voltage", + "displayNameEvent": "Voltage changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "96bc65ce-5bde-4a69-9ebf-711d65c6501c", + "name": "current", + "displayName": "Current", + "displayNameEvent": "Current changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "c824e97b-a6d1-4030-9d7a-00af6fb8e1c3", + "name": "currentPower", + "displayName": "Active power", + "displayNameEvent": "Active power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "31b9032f-f994-472b-94bd-44f9fb094801", + "name": "powerFactor", + "displayName": "Power factor", + "displayNameEvent": "Power factor changed", + "type": "double", + "defaultValue": 0.00 + }, + { + "id": "ab24f26c-dc15-4ec3-8d76-06a48285440b", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0.00 + }, + { + "id": "98d858a8-22e8-4262-b5c7-25bb027942ad", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "e469b3ff-a4c2-42da-af35-ccafaef214af", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + } + ] + } + ] + } + ] +} diff --git a/energymeters/meta.json b/energymeters/meta.json new file mode 100644 index 0000000..e69de29 diff --git a/energymeters/registerdescriptor.h b/energymeters/registerdescriptor.h new file mode 100644 index 0000000..5e53807 --- /dev/null +++ b/energymeters/registerdescriptor.h @@ -0,0 +1,83 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 REGISTERDESCRIPTOR_H +#define REGISTERDESCRIPTOR_H + +#include +#include + +enum ModbusRegisterType { + Voltage, + Current, + ActivePower, + Frequency, + PowerFactor, + EnergyProduced, + EnergyConsumed +}; + +class ModbusRegisterDescriptor +{ + +public: + ModbusRegisterDescriptor() {} + ModbusRegisterDescriptor(int address, int functionCode, int length, QString unit, QString dataType) : + m_address(address), + m_functionCode(functionCode), + m_length(length), + m_unit(unit), + m_dataType(dataType) + {} + + int address() const + { return m_address;} + + int functionCode() const + { return m_functionCode;} + + int length() const + { return m_length;} + + QString unit() const + { return m_unit;} + + QString dataType() const + { return m_dataType;} + +private: + int m_address; + int m_functionCode; + int m_length; + QString m_unit; + QString m_dataType; +}; + +#endif // REGISTERDESCRIPTOR_H diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro index 207d8bc..2515df5 100644 --- a/nymea-plugins-modbus.pro +++ b/nymea-plugins-modbus.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs PLUGIN_DIRS = \ drexelundweiss \ + energymeters \ modbuscommander \ mypv \ sunspec \