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

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 \
sunspec \
unipi \
wattsonic \
webasto \
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