From 0cbbf8ae9be202c0231bdbcdd8a81c43417a01b5 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Mon, 1 Jun 2026 07:05:07 +0200 Subject: [PATCH] Initial commit ABB meter B21/B23/B24 --- abbb2x/abb.png | Bin 0 -> 4860 bytes abbb2x/abbb2x-registers.json | 164 ++++++++++++++++++++++ abbb2x/abbb2x.pro | 12 ++ abbb2x/integrationpluginabbb2x.cpp | 209 ++++++++++++++++++++++++++++ abbb2x/integrationpluginabbb2x.h | 59 ++++++++ abbb2x/integrationpluginabbb2x.json | 181 ++++++++++++++++++++++++ abbb2x/meta.json | 13 ++ 7 files changed, 638 insertions(+) create mode 100644 abbb2x/abb.png create mode 100644 abbb2x/abbb2x-registers.json create mode 100644 abbb2x/abbb2x.pro create mode 100644 abbb2x/integrationpluginabbb2x.cpp create mode 100644 abbb2x/integrationpluginabbb2x.h create mode 100644 abbb2x/integrationpluginabbb2x.json create mode 100644 abbb2x/meta.json diff --git a/abbb2x/abb.png b/abbb2x/abb.png new file mode 100644 index 0000000000000000000000000000000000000000..7f548016b3248b4f20d903b9dc27f6589c964e23 GIT binary patch literal 4860 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 DZ%rae literal 0 HcmV?d00001 diff --git a/abbb2x/abbb2x-registers.json b/abbb2x/abbb2x-registers.json new file mode 100644 index 0000000..dbcec7d --- /dev/null +++ b/abbb2x/abbb2x-registers.json @@ -0,0 +1,164 @@ +{ + "className": "AbbB2x", + "protocol": "BOTH", + "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, + "checkReachableRegister": "voltagePhaseA", + "blocks": [ + { + "id": "instantaneousValues", + "readSchedule": "update", + "registers": [ + { + "id": "voltagePhaseA", + "address": 23296, + "size": 2, + "type": "uint32", + "unit": "0.1V", + "registerType": "holdingRegister", + "description": "Voltage L1-N", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseB", + "address": 23298, + "size": 2, + "type": "uint32", + "unit": "0.1V", + "registerType": "holdingRegister", + "description": "Voltage L2-N", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "voltagePhaseC", + "address": 23300, + "size": 2, + "type": "uint32", + "unit": "0.1V", + "registerType": "holdingRegister", + "description": "Voltage L3-N", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseA", + "address": 23308, + "size": 2, + "type": "uint32", + "unit": "0.01A", + "registerType": "holdingRegister", + "description": "Current L1", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseB", + "address": 23310, + "size": 2, + "type": "uint32", + "unit": "0.01A", + "registerType": "holdingRegister", + "description": "Current L2", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "currentPhaseC", + "address": 23312, + "size": 2, + "type": "uint32", + "unit": "0.01A", + "registerType": "holdingRegister", + "description": "Current L3", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "activePowerTotal", + "address": 23316, + "size": 2, + "type": "int32", + "unit": "0.01W", + "registerType": "holdingRegister", + "description": "Active power Total (signed: + import / - export)", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "activePowerPhaseA", + "address": 23318, + "size": 2, + "type": "int32", + "unit": "0.01W", + "registerType": "holdingRegister", + "description": "Active power L1 (signed)", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "activePowerPhaseB", + "address": 23320, + "size": 2, + "type": "int32", + "unit": "0.01W", + "registerType": "holdingRegister", + "description": "Active power L2 (signed)", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "activePowerPhaseC", + "address": 23322, + "size": 2, + "type": "int32", + "unit": "0.01W", + "registerType": "holdingRegister", + "description": "Active power L3 (signed)", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "frequency", + "address": 23340, + "size": 1, + "type": "uint16", + "unit": "0.01Hz", + "registerType": "holdingRegister", + "description": "Frequency", + "defaultValue": "0", + "access": "RO" + } + ] + }, + { + "id": "energyAccumulators", + "readSchedule": "update", + "registers": [ + { + "id": "totalEnergyConsumed", + "address": 20480, + "size": 4, + "type": "uint64", + "unit": "0.01kWh", + "registerType": "holdingRegister", + "description": "Active import (total consumed energy)", + "defaultValue": "0", + "access": "RO" + }, + { + "id": "totalEnergyProduced", + "address": 20484, + "size": 4, + "type": "uint64", + "unit": "0.01kWh", + "registerType": "holdingRegister", + "description": "Active export (total produced energy)", + "defaultValue": "0", + "access": "RO" + } + ] + } + ] +} diff --git a/abbb2x/abbb2x.pro b/abbb2x/abbb2x.pro new file mode 100644 index 0000000..b1f5e7e --- /dev/null +++ b/abbb2x/abbb2x.pro @@ -0,0 +1,12 @@ +include(../plugins.pri) + +# Generate modbus connections +MODBUS_CONNECTIONS += abbb2x-registers.json +#MODBUS_TOOLS_CONFIG += VERBOSE +include(../modbus.pri) + +HEADERS += \ + integrationpluginabbb2x.h + +SOURCES += \ + integrationpluginabbb2x.cpp diff --git a/abbb2x/integrationpluginabbb2x.cpp b/abbb2x/integrationpluginabbb2x.cpp new file mode 100644 index 0000000..17b1754 --- /dev/null +++ b/abbb2x/integrationpluginabbb2x.cpp @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-plugins-modbus 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "integrationpluginabbb2x.h" +#include "plugininfo.h" + +// Facteurs d'echelle ABB B2x (cf. registers.json / manuel B2x) : +// tension : 0,1 V -> /10 +// courant : 0,01 A -> /100 +// puissance : 0,01 W -> /100 (registre SIGNED : + import / - export) +// frequence : 0,01 Hz -> /100 +// energie cumulee : 0,01 kWh -> /100 +// NB : le generateur renvoie la valeur BRUTE du registre ; le scaling se fait ici +// (meme convention que le plugin abbterra). + +IntegrationPluginAbbB2x::IntegrationPluginAbbB2x() +{ +} + +void IntegrationPluginAbbB2x::init() +{ + connect(hardwareManager()->modbusRtuResource(), &ModbusRtuHardwareResource::modbusRtuMasterRemoved, + this, [=](const QUuid &modbusUuid) { + qCDebug(dcAbbB2x()) << "Modbus RTU master removed:" << modbusUuid.toString(); + + foreach (Thing *thing, myThings()) { + if (thing->paramValue(abbB2xThingModbusMasterUuidParamTypeId) == modbusUuid) { + thing->setStateValue(abbB2xConnectedStateTypeId, false); + if (m_connections.contains(thing)) + delete m_connections.take(thing); + } + } + }); +} + +void IntegrationPluginAbbB2x::discoverThings(ThingDiscoveryInfo *info) +{ + if (info->thingClassId() != abbB2xThingClassId) { + info->finish(Thing::ThingErrorThingClassNotFound); + return; + } + + if (hardwareManager()->modbusRtuResource()->modbusRtuMasters().isEmpty()) { + info->finish(Thing::ThingErrorHardwareNotAvailable, + QT_TR_NOOP("No Modbus RTU interface available. Please set up the Modbus RTU interface first.")); + return; + } + + uint slaveAddress = info->params().paramValue(abbB2xDiscoverySlaveAddressParamTypeId).toUInt(); + if (slaveAddress == 0 || slaveAddress > 254) { + info->finish(Thing::ThingErrorInvalidParameter, + QT_TR_NOOP("The Modbus slave address must be a value between 1 and 254.")); + return; + } + + foreach (ModbusRtuMaster *modbusMaster, hardwareManager()->modbusRtuResource()->modbusRtuMasters()) { + qCDebug(dcAbbB2x()) << "Found RTU master" << modbusMaster << "connected:" << modbusMaster->connected(); + if (!modbusMaster->connected()) + continue; + + ThingDescriptor descriptor(info->thingClassId(), "ABB B2x", + QString::number(slaveAddress) + " " + modbusMaster->serialPort()); + ParamList params; + params << Param(abbB2xThingSlaveAddressParamTypeId, slaveAddress); + params << Param(abbB2xThingModbusMasterUuidParamTypeId, modbusMaster->modbusUuid()); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + } + + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginAbbB2x::setupThing(ThingSetupInfo *info) +{ + Thing *thing = info->thing(); + + if (thing->thingClassId() != abbB2xThingClassId) { + info->finish(Thing::ThingErrorThingClassNotFound); + return; + } + + uint address = thing->paramValue(abbB2xThingSlaveAddressParamTypeId).toUInt(); + if (address == 0 || address > 254) { + qCWarning(dcAbbB2x()) << "Setup failed, invalid slave address" << address; + info->finish(Thing::ThingErrorSetupFailed, + QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 254.")); + return; + } + + QUuid uuid = thing->paramValue(abbB2xThingModbusMasterUuidParamTypeId).toUuid(); + if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) { + qCWarning(dcAbbB2x()) << "Setup failed, Modbus RTU master not available"; + info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU interface not available.")); + return; + } + + if (m_connections.contains(thing)) + m_connections.take(thing)->deleteLater(); + + AbbB2xModbusRtuConnection *connection = new AbbB2xModbusRtuConnection( + hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), address, this); + + connect(connection, &AbbB2xModbusRtuConnection::reachableChanged, this, [=](bool reachable) { + thing->setStateValue(abbB2xConnectedStateTypeId, reachable); + }); + + // Tensions (0,1 V) + connect(connection, &AbbB2xModbusRtuConnection::voltagePhaseAChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xVoltagePhaseAStateTypeId, v / 10.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::voltagePhaseBChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xVoltagePhaseBStateTypeId, v / 10.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::voltagePhaseCChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xVoltagePhaseCStateTypeId, v / 10.0); + }); + + // Courants (0,01 A) + connect(connection, &AbbB2xModbusRtuConnection::currentPhaseAChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xCurrentPhaseAStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::currentPhaseBChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xCurrentPhaseBStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::currentPhaseCChanged, this, [=](quint32 v) { + thing->setStateValue(abbB2xCurrentPhaseCStateTypeId, v / 100.0); + }); + + // Puissances (0,01 W, SIGNED : + import / - export) + connect(connection, &AbbB2xModbusRtuConnection::activePowerTotalChanged, this, [=](qint32 v) { + thing->setStateValue(abbB2xCurrentPowerStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::activePowerPhaseAChanged, this, [=](qint32 v) { + thing->setStateValue(abbB2xCurrentPowerPhaseAStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::activePowerPhaseBChanged, this, [=](qint32 v) { + thing->setStateValue(abbB2xCurrentPowerPhaseBStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::activePowerPhaseCChanged, this, [=](qint32 v) { + thing->setStateValue(abbB2xCurrentPowerPhaseCStateTypeId, v / 100.0); + }); + + // Frequence (0,01 Hz) + connect(connection, &AbbB2xModbusRtuConnection::frequencyChanged, this, [=](quint16 v) { + thing->setStateValue(abbB2xFrequencyStateTypeId, v / 100.0); + }); + + // Energie cumulee (0,01 kWh) + connect(connection, &AbbB2xModbusRtuConnection::totalEnergyConsumedChanged, this, [=](quint64 v) { + thing->setStateValue(abbB2xTotalEnergyConsumedStateTypeId, v / 100.0); + }); + connect(connection, &AbbB2xModbusRtuConnection::totalEnergyProducedChanged, this, [=](quint64 v) { + thing->setStateValue(abbB2xTotalEnergyProducedStateTypeId, v / 100.0); + }); + + m_connections.insert(thing, connection); + info->finish(Thing::ThingErrorNoError); +} + +void IntegrationPluginAbbB2x::postSetupThing(Thing *thing) +{ + qCDebug(dcAbbB2x()) << "Post setup thing" << thing->name(); + if (!m_refreshTimer) { + m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(2); + connect(m_refreshTimer, &PluginTimer::timeout, this, [this] { + foreach (Thing *thing, myThings()) { + if (m_connections.contains(thing)) + m_connections.value(thing)->update(); + } + }); + qCDebug(dcAbbB2x()) << "Refresh timer started"; + m_refreshTimer->start(); + } +} + +void IntegrationPluginAbbB2x::thingRemoved(Thing *thing) +{ + qCDebug(dcAbbB2x()) << "Thing removed" << thing->name(); + + if (m_connections.contains(thing)) + m_connections.take(thing)->deleteLater(); + + if (myThings().isEmpty() && m_refreshTimer) { + hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer); + m_refreshTimer = nullptr; + qCDebug(dcAbbB2x()) << "Refresh timer stopped"; + } +} diff --git a/abbb2x/integrationpluginabbb2x.h b/abbb2x/integrationpluginabbb2x.h new file mode 100644 index 0000000..a8ec198 --- /dev/null +++ b/abbb2x/integrationpluginabbb2x.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2025, ETM-Schurig SARL +* +* This file is part of nymea-plugins-modbus. +* +* nymea-plugins-modbus is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-plugins-modbus 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-plugins-modbus. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTEGRATIONPLUGINABBB2X_H +#define INTEGRATIONPLUGINABBB2X_H + +#include +#include +#include + +#include "abbb2xmodbusrtuconnection.h" + +#include "extern-plugininfo.h" + +#include +#include + +class IntegrationPluginAbbB2x: public IntegrationPlugin +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginabbb2x.json") + Q_INTERFACES(IntegrationPlugin) + +public: + explicit IntegrationPluginAbbB2x(); + void init() override; + void discoverThings(ThingDiscoveryInfo *info) override; + void setupThing(ThingSetupInfo *info) override; + void postSetupThing(Thing *thing) override; + void thingRemoved(Thing *thing) override; + +private: + PluginTimer *m_refreshTimer = nullptr; + QHash m_connections; +}; + +#endif // INTEGRATIONPLUGINABBB2X_H diff --git a/abbb2x/integrationpluginabbb2x.json b/abbb2x/integrationpluginabbb2x.json new file mode 100644 index 0000000..a02c758 --- /dev/null +++ b/abbb2x/integrationpluginabbb2x.json @@ -0,0 +1,181 @@ +{ + "id": "0c5c64dd-52b4-4afe-8438-83997f3485f9", + "name": "AbbB2x", + "displayName": "ABB B2x", + "vendors": [ + { + "id": "c112c5ba-6680-400a-9e67-3333ba6e3bd2", + "name": "abb", + "displayName": "ABB", + "thingClasses": [ + { + "id": "bd391ff1-ce19-48ba-8080-d74b8b88dfa2", + "name": "abbB2x", + "displayName": "ABB B2x energy meter", + "createMethods": ["discovery", "user"], + "interfaces": ["energymeter", "connectable"], + "providedInterfaces": [], + "discoveryParamTypes": [ + { + "id": "4d716b9c-3dab-4827-abdf-2bd394dabe6d", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "minValue": 1, + "maxValue": 254, + "defaultValue": 1 + } + ], + "paramTypes": [ + { + "id": "54faf3d6-b243-4111-ace8-18eef6192e14", + "name": "slaveAddress", + "displayName": "Modbus slave address", + "type": "uint", + "minValue": 1, + "maxValue": 254, + "defaultValue": 1, + "readOnly": true + }, + { + "id": "3c10aac2-e125-40d8-8a7e-23185d3a7667", + "name": "modbusMasterUuid", + "displayName": "Modbus RTU master", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "", + "readOnly": true + } + ], + "stateTypes": [ + { + "id": "0ef1e287-b5c0-4011-9887-eb549a9d0e19", + "name": "connected", + "displayName": "Connected", + "displayNameEvent": "Connected changed", + "type": "bool", + "defaultValue": false + }, + { + "id": "01fd9397-5a66-4a67-8144-ebad166ce926", + "name": "currentPower", + "displayName": "Current power", + "displayNameEvent": "Current power changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "aa41495d-f4b9-44e3-a98c-1e6b83cb57d6", + "name": "totalEnergyConsumed", + "displayName": "Total energy consumed", + "displayNameEvent": "Total energy consumed changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "975a11da-40f0-4b7a-af2b-b96b2dba6eb4", + "name": "totalEnergyProduced", + "displayName": "Total energy produced", + "displayNameEvent": "Total energy produced changed", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0 + }, + { + "id": "d7f3752b-b653-42c7-89aa-cf20cb06e69c", + "name": "frequency", + "displayName": "Frequency", + "displayNameEvent": "Frequency changed", + "type": "double", + "unit": "Hertz", + "defaultValue": 0 + }, + { + "id": "a1a68363-072b-497b-bbb0-6f946198de26", + "name": "voltagePhaseA", + "displayName": "Voltage phase A", + "displayNameEvent": "Voltage phase A changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "69c25429-c4cf-4eb8-ace4-b43b9bb42777", + "name": "voltagePhaseB", + "displayName": "Voltage phase B", + "displayNameEvent": "Voltage phase B changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "c15752c6-7321-4c74-a65e-18ee3eb0cd13", + "name": "voltagePhaseC", + "displayName": "Voltage phase C", + "displayNameEvent": "Voltage phase C changed", + "type": "double", + "unit": "Volt", + "defaultValue": 0 + }, + { + "id": "78ce9dad-6432-4c53-b9bf-0a20ffaf8203", + "name": "currentPhaseA", + "displayName": "Current phase A", + "displayNameEvent": "Current phase A changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "13cd360e-3f25-4c73-b5fe-72aa9ec96b9a", + "name": "currentPhaseB", + "displayName": "Current phase B", + "displayNameEvent": "Current phase B changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "542b90dd-b5c7-4b9e-ba3a-c7416386f9a1", + "name": "currentPhaseC", + "displayName": "Current phase C", + "displayNameEvent": "Current phase C changed", + "type": "double", + "unit": "Ampere", + "defaultValue": 0 + }, + { + "id": "ec52d2f1-af43-4268-8d9e-16bfbe089259", + "name": "currentPowerPhaseA", + "displayName": "Current power phase A", + "displayNameEvent": "Current power phase A changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "0e9a9a3d-4df1-4531-aff3-63b21e560f40", + "name": "currentPowerPhaseB", + "displayName": "Current power phase B", + "displayNameEvent": "Current power phase B changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + }, + { + "id": "93c9329d-fbda-4d79-bc24-c133b3763b9b", + "name": "currentPowerPhaseC", + "displayName": "Current power phase C", + "displayNameEvent": "Current power phase C changed", + "type": "double", + "unit": "Watt", + "defaultValue": 0 + } + ] + } + ] + } + ] +} diff --git a/abbb2x/meta.json b/abbb2x/meta.json new file mode 100644 index 0000000..b373dfb --- /dev/null +++ b/abbb2x/meta.json @@ -0,0 +1,13 @@ +{ + "title": "ABB B2x", + "tagline": "Connect ABB B21 / B23 / B24 energy meters via Modbus RTU.", + "icon": "abb.png", + "stability": "consumer", + "offline": true, + "technologies": [ + "modbus" + ], + "categories": [ + "energy" + ] +}