New plugin: Wattsonic

This commit is contained in:
Michael Zanetti 2023-06-26 22:51:23 +02:00 committed by Simon Stürz
parent eae2cbe0ee
commit 2d9d4aea42
13 changed files with 1210 additions and 0 deletions

9
debian/control vendored
View File

@ -258,6 +258,15 @@ Description: nymea integration plugin for PhoenixConnect wallboxes
This package contains the nymea integration plugin for wallboxes made This package contains the nymea integration plugin for wallboxes made
by PhonenixConnect and rebranded as Wallbe, Compleo and Scapo. 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 Package: nymea-plugin-webasto
Architecture: any Architecture: any
Section: libs Section: libs

View File

@ -0,0 +1,2 @@
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginwattsonic.so
wattsonic/translations/*qm usr/share/nymea/translations/

View File

@ -25,6 +25,7 @@ PLUGIN_DIRS = \
stiebeleltron \ stiebeleltron \
sunspec \ sunspec \
unipi \ unipi \
wattsonic \
webasto \ webasto \
gcc { gcc {

4
wattsonic/README.md Normal file
View File

@ -0,0 +1,4 @@
# Wattsonic
This plugin allows to connect wattsonic hybrid inverters to nymea

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <network/networkdevicediscovery.h>
#include <hardwaremanager.h>
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<WattsonicModbusRtuConnection::BatteryMode, QString> 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);
}
});
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <plugintimer.h>
#include <integrations/integrationplugin.h>
#include <network/networkdevicemonitor.h>
#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<Thing *, WattsonicModbusRtuConnection *> m_connections;
};
#endif // INTEGRATIONPLUGINWATTSONIC_H

View File

@ -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
}
]
}
]
}
]
}

13
wattsonic/meta.json Normal file
View File

@ -0,0 +1,13 @@
{
"title": "Wattsonic",
"tagline": "Connect Wattsonic devices to nymea.",
"icon": "wattsonic.svg",
"stability": "consumer",
"offline": true,
"technologies": [
"network"
],
"categories": [
"energy"
]
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>IntegrationPluginWattsonic</name>
<message>
<location filename="../integrationpluginwattsonic.cpp" line="140"/>
<source>The Modbus address not valid. It must be a value between 1 and 247.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../integrationpluginwattsonic.cpp" line="147"/>
<source>The Modbus RTU resource is not available.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Wattsonic</name>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="33"/>
<source>Connected</source>
<extracomment>The name of the StateType ({c0f77c00-b82a-478e-826b-bc3204d66100}) of ThingClass inverter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="36"/>
<source>Current power consumption</source>
<extracomment>The name of the StateType ({3107958e-07a2-4ebd-83a1-96fb5998cfb9}) of ThingClass inverter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="39"/>
<source>Modbus RTU master</source>
<extracomment>The name of the ParamType (ThingClass: inverter, Type: thing, ID: {4f1238b5-07e0-4516-b84a-71670141ef81})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="42"/>
<source>Modbus slave address</source>
<extracomment>The name of the ParamType (ThingClass: inverter, Type: thing, ID: {55a7d9ed-5f4f-41a2-8dc1-c6a5a79512d2})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="45"/>
<source>Total energy produced</source>
<extracomment>The name of the StateType ({2cbb25e6-c1bd-4216-b354-6ad6fa957e29}) of ThingClass inverter</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="48"/>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="51"/>
<source>Wattsonic</source>
<extracomment>The name of the vendor ({c335f9bc-3bc9-46bf-8c50-800cd93e827a})
----------
The name of the plugin Wattsonic ({d0eaf684-001e-4b1c-9e37-122955958de3})</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../../../build/nymea-plugins-modbus-Desktop-Debug/wattsonic/plugininfo.h" line="54"/>
<source>Wattsonic hybrid inverter</source>
<extracomment>The name of the ThingClass ({688bef8d-2ba8-4eb3-b30e-16193eba02fb})</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -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"
}
]
}

15
wattsonic/wattsonic.pro Normal file
View File

@ -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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <modbusdatautils.h>
QList<int> 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<ModbusRtuMaster*> 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::Result> 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);
}
});
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <hardware/modbus/modbusrtuhardwareresource.h>
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<Result> discoveryResults() const;
signals:
void discoveryFinished(bool modbusRtuMasterAvailable);
private slots:
void tryConnect(ModbusRtuMaster *master, quint16 slaveIdIndex);
private:
ModbusRtuHardwareResource *m_modbusRtuResource = nullptr;
QList<Result> m_discoveryResults;
};
#endif // WATTSONICDISCOVERY_H