diff --git a/debian/control b/debian/control
index 71da841..cadd11d 100644
--- a/debian/control
+++ b/debian/control
@@ -258,6 +258,15 @@ Description: nymea integration plugin for PhoenixConnect wallboxes
This package contains the nymea integration plugin for wallboxes made
by PhonenixConnect and rebranded as Wallbe, Compleo and Scapo.
+Package: nymea-plugin-wattsonic
+Architecture: any
+Section: libs
+Depends: ${shlibs:Depends},
+ ${misc:Depends},
+Description: nymea integration plugin for Wattsonic hybrid inverters
+ This package contains the nymea integration plugin for hybrid inverters made
+ by Wattsonic.
+
Package: nymea-plugin-webasto
Architecture: any
Section: libs
diff --git a/debian/nymea-plugin-wattsonic.install.in b/debian/nymea-plugin-wattsonic.install.in
new file mode 100644
index 0000000..c860a58
--- /dev/null
+++ b/debian/nymea-plugin-wattsonic.install.in
@@ -0,0 +1,2 @@
+usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginwattsonic.so
+wattsonic/translations/*qm usr/share/nymea/translations/
diff --git a/nymea-plugins-modbus.pro b/nymea-plugins-modbus.pro
index 771cf06..135ef91 100644
--- a/nymea-plugins-modbus.pro
+++ b/nymea-plugins-modbus.pro
@@ -25,6 +25,7 @@ PLUGIN_DIRS = \
stiebeleltron \
sunspec \
unipi \
+ wattsonic \
webasto \
gcc {
diff --git a/wattsonic/README.md b/wattsonic/README.md
new file mode 100644
index 0000000..b0a1386
--- /dev/null
+++ b/wattsonic/README.md
@@ -0,0 +1,4 @@
+# Wattsonic
+
+This plugin allows to connect wattsonic hybrid inverters to nymea
+
diff --git a/wattsonic/integrationpluginwattsonic.cpp b/wattsonic/integrationpluginwattsonic.cpp
new file mode 100644
index 0000000..f9b5c9c
--- /dev/null
+++ b/wattsonic/integrationpluginwattsonic.cpp
@@ -0,0 +1,262 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 "integrationpluginwattsonic.h"
+#include "plugininfo.h"
+#include "wattsonicdiscovery.h"
+
+#include
+#include
+
+IntegrationPluginWattsonic::IntegrationPluginWattsonic()
+{
+
+}
+
+void IntegrationPluginWattsonic::discoverThings(ThingDiscoveryInfo *info)
+{
+
+ if (info->thingClassId() == inverterThingClassId) {
+
+ WattsonicDiscovery *discovery = new WattsonicDiscovery(hardwareManager()->modbusRtuResource(), info);
+ connect(discovery, &WattsonicDiscovery::discoveryFinished, info, [=](bool modbusRtuMasterAvailable){
+ if (!modbusRtuMasterAvailable) {
+ info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("No suitable Modbus RTU connection available. Please set up a Modbus RTU master with a baudrate of 9600, 8 data bits, 1 stop bit and no parity."));
+ return;
+ }
+
+ foreach (const WattsonicDiscovery::Result &result, discovery->discoveryResults()) {
+
+ QString name = "Wattsonic hybrid inverter";
+ ThingDescriptor descriptor(inverterThingClassId, name, result.serialNumber);
+ qCDebug(dcWattsonic()) << "Discovered:" << descriptor.title() << descriptor.description();
+
+ ParamList params {
+ {inverterThingModbusMasterUuidParamTypeId, result.modbusRtuMasterId},
+ {inverterThingSlaveAddressParamTypeId, result.slaveId}
+ };
+ descriptor.setParams(params);
+
+ // Check if we already have set up this device
+ Thing *existingThing = myThings().findByParams(params);
+ if (existingThing) {
+ qCDebug(dcWattsonic()) << "This inverter already exists in the system:" << result.serialNumber;
+ descriptor.setThingId(existingThing->id());
+ }
+
+ info->addThingDescriptor(descriptor);
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+ });
+ discovery->startDiscovery();
+ }
+}
+
+void IntegrationPluginWattsonic::setupThing(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+ qCDebug(dcWattsonic()) << "Setup" << thing << thing->params();
+
+ if (info->thing()->thingClassId() == inverterThingClassId) {
+ if (m_connections.contains(thing)) {
+ qCDebug(dcWattsonic()) << "Reconfiguring existing thing" << thing->name();
+ m_connections.take(thing)->deleteLater();
+ }
+
+ setupWattsonicConnection(info);
+ return;
+ }
+
+ if (info->thing()->thingClassId() == meterThingClassId) {
+ info->finish(Thing::ThingErrorNoError);
+ return;
+ }
+
+ if (info->thing()->thingClassId() == batteryThingClassId) {
+ info->finish(Thing::ThingErrorNoError);
+ return;
+ }
+
+}
+
+void IntegrationPluginWattsonic::postSetupThing(Thing *thing)
+{
+ if (thing->thingClassId() == inverterThingClassId) {
+ Things meters = myThings().filterByParentId(thing->id()).filterByThingClassId(meterThingClassId);
+ if (meters.isEmpty()) {
+ qCInfo(dcWattsonic()) << "No energy meter set up yet. Creating thing...";
+ ThingDescriptor descriptor(meterThingClassId, "Wattsonic energy meter", QString(), thing->id());
+ emit autoThingsAppeared({descriptor});
+ }
+ Things batteries = myThings().filterByParentId(thing->id()).filterByThingClassId(batteryThingClassId);
+ if (batteries.isEmpty()) {
+ qCInfo(dcWattsonic()) << "No battery set up yet. Creating thing...";
+ ThingDescriptor descriptor(batteryThingClassId, "Wattsonic energy storage", QString(), thing->id());
+ emit autoThingsAppeared({descriptor});
+ }
+ }
+
+
+ if (!m_pluginTimer) {
+ qCDebug(dcWattsonic()) << "Starting plugin timer...";
+ m_pluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(2);
+ connect(m_pluginTimer, &PluginTimer::timeout, this, [this] {
+ foreach(WattsonicModbusRtuConnection *connection, m_connections) {
+ qCDebug(dcWattsonic()) << "Updating connection" << connection->modbusRtuMaster()->serialPort() << connection->slaveId();
+ connection->update();
+ }
+ });
+
+ m_pluginTimer->start();
+ }
+}
+
+void IntegrationPluginWattsonic::thingRemoved(Thing *thing)
+{
+ if (thing->thingClassId() == inverterThingClassId && m_connections.contains(thing)) {
+ delete m_connections.take(thing);
+ }
+
+ if (myThings().isEmpty() && m_pluginTimer) {
+ hardwareManager()->pluginTimerManager()->unregisterTimer(m_pluginTimer);
+ m_pluginTimer = nullptr;
+ }
+}
+
+void IntegrationPluginWattsonic::setupWattsonicConnection(ThingSetupInfo *info)
+{
+ Thing *thing = info->thing();
+
+ uint slaveId = thing->paramValue(inverterThingSlaveAddressParamTypeId).toUInt();
+ if (slaveId > 247 || slaveId == 0) {
+ qCWarning(dcWattsonic()) << "Setup failed, slave ID is not valid" << slaveId;
+ info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus address not valid. It must be a value between 1 and 247."));
+ return;
+ }
+
+ QUuid uuid = thing->paramValue(inverterThingModbusMasterUuidParamTypeId).toUuid();
+ if (!hardwareManager()->modbusRtuResource()->hasModbusRtuMaster(uuid)) {
+ qCWarning(dcWattsonic()) << "Setup failed, hardware manager not available";
+ info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("The Modbus RTU resource is not available."));
+ return;
+ }
+
+ WattsonicModbusRtuConnection *connection = new WattsonicModbusRtuConnection(hardwareManager()->modbusRtuResource()->getModbusRtuMaster(uuid), slaveId, this);
+ connect(info, &ThingSetupInfo::aborted, connection, &ModbusRtuMaster::deleteLater);
+
+ m_connections.insert(thing, connection);
+ connect(info, &ThingSetupInfo::aborted, this, [=](){
+ m_connections.take(info->thing())->deleteLater();
+ });
+
+ connect(connection, &WattsonicModbusRtuConnection::reachableChanged, thing, [connection, thing, this](bool reachable){
+ qCDebug(dcWattsonic()) << "Reachable state changed" << reachable;
+ if (reachable) {
+ connection->initialize();
+ } else {
+ thing->setStateValue("connected", false);
+ foreach (Thing *child, myThings().filterByParentId(thing->id())) {
+ child->setStateValue("connected", true);
+ }
+ }
+ });
+
+ connect(connection, &WattsonicModbusRtuConnection::initializationFinished, info, [=](bool success){
+ qCDebug(dcWattsonic()) << "Initialisation finished" << success;
+ if (info->isInitialSetup() && !success) {
+ m_connections.take(info->thing())->deleteLater();
+ info->finish(Thing::ThingErrorHardwareNotAvailable);
+ return;
+ }
+
+ info->finish(Thing::ThingErrorNoError);
+
+ if (success) {
+ qCDebug(dcWattsonic) << "Firmware version:" << connection->firmwareVersion();
+// info->thing()->setStateValue(inverterCurrentVersionStateTypeId, compact20Connection->firmwareVersion());
+ }
+ });
+
+ connect(connection, &WattsonicModbusRtuConnection::reachableChanged, thing, [=](bool reachable){
+ thing->setStateValue(inverterConnectedStateTypeId, reachable);
+ foreach (Thing *child, myThings().filterByParentId(thing->id())) {
+ child->setStateValue("connected", reachable);
+ }
+ });
+
+
+ connect(connection, &WattsonicModbusRtuConnection::updateFinished, thing, [this, connection, thing](){
+ qCDebug(dcWattsonic()) << "Update finished:" << thing->name() << connection;
+
+ Thing *inverter = thing;
+
+ inverter->setStateValue(inverterCurrentPowerStateTypeId, connection->pAC() * -1.0);
+ inverter->setStateValue(inverterTotalEnergyProducedStateTypeId, connection->totalPVGenerationFromInstallation() * 0.1);
+ qCInfo(dcWattsonic()) << "Updating inverter:" << inverter->stateValue(inverterCurrentPowerStateTypeId).toDouble() << "W" << inverter->stateValue(inverterTotalEnergyProducedStateTypeId).toDouble() << "kWh";
+
+ Things meters = myThings().filterByParentId(thing->id()).filterByThingClassId(meterThingClassId);
+ if (!meters.isEmpty()) {
+ Thing *meter = meters.first();
+ meter->setStateValue(meterCurrentPowerStateTypeId, connection->totalPowerOnMeter() * -1.0);
+ meter->setStateValue(meterTotalEnergyConsumedStateTypeId, connection->totalEnergyPurchasedFromGrid() / 10.0);
+ meter->setStateValue(meterTotalEnergyProducedStateTypeId, connection->totalEnergyInjectedToGrid() / 10.0);
+ meter->setStateValue(meterCurrentPowerPhaseAStateTypeId, connection->phaseAPower() * -1.0);
+ meter->setStateValue(meterCurrentPowerPhaseBStateTypeId, connection->phaseBPower() * -1.0);
+ meter->setStateValue(meterCurrentPowerPhaseCStateTypeId, connection->phaseCPower() * -1.0);
+ meter->setStateValue(meterVoltagePhaseAStateTypeId, connection->gridPhaseAVoltage() / 10.0);
+ meter->setStateValue(meterVoltagePhaseBStateTypeId, connection->gridPhaseBVoltage() / 10.0);
+ meter->setStateValue(meterVoltagePhaseCStateTypeId, connection->gridPhaseCVoltage() / 10.0);
+ // The phase current registers don't seem to contain proper values. Calculating ourselves instead
+// meter->setStateValue(meterCurrentPhaseAStateTypeId, connection->gridPhaseACurrent() / 10.0);
+// meter->setStateValue(meterCurrentPhaseBStateTypeId, connection->gridPhaseBCurrent() / 10.0);
+// meter->setStateValue(meterCurrentPhaseCStateTypeId, connection->gridPhaseCCurrent() / 10.0);
+ meter->setStateValue(meterCurrentPhaseAStateTypeId, (connection->phaseAPower() * -1.0) / (connection->gridPhaseAVoltage() / 10.0));
+ meter->setStateValue(meterCurrentPhaseBStateTypeId, (connection->phaseBPower() * -1.0) / (connection->gridPhaseBVoltage() / 10.0));
+ meter->setStateValue(meterCurrentPhaseCStateTypeId, (connection->phaseCPower() * -1.0) / (connection->gridPhaseCVoltage() / 10.0));
+ qCInfo(dcWattsonic()) << "Updating meter:" << meter->stateValue(meterCurrentPowerStateTypeId).toDouble() << "W" << meter->stateValue(meterTotalEnergyProducedStateTypeId).toDouble() << "kWh";
+ }
+ Things batteries = myThings().filterByParentId(thing->id()).filterByThingClassId(batteryThingClassId);
+ if (!batteries.isEmpty() && connection->SOC() > 0) {
+ Thing *battery = batteries.first();
+ QHash map {
+ {WattsonicModbusRtuConnection::BatteryModeDischarge, "discharging"},
+ {WattsonicModbusRtuConnection::BatteryModeCharge, "charging"}
+ };
+ battery->setStateValue(batteryChargingStateStateTypeId, map.value(connection->batteryMode()));
+ battery->setStateValue(batteryCurrentPowerStateTypeId, connection->batteryPower() * -1.0);
+ battery->setStateValue(batteryBatteryLevelStateTypeId, connection->SOC() / 100.0);
+ battery->setStateValue(batteryBatteryCriticalStateTypeId, connection->SOC() < 500);
+ }
+
+ });
+
+
+}
diff --git a/wattsonic/integrationpluginwattsonic.h b/wattsonic/integrationpluginwattsonic.h
new file mode 100644
index 0000000..7ab7271
--- /dev/null
+++ b/wattsonic/integrationpluginwattsonic.h
@@ -0,0 +1,66 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 INTEGRATIONPLUGINWATTSONIC_H
+#define INTEGRATIONPLUGINWATTSONIC_H
+
+#include
+#include
+#include
+
+#include "extern-plugininfo.h"
+
+#include "wattsonicmodbusrtuconnection.h"
+
+class IntegrationPluginWattsonic: public IntegrationPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginwattsonic.json")
+ Q_INTERFACES(IntegrationPlugin)
+
+public:
+ explicit IntegrationPluginWattsonic();
+
+ void discoverThings(ThingDiscoveryInfo *info) override;
+ void setupThing(ThingSetupInfo *info) override;
+ void postSetupThing(Thing *thing) override;
+ void thingRemoved(Thing *thing) override;
+
+private:
+ void setupWattsonicConnection(ThingSetupInfo *info);
+
+ PluginTimer *m_pluginTimer = nullptr;
+ QHash m_connections;
+};
+
+#endif // INTEGRATIONPLUGINWATTSONIC_H
+
+
diff --git a/wattsonic/integrationpluginwattsonic.json b/wattsonic/integrationpluginwattsonic.json
new file mode 100644
index 0000000..ee3a1d5
--- /dev/null
+++ b/wattsonic/integrationpluginwattsonic.json
@@ -0,0 +1,246 @@
+{
+ "name": "Wattsonic",
+ "displayName": "Wattsonic",
+ "id": "d0eaf684-001e-4b1c-9e37-122955958de3",
+ "vendors": [
+ {
+ "name": "wattsonic",
+ "displayName": "Wattsonic",
+ "id": "c335f9bc-3bc9-46bf-8c50-800cd93e827a",
+ "thingClasses": [
+ {
+ "name": "inverter",
+ "displayName": "Wattsonic hybrid inverter",
+ "id": "688bef8d-2ba8-4eb3-b30e-16193eba02fb",
+ "createMethods": ["discovery", "user"],
+ "interfaces": ["solarinverter", "connectable"],
+ "paramTypes": [
+ {
+ "id": "55a7d9ed-5f4f-41a2-8dc1-c6a5a79512d2",
+ "name": "slaveAddress",
+ "displayName": "Modbus slave address",
+ "type": "uint",
+ "defaultValue": 1
+ },
+ {
+ "id": "4f1238b5-07e0-4516-b84a-71670141ef81",
+ "name": "modbusMasterUuid",
+ "displayName": "Modbus RTU master",
+ "type": "QUuid",
+ "defaultValue": ""
+ }
+ ],
+ "stateTypes": [
+ {
+ "id": "c0f77c00-b82a-478e-826b-bc3204d66100",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "2cbb25e6-c1bd-4216-b354-6ad6fa957e29",
+ "name": "totalEnergyProduced",
+ "displayName": "Total energy produced",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ },
+ {
+ "id": "3107958e-07a2-4ebd-83a1-96fb5998cfb9",
+ "name": "currentPower",
+ "displayName": "Current power consumption",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ }
+ ]
+ },
+ {
+ "id": "e27a9590-0d13-4d7f-b66b-946dad86b8c8",
+ "name": "meter",
+ "displayName": "Wattsonic energy meter",
+ "createMethods": ["auto"],
+ "interfaces": ["energymeter", "connectable"],
+ "stateTypes": [
+ {
+ "id": "d3bf44be-00d0-4119-ad16-45648bc1532a",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "d5ce5ba0-bc1d-4af3-9afa-cbfb6c0d01ec",
+ "name": "currentPower",
+ "displayName": "Current power consumption",
+ "type": "double",
+ "unit": "Watt",
+ "cached": false,
+ "defaultValue": 0
+ },
+ {
+ "id": "60d486a6-337d-4977-9624-78d99657aea9",
+ "name": "totalEnergyConsumed",
+ "displayName": "Total consumed energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ },
+ {
+ "id": "3fd2e110-8851-4530-bb53-e8e5b20cd4cb",
+ "name": "totalEnergyProduced",
+ "displayName": "Total returned energy",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ },
+ {
+ "id": "1def2f00-8d1c-4d22-b662-a31a552c8a82",
+ "name": "currentPowerPhaseA",
+ "displayName": "Power consumption phase A",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "404549a1-de1e-42e2-8d5d-f581b8674b7d",
+ "name": "currentPowerPhaseB",
+ "displayName": "Power consumption phase B",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "6fccc09c-7c89-45b7-908e-9cbe5bda1c2c",
+ "name": "currentPowerPhaseC",
+ "displayName": "Power consumption phase C",
+ "type": "double",
+ "unit": "Watt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "12798e83-3093-411e-ab8e-5955956717da",
+ "name": "voltagePhaseA",
+ "displayName": "Voltage phase A",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "eef67976-2c39-4d79-8b6d-053e369346a8",
+ "name": "voltagePhaseB",
+ "displayName": "Voltage phase B",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "1e237634-d23c-4c20-b710-b3586bbb988f",
+ "name": "voltagePhaseC",
+ "displayName": "Voltage phase C",
+ "type": "double",
+ "unit": "Volt",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "80c12358-c2d9-4c1b-9150-b7dc3da8a7ae",
+ "name": "currentPhaseA",
+ "displayName": "Current phase A",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "d0914688-59ae-4bd9-97ee-1668e7948ab0",
+ "name": "currentPhaseB",
+ "displayName": "Current phase B",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0,
+ "cached": false
+ },
+ {
+ "id": "ecb8072f-dcb0-4e5e-8cc9-95947d3b7185",
+ "name": "currentPhaseC",
+ "displayName": "Current phase C",
+ "type": "double",
+ "unit": "Ampere",
+ "defaultValue": 0,
+ "cached": false
+ }
+ ]
+ },
+ {
+ "id": "04c4e5fd-7b64-444f-8905-75651833224e",
+ "name": "battery",
+ "displayName": "Wattsonic energy storage",
+ "createMethods": ["auto"],
+ "interfaces": ["energystorage", "connectable"],
+ "stateTypes": [
+ {
+ "id": "a8673429-043e-4149-8775-c89cab0b63ca",
+ "name": "connected",
+ "displayName": "Connected",
+ "type": "bool",
+ "defaultValue": false,
+ "cached": false
+ },
+ {
+ "id": "76c70da1-484d-4bb2-8070-22e5e141cadd",
+ "name": "batteryCritical",
+ "displayName": "Battery critical",
+ "type": "bool",
+ "defaultValue": false
+ },
+ {
+ "id": "113cac91-24cf-45eb-93bf-e81145ba6c4e",
+ "name": "batteryLevel",
+ "displayName": "Battery level",
+ "type": "int",
+ "minValue": 0,
+ "maxValue": 100,
+ "unit": "Percentage",
+ "defaultValue": 0
+ },
+ {
+ "id": "dfaa3890-e3d2-4262-be3d-ece18b520ed9",
+ "name": "chargingState",
+ "displayName": "Charging state",
+ "type": "QString",
+ "possibleValues": ["idle", "charging", "discharging"],
+ "defaultValue": "idle"
+ },
+ {
+ "id": "608da87c-e45f-471d-975d-936807a94c9a",
+ "name": "currentPower",
+ "displayName": "Current power consumption",
+ "type": "double",
+ "unit": "Watt",
+ "cached": false,
+ "defaultValue": 0
+ },
+ {
+ "id": "5c652ecd-1302-4796-92fa-672a3d9442c8",
+ "name": "capacity",
+ "displayName": "Capacity",
+ "type": "double",
+ "unit": "KiloWattHour",
+ "defaultValue": 0
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/wattsonic/meta.json b/wattsonic/meta.json
new file mode 100644
index 0000000..3fd2ac8
--- /dev/null
+++ b/wattsonic/meta.json
@@ -0,0 +1,13 @@
+{
+ "title": "Wattsonic",
+ "tagline": "Connect Wattsonic devices to nymea.",
+ "icon": "wattsonic.svg",
+ "stability": "consumer",
+ "offline": true,
+ "technologies": [
+ "network"
+ ],
+ "categories": [
+ "energy"
+ ]
+}
diff --git a/wattsonic/translations/d0eaf684-001e-4b1c-9e37-122955958de3-en_US.ts b/wattsonic/translations/d0eaf684-001e-4b1c-9e37-122955958de3-en_US.ts
new file mode 100644
index 0000000..edb17f7
--- /dev/null
+++ b/wattsonic/translations/d0eaf684-001e-4b1c-9e37-122955958de3-en_US.ts
@@ -0,0 +1,65 @@
+
+
+
+
+ IntegrationPluginWattsonic
+
+
+ The Modbus address not valid. It must be a value between 1 and 247.
+
+
+
+
+ The Modbus RTU resource is not available.
+
+
+
+
+ Wattsonic
+
+
+ Connected
+ The name of the StateType ({c0f77c00-b82a-478e-826b-bc3204d66100}) of ThingClass inverter
+
+
+
+
+ Current power consumption
+ The name of the StateType ({3107958e-07a2-4ebd-83a1-96fb5998cfb9}) of ThingClass inverter
+
+
+
+
+ Modbus RTU master
+ The name of the ParamType (ThingClass: inverter, Type: thing, ID: {4f1238b5-07e0-4516-b84a-71670141ef81})
+
+
+
+
+ Modbus slave address
+ The name of the ParamType (ThingClass: inverter, Type: thing, ID: {55a7d9ed-5f4f-41a2-8dc1-c6a5a79512d2})
+
+
+
+
+ Total energy produced
+ The name of the StateType ({2cbb25e6-c1bd-4216-b354-6ad6fa957e29}) of ThingClass inverter
+
+
+
+
+
+ Wattsonic
+ The name of the vendor ({c335f9bc-3bc9-46bf-8c50-800cd93e827a})
+----------
+The name of the plugin Wattsonic ({d0eaf684-001e-4b1c-9e37-122955958de3})
+
+
+
+
+ Wattsonic hybrid inverter
+ The name of the ThingClass ({688bef8d-2ba8-4eb3-b30e-16193eba02fb})
+
+
+
+
diff --git a/wattsonic/wattsonic-registers.json b/wattsonic/wattsonic-registers.json
new file mode 100644
index 0000000..383cfba
--- /dev/null
+++ b/wattsonic/wattsonic-registers.json
@@ -0,0 +1,362 @@
+{
+ "className": "Wattsonic",
+ "protocol": "RTU",
+ "endianness": "BigEndian",
+ "errorLimitUntilNotReachable": 20,
+ "checkReachableRegister": "serialNumber",
+ "enums": [
+ {
+ "name": "InverterStatus",
+ "values": [
+ {
+ "key": "Wait",
+ "value": 0
+ },
+ {
+ "key": "Check",
+ "value": 1
+ },
+ {
+ "key": "OnGrid",
+ "value": 2
+ },
+ {
+ "key": "Fault",
+ "value": 3
+ },
+ {
+ "key": "Flash",
+ "value": 4
+ },
+ {
+ "key": "OffGrid",
+ "value": 5
+ }
+ ]
+ },
+ {
+ "name": "BatteryMode",
+ "values": [
+ {
+ "key": "Discharge",
+ "value": 0
+ },
+ {
+ "key": "Charge",
+ "value": 1
+ }
+ ]
+ }
+ ],
+ "blocks": [
+ ],
+ "registers": [
+ {
+ "id": "serialNumber",
+ "address": 10000,
+ "size": 8,
+ "type": "string",
+ "readSchedule": "init",
+ "registerType": "holdingRegister",
+ "description": "Serial number",
+ "access": "RO"
+ },
+ {
+ "id": "firmwareVersion",
+ "address": 10011,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "init",
+ "registerType": "holdingRegister",
+ "description": "Firmware version",
+ "access": "RO"
+ },
+ {
+ "id": "inverterStatus",
+ "address": 10105,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Inverter status",
+ "enum": "InverterStatus",
+ "defaultValue": "InverterStatusWait",
+ "access": "RO"
+ },
+ {
+ "id": "phaseAPower",
+ "address": 10994,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Phase A power",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "phaseBPower",
+ "address": 10996,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Phase B power",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "phaseCPower",
+ "address": 10998,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Phase C power",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "totalPowerOnMeter",
+ "address": 11000,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total power on meter",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "totalGridInjectionEnergy",
+ "address": 11002,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total grid injection energy on meter",
+ "defaultValue": 0,
+ "unit": "1/100 kWh",
+ "access": "RO"
+ },
+ {
+ "id": "totalPurchasingEnergyFromGrid",
+ "address": 11004,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total purchasing energy from grid on meter",
+ "defaultValue": 0,
+ "unit": "1/100 kWh",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseAVoltage",
+ "address": 11009,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase A Voltage",
+ "defaultValue": 0,
+ "unit": "1/10 V",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseACurrent",
+ "address": 11010,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase A Current",
+ "defaultValue": 0,
+ "unit": "1/10 A",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseBVoltage",
+ "address": 11011,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase B Voltage",
+ "defaultValue": 0,
+ "unit": "1/10 V",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseBCurrent",
+ "address": 11012,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase B Current",
+ "defaultValue": 0,
+ "unit": "1/10 A",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseCVoltage",
+ "address": 11013,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase C Voltage",
+ "defaultValue": 0,
+ "unit": "1/10 V",
+ "access": "RO"
+ },
+ {
+ "id": "gridPhaseCCurrent",
+ "address": 11014,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Grid Phase C Current",
+ "defaultValue": 0,
+ "unit": "1/10 A",
+ "access": "RO"
+ },
+ {
+ "id": "pAC",
+ "address": 11016,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "P_AC",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "totalPVGenerationFromInstallation",
+ "address": 11020,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total PV Generation from installation",
+ "defaultValue": 0,
+ "unit": "1/10 kWh",
+ "access": "RO"
+ },
+ {
+ "id": "pvInputTotalPower",
+ "address": 11028,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "PV Total Input Power",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "totalBackupP",
+ "address": 30230,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total_Backup_P/AC Active Power",
+ "unit": "1/1000 kW",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "batteryMode",
+ "address": 30256,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Battery mode",
+ "enum": "BatteryMode",
+ "defaultValue": "BatteryModeDischarge",
+ "access": "RO"
+ },
+ {
+ "id": "batteryPower",
+ "address": 30258,
+ "size": 2,
+ "type": "int32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Battery power",
+ "defaultValue": 0,
+ "unit": "1/1000 kW",
+ "access": "RO"
+ },
+ {
+ "id": "totalEnergyInjectedToGrid",
+ "address": 31102,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total energy injected to grid",
+ "defaultValue": 0,
+ "unit": "1/10 kWh",
+ "access": "RO"
+ },
+ {
+ "id": "totalEnergyPurchasedFromGrid",
+ "address": 31104,
+ "size": 2,
+ "type": "uint32",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Total energy purchased from grid",
+ "defaultValue": 0,
+ "unit": "1/10 kWh",
+ "access": "RO"
+ },
+ {
+ "id": "batteryStrings",
+ "address": 32001,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "Battery strings",
+ "defaultValue": 0,
+ "access": "RO"
+ },
+ {
+ "id": "SOC",
+ "address": 33000,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "SOC",
+ "defaultValue": 0,
+ "unit": "% * 100",
+ "access": "RO"
+ },
+ {
+ "id": "SOH",
+ "address": 33001,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "registerType": "holdingRegister",
+ "description": "SOH",
+ "defaultValue": 0,
+ "unit": "% * 100",
+ "access": "RO"
+ }
+ ]
+}
diff --git a/wattsonic/wattsonic.pro b/wattsonic/wattsonic.pro
new file mode 100644
index 0000000..9875f8e
--- /dev/null
+++ b/wattsonic/wattsonic.pro
@@ -0,0 +1,15 @@
+include(../plugins.pri)
+
+# Generate modbus connection
+MODBUS_CONNECTIONS += wattsonic-registers.json
+
+#MODBUS_TOOLS_CONFIG += VERBOSE
+include(../modbus.pri)
+
+HEADERS += \
+ wattsonicdiscovery.h \
+ integrationpluginwattsonic.h
+
+SOURCES += \
+ wattsonicdiscovery.cpp \
+ integrationpluginwattsonic.cpp
diff --git a/wattsonic/wattsonicdiscovery.cpp b/wattsonic/wattsonicdiscovery.cpp
new file mode 100644
index 0000000..5e599d1
--- /dev/null
+++ b/wattsonic/wattsonicdiscovery.cpp
@@ -0,0 +1,100 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 "wattsonicdiscovery.h"
+#include "extern-plugininfo.h"
+
+#include
+
+QList slaveIdCandidates = {247};
+
+WattsonicDiscovery::WattsonicDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent):
+ QObject{parent},
+ m_modbusRtuResource(modbusRtuResource)
+{
+
+}
+
+void WattsonicDiscovery::startDiscovery()
+{
+ qCInfo(dcWattsonic()) << "Discovery: Searching for Wattsonic device on modbus RTU...";
+
+ QList candidateMasters;
+ foreach (ModbusRtuMaster *master, m_modbusRtuResource->modbusRtuMasters()) {
+ if (master->baudrate() == 9600 && master->dataBits() == 8 && master->stopBits() == 1 && master->parity() == QSerialPort::NoParity) {
+ candidateMasters.append(master);
+ }
+ }
+
+ if (candidateMasters.isEmpty()) {
+ qCWarning(dcWattsonic()) << "No usable modbus RTU master found.";
+ emit discoveryFinished(false);
+ return;
+ }
+
+ foreach (ModbusRtuMaster *master, candidateMasters) {
+ if (master->connected()) {
+ tryConnect(master, 0);
+ } else {
+ qCWarning(dcWattsonic()) << "Modbus RTU master" << master->modbusUuid().toString() << "is not connected.";
+ }
+ }
+}
+
+QList WattsonicDiscovery::discoveryResults() const
+{
+ return m_discoveryResults;
+}
+
+void WattsonicDiscovery::tryConnect(ModbusRtuMaster *master, quint16 slaveIdIndex)
+{
+ quint8 slaveId = slaveIdCandidates.at(slaveIdIndex);
+ qCDebug(dcWattsonic()) << "Scanning modbus RTU master" << master->modbusUuid() << "Slave ID:" << slaveId;
+
+ ModbusRtuReply *reply = master->readHoldingRegister(slaveId, 10000, 8);
+ connect(reply, &ModbusRtuReply::finished, this, [=](){
+
+ if (reply->error() == ModbusRtuReply::NoError) {
+
+ QString serialNumber = ModbusDataUtils::convertToString(reply->result(), ModbusDataUtils::ByteOrderBigEndian);
+ qCDebug(dcWattsonic()) << "Test reply finished!" << reply->error() << reply->result() << serialNumber;
+
+ Result result {master->modbusUuid(), serialNumber, slaveId};
+ m_discoveryResults.append(result);
+
+ }
+
+ if (slaveIdIndex < slaveIdCandidates.count() - 1) {
+ tryConnect(master, slaveIdIndex+1);
+ } else {
+ emit discoveryFinished(true);
+ }
+ });
+}
diff --git a/wattsonic/wattsonicdiscovery.h b/wattsonic/wattsonicdiscovery.h
new file mode 100644
index 0000000..1001b52
--- /dev/null
+++ b/wattsonic/wattsonicdiscovery.h
@@ -0,0 +1,65 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2023, 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 WATTSONICDISCOVERY_H
+#define WATTSONICDISCOVERY_H
+
+#include
+#include
+
+class WattsonicDiscovery : public QObject
+{
+ Q_OBJECT
+public:
+ explicit WattsonicDiscovery(ModbusRtuHardwareResource *modbusRtuResource, QObject *parent = nullptr);
+
+ struct Result {
+ QUuid modbusRtuMasterId;
+ QString serialNumber;
+ quint16 slaveId;
+ };
+
+ void startDiscovery();
+
+ QList discoveryResults() const;
+
+signals:
+ void discoveryFinished(bool modbusRtuMasterAvailable);
+
+private slots:
+ void tryConnect(ModbusRtuMaster *master, quint16 slaveIdIndex);
+
+private:
+ ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
+
+ QList m_discoveryResults;
+};
+
+#endif // WATTSONICDISCOVERY_H