Merge PR #95: SMA: Import plugin from nymea-plugins and add modbus inverters
This commit is contained in:
commit
0ed67cecce
8
debian/control
vendored
8
debian/control
vendored
@ -185,6 +185,14 @@ Description: nymea integration plugin for Schrack wallboxes
|
||||
This package contains the nymea integration plugin for Schrack wallboxes.
|
||||
|
||||
|
||||
Package: nymea-plugin-sma
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: nymea integration plugin for SMA solar inverters and meters
|
||||
This package contains the nymea integration plugin for SMA solar inverters and meters.
|
||||
|
||||
|
||||
Package: nymea-plugin-stiebeleltron
|
||||
Architecture: any
|
||||
Section: libs
|
||||
|
||||
2
debian/nymea-plugin-sma.install.in
vendored
Normal file
2
debian/nymea-plugin-sma.install.in
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginsma.so
|
||||
sma/translations/*qm usr/share/nymea/translations/
|
||||
@ -18,6 +18,7 @@ PLUGIN_DIRS = \
|
||||
mypv \
|
||||
phoenixconnect \
|
||||
schrack \
|
||||
sma \
|
||||
stiebeleltron \
|
||||
sunspec \
|
||||
unipi \
|
||||
|
||||
20
sma/README.md
Normal file
20
sma/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# SMA
|
||||
|
||||
nymea plug-in for SMA solar equipment.
|
||||
|
||||
## Supported Things
|
||||
|
||||
* Sunny WebBox
|
||||
* SMA speedwire Meters
|
||||
* SMA speedwire Inverters
|
||||
* SMA inverters using modbus
|
||||
|
||||
> Note: the SMA battery equipment is still missing due to testing possibilities. Will be added as soon someone can with the appropriate setup.
|
||||
|
||||
## Requirements
|
||||
|
||||
* The package "nymea-plugin-sma" must be installed.
|
||||
* The speedwire port `9522` must not be blocked for UDP packages in the network.
|
||||
|
||||
## More
|
||||
https://www.sma.de/en/
|
||||
731
sma/integrationpluginsma.cpp
Normal file
731
sma/integrationpluginsma.cpp
Normal file
@ -0,0 +1,731 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "integrationpluginsma.h"
|
||||
#include "plugininfo.h"
|
||||
|
||||
#include "sma.h"
|
||||
#include "speedwire/speedwirediscovery.h"
|
||||
#include "sunnywebbox/sunnywebboxdiscovery.h"
|
||||
#include "modbus/smamodbusdiscovery.h"
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
IntegrationPluginSma::IntegrationPluginSma()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
|
||||
{
|
||||
if (info->thingClassId() == sunnyWebBoxThingClassId) {
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSma()) << "Failed to discover network devices. The network device discovery is not available.";
|
||||
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Unable to discover devices in your network."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Starting Sunny WebBox discovery...";
|
||||
SunnyWebBoxDiscovery *webBoxDiscovery = new SunnyWebBoxDiscovery(hardwareManager()->networkManager(), hardwareManager()->networkDeviceDiscovery(), info);
|
||||
connect(webBoxDiscovery, &SunnyWebBoxDiscovery::discoveryFinished, this, [=](){
|
||||
webBoxDiscovery->deleteLater();
|
||||
ThingDescriptors descriptors;
|
||||
foreach (const NetworkDeviceInfo &networkDeviceInfo, webBoxDiscovery->discoveryResults()) {
|
||||
QString title = networkDeviceInfo.hostName() + " (" + networkDeviceInfo.address().toString() + ")";
|
||||
QString description;
|
||||
if (networkDeviceInfo.macAddressManufacturer().isEmpty()) {
|
||||
description = networkDeviceInfo.macAddress();
|
||||
} else {
|
||||
description = networkDeviceInfo.macAddress() + " (" + networkDeviceInfo.macAddressManufacturer() + ")";
|
||||
}
|
||||
|
||||
ThingDescriptor descriptor(sunnyWebBoxThingClassId, title, description);
|
||||
|
||||
// Check for reconfiguration
|
||||
foreach (Thing *existingThing, myThings()) {
|
||||
if (existingThing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() == networkDeviceInfo.macAddress()) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(sunnyWebBoxThingHostParamTypeId, networkDeviceInfo.address().toString());
|
||||
params << Param(sunnyWebBoxThingMacAddressParamTypeId, networkDeviceInfo.macAddress());
|
||||
descriptor.setParams(params);
|
||||
descriptors.append(descriptor);
|
||||
}
|
||||
info->addThingDescriptors(descriptors);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
webBoxDiscovery->startDiscovery();
|
||||
|
||||
} else if (info->thingClassId() == speedwireMeterThingClassId) {
|
||||
SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
|
||||
if (!speedwireDiscovery->initialize()) {
|
||||
qCWarning(dcSma()) << "Could not discovery inverter. The speedwire interface initialization failed.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to discover the network."));
|
||||
return;
|
||||
}
|
||||
|
||||
connect(speedwireDiscovery, &SpeedwireDiscovery::discoveryFinished, this, [=](){
|
||||
qCDebug(dcSma()) << "Speed wire discovery finished.";
|
||||
speedwireDiscovery->deleteLater();
|
||||
|
||||
ThingDescriptors descriptors;
|
||||
foreach (const SpeedwireDiscovery::SpeedwireDiscoveryResult &result, speedwireDiscovery->discoveryResult()) {
|
||||
if (result.deviceType == SpeedwireInterface::DeviceTypeMeter) {
|
||||
if (result.serialNumber == 0)
|
||||
continue;
|
||||
|
||||
ThingDescriptor descriptor(speedwireMeterThingClassId, "SMA Energy Meter", "Serial: " + QString::number(result.serialNumber) + " - " + result.address.toString());
|
||||
// We found an energy meter, let's check if we already added this one
|
||||
foreach (Thing *existingThing, myThings()) {
|
||||
if (existingThing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(speedwireMeterThingHostParamTypeId, result.address.toString());
|
||||
params << Param(speedwireMeterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(speedwireMeterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
params << Param(speedwireMeterThingModelIdParamTypeId, result.modelId);
|
||||
descriptor.setParams(params);
|
||||
descriptors.append(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
info->addThingDescriptors(descriptors);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
speedwireDiscovery->startDiscovery();
|
||||
} else if (info->thingClassId() == speedwireInverterThingClassId) {
|
||||
SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info);
|
||||
if (!speedwireDiscovery->initialize()) {
|
||||
qCWarning(dcSma()) << "Could not discovery inverter. The speedwire interface initialization failed.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Unable to discover the network."));
|
||||
return;
|
||||
}
|
||||
|
||||
connect(speedwireDiscovery, &SpeedwireDiscovery::discoveryFinished, this, [=](){
|
||||
qCDebug(dcSma()) << "Speed wire discovery finished.";
|
||||
speedwireDiscovery->deleteLater();
|
||||
|
||||
ThingDescriptors descriptors;
|
||||
foreach (const SpeedwireDiscovery::SpeedwireDiscoveryResult &result, speedwireDiscovery->discoveryResult()) {
|
||||
if (result.deviceType == SpeedwireInterface::DeviceTypeInverter) {
|
||||
if (result.serialNumber == 0)
|
||||
continue;
|
||||
|
||||
ThingDescriptor descriptor(speedwireInverterThingClassId, Sma::getModelName(result.modelId), "Serial: " + QString::number(result.serialNumber) + " - " + result.address.toString());
|
||||
// We found an energy meter, let's check if we already added this one
|
||||
foreach (Thing *existingThing, myThings()) {
|
||||
if (existingThing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt() == result.serialNumber) {
|
||||
descriptor.setThingId(existingThing->id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(speedwireInverterThingHostParamTypeId, result.address.toString());
|
||||
params << Param(speedwireInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(speedwireInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
params << Param(speedwireInverterThingModelIdParamTypeId, result.modelId);
|
||||
descriptor.setParams(params);
|
||||
descriptors.append(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
info->addThingDescriptors(descriptors);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
speedwireDiscovery->startDiscovery();
|
||||
} else if (info->thingClassId() == modbusInverterThingClassId) {
|
||||
if (!hardwareManager()->networkDeviceDiscovery()->available()) {
|
||||
qCWarning(dcSma()) << "The network discovery is not available on this platform.";
|
||||
info->finish(Thing::ThingErrorUnsupportedFeature, QT_TR_NOOP("The network device discovery is not available."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a discovery with the info as parent for auto deleting the object once the discovery info is done
|
||||
SmaModbusDiscovery *discovery = new SmaModbusDiscovery(hardwareManager()->networkDeviceDiscovery(), 502, 3, info);
|
||||
connect(discovery, &SmaModbusDiscovery::discoveryFinished, info, [=](){
|
||||
foreach (const SmaModbusDiscovery::SmaModbusDiscoveryResult &result, discovery->discoveryResults()) {
|
||||
|
||||
ThingDescriptor descriptor(modbusInverterThingClassId, "SMA inverter " + result.productName, QT_TR_NOOP("Serial: ") + result.serialNumber + " (" + result.networkDeviceInfo.address().toString() + ")");
|
||||
qCDebug(dcSma()) << "Discovered:" << descriptor.title() << descriptor.description();
|
||||
|
||||
// Note: use the serial and not the mac address as identifier because more than one inverter might be behind a network device
|
||||
Things existingThings = myThings().filterByParam(modbusInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
if (existingThings.count() == 1) {
|
||||
qCDebug(dcSma()) << "This SMA inverter already exists in the system:" << result.serialNumber;
|
||||
descriptor.setThingId(existingThings.first()->id());
|
||||
}
|
||||
|
||||
ParamList params;
|
||||
params << Param(modbusInverterThingMacAddressParamTypeId, result.networkDeviceInfo.macAddress());
|
||||
params << Param(modbusInverterThingPortParamTypeId, result.port);
|
||||
params << Param(modbusInverterThingSlaveIdParamTypeId, result.modbusAddress);
|
||||
params << Param(modbusInverterThingSerialNumberParamTypeId, result.serialNumber);
|
||||
descriptor.setParams(params);
|
||||
info->addThingDescriptor(descriptor);
|
||||
}
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
});
|
||||
|
||||
// Start the discovery process
|
||||
discovery->startDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::startPairing(ThingPairingInfo *info)
|
||||
{
|
||||
info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the password of your inverter. If no password has been explicitly set, leave it empty to use the default password for SMA inverters."));
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
|
||||
{
|
||||
Q_UNUSED(username)
|
||||
|
||||
if (info->thingClassId() == speedwireInverterThingClassId) {
|
||||
// On speedwire the password length has a maximum of 12 characters
|
||||
if (secret.count() > 12) {
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The password can not be longer than 12 characters."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Init with the default password
|
||||
QString password = "0000";
|
||||
if (!secret.isEmpty()) {
|
||||
qCDebug(dcSma()) << "Pairing: Using password" << secret;
|
||||
password = secret;
|
||||
} else {
|
||||
qCDebug(dcSma()) << "Pairing: The given password is empty. Using default password" << password;
|
||||
}
|
||||
|
||||
// Just store details, we'll test the login in setupDevice
|
||||
pluginStorage()->beginGroup(info->thingId().toString());
|
||||
pluginStorage()->setValue("password", password);
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
qCDebug(dcSma()) << "Setup thing" << thing << thing->params();
|
||||
|
||||
if (thing->thingClassId() == sunnyWebBoxThingClassId) {
|
||||
// Check if a Sunny WebBox is already added with this mac address
|
||||
foreach (SunnyWebBox *sunnyWebBox, m_sunnyWebBoxes.values()) {
|
||||
if (sunnyWebBox->macAddress() == thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString()){
|
||||
qCWarning(dcSma()) << "Thing with mac address" << thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString() << " already added!";
|
||||
info->finish(Thing::ThingErrorThingInUse);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_sunnyWebBoxes.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Setup after reconfiguration, cleaning up...";
|
||||
m_sunnyWebBoxes.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
SunnyWebBox *sunnyWebBox = new SunnyWebBox(hardwareManager()->networkManager(), QHostAddress(thing->paramValue(sunnyWebBoxThingHostParamTypeId).toString()), this);
|
||||
sunnyWebBox->setMacAddress(thing->paramValue(sunnyWebBoxThingMacAddressParamTypeId).toString());
|
||||
|
||||
connect(info, &ThingSetupInfo::aborted, sunnyWebBox, &SunnyWebBox::deleteLater);
|
||||
connect(sunnyWebBox, &SunnyWebBox::destroyed, this, [thing, this] { m_sunnyWebBoxes.remove(thing);});
|
||||
|
||||
QString requestId = sunnyWebBox->getPlantOverview();
|
||||
connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, info, [=] (const QString &messageId, SunnyWebBox::Overview overview) {
|
||||
qCDebug(dcSma()) << "Received plant overview" << messageId << "Finish setup";
|
||||
Q_UNUSED(overview)
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
connect(sunnyWebBox, &SunnyWebBox::connectedChanged, this, &IntegrationPluginSma::onConnectedChanged);
|
||||
connect(sunnyWebBox, &SunnyWebBox::plantOverviewReceived, this, &IntegrationPluginSma::onPlantOverviewReceived);
|
||||
m_sunnyWebBoxes.insert(info->thing(), sunnyWebBox);
|
||||
});
|
||||
|
||||
} else if (thing->thingClassId() == speedwireMeterThingClassId) {
|
||||
|
||||
QHostAddress address = QHostAddress(thing->paramValue(speedwireMeterThingHostParamTypeId).toString());
|
||||
quint32 serialNumber = static_cast<quint32>(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt());
|
||||
quint16 modelId = static_cast<quint16>(thing->paramValue(speedwireMeterThingModelIdParamTypeId).toUInt());
|
||||
|
||||
if (m_speedwireMeters.contains(thing)) {
|
||||
m_speedwireMeters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
SpeedwireMeter *meter = new SpeedwireMeter(address, modelId, serialNumber, this);
|
||||
if (!meter->initialize()) {
|
||||
qCWarning(dcSma()) << "Setup failed. Could not initialize meter interface.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(meter, &SpeedwireMeter::reachableChanged, thing, [=](bool reachable){
|
||||
thing->setStateValue(speedwireMeterConnectedStateTypeId, reachable);
|
||||
});
|
||||
|
||||
connect(meter, &SpeedwireMeter::valuesUpdated, thing, [=](){
|
||||
thing->setStateValue(speedwireMeterConnectedStateTypeId, true);
|
||||
thing->setStateValue(speedwireMeterCurrentPowerStateTypeId, meter->currentPower());
|
||||
thing->setStateValue(speedwireMeterCurrentPowerPhaseAStateTypeId, meter->currentPowerPhaseA());
|
||||
thing->setStateValue(speedwireMeterCurrentPowerPhaseBStateTypeId, meter->currentPowerPhaseB());
|
||||
thing->setStateValue(speedwireMeterCurrentPowerPhaseCStateTypeId, meter->currentPowerPhaseC());
|
||||
thing->setStateValue(speedwireMeterVoltagePhaseAStateTypeId, meter->voltagePhaseA());
|
||||
thing->setStateValue(speedwireMeterVoltagePhaseBStateTypeId, meter->voltagePhaseB());
|
||||
thing->setStateValue(speedwireMeterVoltagePhaseCStateTypeId, meter->voltagePhaseC());
|
||||
thing->setStateValue(speedwireMeterTotalEnergyConsumedStateTypeId, meter->totalEnergyConsumed());
|
||||
thing->setStateValue(speedwireMeterTotalEnergyProducedStateTypeId, meter->totalEnergyProduced());
|
||||
thing->setStateValue(speedwireMeterEnergyConsumedPhaseAStateTypeId, meter->energyConsumedPhaseA());
|
||||
thing->setStateValue(speedwireMeterEnergyConsumedPhaseBStateTypeId, meter->energyConsumedPhaseB());
|
||||
thing->setStateValue(speedwireMeterEnergyConsumedPhaseCStateTypeId, meter->energyConsumedPhaseC());
|
||||
thing->setStateValue(speedwireMeterEnergyProducedPhaseAStateTypeId, meter->energyProducedPhaseA());
|
||||
thing->setStateValue(speedwireMeterEnergyProducedPhaseBStateTypeId, meter->energyProducedPhaseB());
|
||||
thing->setStateValue(speedwireMeterEnergyProducedPhaseCStateTypeId, meter->energyProducedPhaseC());
|
||||
thing->setStateValue(speedwireMeterCurrentPhaseAStateTypeId, meter->amperePhaseA());
|
||||
thing->setStateValue(speedwireMeterCurrentPhaseBStateTypeId, meter->amperePhaseB());
|
||||
thing->setStateValue(speedwireMeterCurrentPhaseCStateTypeId, meter->amperePhaseC());
|
||||
thing->setStateValue(speedwireMeterFirmwareVersionStateTypeId, meter->softwareVersion());
|
||||
});
|
||||
|
||||
m_speedwireMeters.insert(thing, meter);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
} else if (thing->thingClassId() == speedwireInverterThingClassId) {
|
||||
|
||||
QHostAddress address = QHostAddress(thing->paramValue(speedwireInverterThingHostParamTypeId).toString());
|
||||
quint32 serialNumber = static_cast<quint32>(thing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt());
|
||||
quint16 modelId = static_cast<quint16>(thing->paramValue(speedwireInverterThingModelIdParamTypeId).toUInt());
|
||||
|
||||
if (m_speedwireInverters.contains(thing)) {
|
||||
m_speedwireInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
SpeedwireInverter *inverter = new SpeedwireInverter(address, modelId, serialNumber, this);
|
||||
if (!inverter->initialize()) {
|
||||
qCWarning(dcSma()) << "Setup failed. Could not initialize inverter interface.";
|
||||
info->finish(Thing::ThingErrorHardwareFailure);
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Inverter: Interface initialized successfully.";
|
||||
|
||||
QString password;
|
||||
pluginStorage()->beginGroup(info->thing()->id().toString());
|
||||
password = pluginStorage()->value("password", "0000").toString();
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
// Connection exists only as long info exists
|
||||
connect(inverter, &SpeedwireInverter::loginFinished, info, [=](bool success){
|
||||
if (!success) {
|
||||
qCWarning(dcSma()) << "Failed to set up inverter. Wrong password.";
|
||||
|
||||
// Remove invalid password from settings
|
||||
pluginStorage()->beginGroup(info->thing()->id().toString());
|
||||
pluginStorage()->remove("");
|
||||
pluginStorage()->endGroup();
|
||||
|
||||
delete inverter;
|
||||
info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in with the given password. Please try again."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Inverter set up successfully.";
|
||||
m_speedwireInverters.insert(thing, inverter);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
// Note: the data is already refreshing here
|
||||
});
|
||||
|
||||
// Make sure an aborted setup will clean up the object
|
||||
connect(info, &ThingSetupInfo::aborted, inverter, &SpeedwireInverter::deleteLater);
|
||||
|
||||
// Runtime connections
|
||||
connect(inverter, &SpeedwireInverter::reachableChanged, thing, [=](bool reachable){
|
||||
thing->setStateValue(speedwireInverterConnectedStateTypeId, reachable);
|
||||
});
|
||||
|
||||
connect(inverter, &SpeedwireInverter::valuesUpdated, thing, [=](){
|
||||
thing->setStateValue(speedwireInverterTotalEnergyProducedStateTypeId, inverter->totalEnergyProduced());
|
||||
thing->setStateValue(speedwireInverterEnergyProducedTodayStateTypeId, inverter->todayEnergyProduced());
|
||||
thing->setStateValue(speedwireInverterCurrentPowerStateTypeId, -inverter->totalAcPower());
|
||||
thing->setStateValue(speedwireInverterFrequencyStateTypeId, inverter->gridFrequency());
|
||||
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseAStateTypeId, inverter->voltageAcPhase1());
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseBStateTypeId, inverter->voltageAcPhase2());
|
||||
thing->setStateValue(speedwireInverterVoltagePhaseCStateTypeId, inverter->voltageAcPhase3());
|
||||
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseAStateTypeId, inverter->currentAcPhase1());
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseBStateTypeId, inverter->currentAcPhase2());
|
||||
thing->setStateValue(speedwireInverterCurrentPhaseCStateTypeId, inverter->currentAcPhase3());
|
||||
|
||||
thing->setStateValue(speedwireInverterCurrentPowerMpp1StateTypeId, inverter->powerDcMpp1());
|
||||
thing->setStateValue(speedwireInverterCurrentPowerMpp2StateTypeId, inverter->powerDcMpp2());
|
||||
});
|
||||
|
||||
qCDebug(dcSma()) << "Inverter: Start connecting using password" << password;
|
||||
inverter->startConnecting(password);
|
||||
|
||||
} else if (thing->thingClassId() == modbusInverterThingClassId) {
|
||||
|
||||
// Handle reconfigure
|
||||
if (m_modbusInverters.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Reconfiguring existing thing" << thing->name();
|
||||
m_modbusInverters.take(thing)->deleteLater();
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
}
|
||||
|
||||
MacAddress macAddress = MacAddress(thing->paramValue(modbusInverterThingMacAddressParamTypeId).toString());
|
||||
if (!macAddress.isValid()) {
|
||||
qCWarning(dcSma()) << "The configured mac address is not valid" << thing->params();
|
||||
info->finish(Thing::ThingErrorInvalidParameter, QT_TR_NOOP("The MAC address is not known. Please reconfigure the thing."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the monitor
|
||||
NetworkDeviceMonitor *monitor = hardwareManager()->networkDeviceDiscovery()->registerMonitor(macAddress);
|
||||
m_monitors.insert(thing, monitor);
|
||||
|
||||
QHostAddress address = monitor->networkDeviceInfo().address();
|
||||
if (address.isNull()) {
|
||||
qCWarning(dcSma()) << "Cannot set up sma modbus inverter. The host address is not known yet. Maybe it will be available in the next run...";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The host address is not known yet. Trying again later."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up in case the setup gets aborted
|
||||
connect(info, &ThingSetupInfo::aborted, monitor, [=](){
|
||||
if (m_monitors.contains(thing)) {
|
||||
qCDebug(dcSma()) << "Unregister monitor because setup has been aborted.";
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the monitor to be ready
|
||||
if (monitor->reachable()) {
|
||||
// Thing already reachable...let's continue with the setup
|
||||
setupModbusInverterConnection(info);
|
||||
} else {
|
||||
qCDebug(dcSma()) << "Waiting for the network monitor to get reachable before continue to set up the connection" << thing->name() << address.toString() << "...";
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, info, [=](bool reachable){
|
||||
if (reachable) {
|
||||
qCDebug(dcSma()) << "The monitor for thing setup" << thing->name() << "is now reachable. Continue setup...";
|
||||
setupModbusInverterConnection(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::postSetupThing(Thing *thing)
|
||||
{
|
||||
qCDebug(dcSma()) << "Post setup thing" << thing->name();
|
||||
if (thing->thingClassId() == sunnyWebBoxThingClassId) {
|
||||
SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
|
||||
if (!sunnyWebBox)
|
||||
return;
|
||||
|
||||
sunnyWebBox->getPlantOverview();
|
||||
thing->setStateValue("connected", true);
|
||||
setupRefreshTimer();
|
||||
|
||||
} else if (thing->thingClassId() == speedwireInverterThingClassId) {
|
||||
SpeedwireInverter *inverter = m_speedwireInverters.value(thing);
|
||||
if (inverter) {
|
||||
thing->setStateValue("connected", inverter->reachable());
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
}
|
||||
|
||||
setupRefreshTimer();
|
||||
|
||||
} else if (thing->thingClassId() == modbusInverterThingClassId) {
|
||||
SmaInverterModbusTcpConnection *connection = m_modbusInverters.value(thing);
|
||||
if (connection) {
|
||||
thing->setStateValue("connected", connection->reachable());
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
}
|
||||
|
||||
setupRefreshTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::thingRemoved(Thing *thing)
|
||||
{
|
||||
if (thing->thingClassId() == sunnyWebBoxThingClassId) {
|
||||
m_sunnyWebBoxes.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == speedwireMeterThingClassId && m_speedwireMeters.contains(thing)) {
|
||||
m_speedwireMeters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == speedwireInverterThingClassId && m_speedwireInverters.contains(thing)) {
|
||||
m_speedwireInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (thing->thingClassId() == modbusInverterThingClassId && m_modbusInverters.contains(thing)) {
|
||||
m_modbusInverters.take(thing)->deleteLater();
|
||||
}
|
||||
|
||||
if (m_monitors.contains(thing)) {
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(m_monitors.take(thing));
|
||||
}
|
||||
|
||||
if (myThings().isEmpty()) {
|
||||
qCDebug(dcSma()) << "Stopping timer";
|
||||
hardwareManager()->pluginTimerManager()->unregisterTimer(m_refreshTimer);
|
||||
m_refreshTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::onConnectedChanged(bool connected)
|
||||
{
|
||||
Thing *thing = m_sunnyWebBoxes.key(static_cast<SunnyWebBox *>(sender()));
|
||||
if (!thing)
|
||||
return;
|
||||
|
||||
thing->setStateValue(sunnyWebBoxConnectedStateTypeId, connected);
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview)
|
||||
{
|
||||
Q_UNUSED(messageId)
|
||||
|
||||
qCDebug(dcSma()) << "Plant overview received" << overview.status;
|
||||
Thing *thing = m_sunnyWebBoxes.key(static_cast<SunnyWebBox *>(sender()));
|
||||
if (!thing)
|
||||
return;
|
||||
|
||||
thing->setStateValue(sunnyWebBoxCurrentPowerStateTypeId, overview.power);
|
||||
thing->setStateValue(sunnyWebBoxDayEnergyProducedStateTypeId, overview.dailyYield);
|
||||
thing->setStateValue(sunnyWebBoxTotalEnergyProducedStateTypeId, overview.totalYield);
|
||||
thing->setStateValue(sunnyWebBoxModeStateTypeId, overview.status);
|
||||
if (!overview.error.isEmpty()){
|
||||
qCDebug(dcSma()) << "Received error" << overview.error;
|
||||
thing->setStateValue(sunnyWebBoxErrorStateTypeId, overview.error);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupRefreshTimer()
|
||||
{
|
||||
// If already set up...
|
||||
if (m_refreshTimer)
|
||||
return;
|
||||
|
||||
m_refreshTimer = hardwareManager()->pluginTimerManager()->registerTimer(5);
|
||||
connect(m_refreshTimer, &PluginTimer::timeout, this, [=](){
|
||||
foreach (Thing *thing, myThings().filterByThingClassId(sunnyWebBoxThingClassId)) {
|
||||
SunnyWebBox *sunnyWebBox = m_sunnyWebBoxes.value(thing);
|
||||
sunnyWebBox->getPlantOverview();
|
||||
}
|
||||
|
||||
foreach (SpeedwireInverter *inverter, m_speedwireInverters) {
|
||||
// Note: refresh will not be triggered if there is already a refresh process running
|
||||
inverter->refresh();
|
||||
}
|
||||
|
||||
foreach (SmaInverterModbusTcpConnection *connection, m_modbusInverters) {
|
||||
connection->update();
|
||||
}
|
||||
});
|
||||
|
||||
m_refreshTimer->start();
|
||||
}
|
||||
|
||||
void IntegrationPluginSma::setupModbusInverterConnection(ThingSetupInfo *info)
|
||||
{
|
||||
Thing *thing = info->thing();
|
||||
|
||||
QHostAddress address = m_monitors.value(thing)->networkDeviceInfo().address();
|
||||
uint port = thing->paramValue(modbusInverterThingPortParamTypeId).toUInt();
|
||||
quint16 slaveId = thing->paramValue(modbusInverterThingSlaveIdParamTypeId).toUInt();
|
||||
|
||||
qCDebug(dcSma()) << "Setting up SMA inverter on" << address.toString() << port << "unit ID:" << slaveId;
|
||||
SmaInverterModbusTcpConnection *connection = new SmaInverterModbusTcpConnection(address, port, slaveId, this);
|
||||
connect(info, &ThingSetupInfo::aborted, connection, &SmaInverterModbusTcpConnection::deleteLater);
|
||||
|
||||
// Reconnect on monitor reachable changed
|
||||
NetworkDeviceMonitor *monitor = m_monitors.value(thing);
|
||||
connect(monitor, &NetworkDeviceMonitor::reachableChanged, thing, [=](bool reachable){
|
||||
qCDebug(dcSma()) << "Network device monitor reachable changed for" << thing->name() << reachable;
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
if (reachable && !thing->stateValue("connected").toBool()) {
|
||||
connection->setHostAddress(monitor->networkDeviceInfo().address());
|
||||
connection->connectDevice();
|
||||
} else if (!reachable) {
|
||||
// Note: We disable autoreconnect explicitly and we will
|
||||
// connect the device once the monitor says it is reachable again
|
||||
connection->disconnectDevice();
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::reachableChanged, thing, [this, thing, connection](bool reachable){
|
||||
qCDebug(dcSma()) << "Reachable changed to" << reachable << "for" << thing;
|
||||
if (reachable) {
|
||||
// Connected true will be set after successfull init
|
||||
connection->initialize();
|
||||
} else {
|
||||
thing->setStateValue("connected", false);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, thing, [=](bool success){
|
||||
if (!thing->setupComplete())
|
||||
return;
|
||||
|
||||
thing->setStateValue("connected", success);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", success);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// Try once to reconnect the device
|
||||
connection->reconnectDevice();
|
||||
}
|
||||
});
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, info, [=](bool success){
|
||||
if (!success) {
|
||||
qCWarning(dcSma()) << "Connection init finished with errors" << thing->name() << connection->hostAddress().toString();
|
||||
hardwareManager()->networkDeviceDiscovery()->unregisterMonitor(monitor);
|
||||
connection->deleteLater();
|
||||
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Could not initialize the communication with the inverter."));
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Connection init finished successfully" << connection;
|
||||
m_modbusInverters.insert(thing, connection);
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
|
||||
// Set connected true
|
||||
thing->setStateValue("connected", true);
|
||||
foreach (Thing *childThing, myThings().filterByParentId(thing->id())) {
|
||||
childThing->setStateValue("connected", true);
|
||||
}
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::updateFinished, thing, [=](){
|
||||
qCDebug(dcSma()) << "Updated" << connection;
|
||||
|
||||
// Grid voltage
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseA()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseAStateTypeId, connection->gridVoltagePhaseA() / 100.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseB()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseBStateTypeId, connection->gridVoltagePhaseB() / 100.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridVoltagePhaseC()))
|
||||
thing->setStateValue(modbusInverterVoltagePhaseCStateTypeId, connection->gridVoltagePhaseC() / 100.0);
|
||||
|
||||
// Grid current
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseA()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseAStateTypeId, connection->gridCurrentPhaseA() / 1000.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseB()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseBStateTypeId, connection->gridCurrentPhaseB() / 1000.0);
|
||||
|
||||
if (isModbusValueValid(connection->gridCurrentPhaseC()))
|
||||
thing->setStateValue(modbusInverterCurrentPhaseCStateTypeId, connection->gridCurrentPhaseC() / 1000.0);
|
||||
|
||||
// Phase power
|
||||
if (isModbusValueValid(connection->currentPowerPhaseA()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseAStateTypeId, connection->currentPowerPhaseA());
|
||||
|
||||
if (isModbusValueValid(connection->currentPowerPhaseB()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseBStateTypeId, connection->currentPowerPhaseB());
|
||||
|
||||
if (isModbusValueValid(connection->currentPowerPhaseC()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerPhaseCStateTypeId, connection->currentPowerPhaseC());
|
||||
|
||||
// Others
|
||||
if (isModbusValueValid(connection->totalYield()))
|
||||
thing->setStateValue(modbusInverterTotalEnergyProducedStateTypeId, connection->totalYield() / 1000.0); // kWh
|
||||
|
||||
if (isModbusValueValid(connection->dailyYield()))
|
||||
thing->setStateValue(modbusInverterEnergyProducedTodayStateTypeId, connection->dailyYield() / 1000.0); // kWh
|
||||
|
||||
// Power
|
||||
if (isModbusValueValid(connection->currentPower()))
|
||||
thing->setStateValue(modbusInverterCurrentPowerStateTypeId, -connection->currentPower());
|
||||
|
||||
// Version
|
||||
thing->setStateValue(modbusInverterFirmwareVersionStateTypeId, Sma::buildSoftwareVersionString(connection->softwarePackage()));
|
||||
});
|
||||
|
||||
// Update registers
|
||||
connection->update();
|
||||
});
|
||||
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
bool IntegrationPluginSma::isModbusValueValid(quint32 value)
|
||||
{
|
||||
return value != 0xffffffff;
|
||||
}
|
||||
|
||||
bool IntegrationPluginSma::isModbusValueValid(qint32 value)
|
||||
{
|
||||
return value != static_cast<qint32>(0x80000000);
|
||||
}
|
||||
|
||||
bool IntegrationPluginSma::isModbusValueValid(quint64 value)
|
||||
{
|
||||
return value != 0xffffffffffffffff;
|
||||
}
|
||||
|
||||
88
sma/integrationpluginsma.h
Normal file
88
sma/integrationpluginsma.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 INTEGRATIONPLUGINSMA_H
|
||||
#define INTEGRATIONPLUGINSMA_H
|
||||
|
||||
#include <integrations/integrationplugin.h>
|
||||
#include <plugintimer.h>
|
||||
#include <network/networkdevicemonitor.h>
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "sunnywebbox/sunnywebbox.h"
|
||||
#include "speedwire/speedwiremeter.h"
|
||||
#include "speedwire/speedwireinverter.h"
|
||||
|
||||
#include "smainvertermodbustcpconnection.h"
|
||||
|
||||
class IntegrationPluginSma: public IntegrationPlugin {
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsma.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
public:
|
||||
explicit IntegrationPluginSma();
|
||||
|
||||
void init() override;
|
||||
void discoverThings(ThingDiscoveryInfo *info) override;
|
||||
|
||||
void startPairing(ThingPairingInfo *info) override;
|
||||
void confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret) override;
|
||||
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
void postSetupThing(Thing *thing) override;
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private slots:
|
||||
void onConnectedChanged(bool connected);
|
||||
void onPlantOverviewReceived(const QString &messageId, SunnyWebBox::Overview overview);
|
||||
|
||||
void setupRefreshTimer();
|
||||
|
||||
void setupModbusInverterConnection(ThingSetupInfo *info);
|
||||
|
||||
private:
|
||||
PluginTimer *m_refreshTimer = nullptr;
|
||||
|
||||
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
|
||||
|
||||
QHash<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
|
||||
QHash<Thing *, SpeedwireMeter *> m_speedwireMeters;
|
||||
QHash<Thing *, SpeedwireInverter *> m_speedwireInverters;
|
||||
QHash<Thing *, SmaInverterModbusTcpConnection *> m_modbusInverters;
|
||||
|
||||
// Sma modbus data validation
|
||||
bool isModbusValueValid(quint32 value);
|
||||
bool isModbusValueValid(qint32 value);
|
||||
bool isModbusValueValid(quint64 value);
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINSMA_H
|
||||
633
sma/integrationpluginsma.json
Normal file
633
sma/integrationpluginsma.json
Normal file
@ -0,0 +1,633 @@
|
||||
{
|
||||
"id": "b8442bbf-9d3f-4aa2-9443-b3a31ae09bac",
|
||||
"name": "sma",
|
||||
"displayName": "SMA",
|
||||
"vendors": [
|
||||
{
|
||||
"id": "16d5a4a3-36d5-46c0-b7dd-df166ddf5981",
|
||||
"name": "Sma",
|
||||
"displayName": "SMA Solar Technology AG",
|
||||
"thingClasses": [
|
||||
{
|
||||
"id": "49304127-ce9b-45dd-8511-05030a4ac003",
|
||||
"name": "sunnyWebBox",
|
||||
"displayName": "SMA Sunny WebBox",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": ["solarinverter"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "864d4162-e3ce-48b8-b8ac-c1b971b52d42",
|
||||
"name": "host",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": "192.168.0.168"
|
||||
},
|
||||
{
|
||||
"id": "03f32361-4e13-4597-a346-af8d16a986b3",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": "00:00:00:00:00:00"
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "c05e6a1a-252c-4f2b-8b31-09cf113d01c1",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "16f34c5c-8dbb-4dcc-9faa-4b782d57226c",
|
||||
"name": "dayEnergyProduced",
|
||||
"displayName": "Day energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "0bb4e227-7e38-49ca-9b32-ce4621c9305b",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0
|
||||
},
|
||||
{
|
||||
"id": "1974550b-6059-4b0e-83f4-70177e20dac3",
|
||||
"name": "mode",
|
||||
"displayName": "Mode",
|
||||
"type": "QString",
|
||||
"defaultValue": "MPP"
|
||||
},
|
||||
{
|
||||
"id": "4e64f9ca-7e5a-4897-8035-6f2ae88fde89",
|
||||
"name": "error",
|
||||
"displayName": "Error",
|
||||
"type": "QString",
|
||||
"defaultValue": "None"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "0c5097af-e136-4430-9fb4-0ccbb30c3e1c",
|
||||
"name": "speedwireMeter",
|
||||
"displayName": "SMA Energy Meter",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": [ "energymeter" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "d90193e6-a996-4e49-bf6d-564d596d7e74",
|
||||
"name": "host",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": "192.168.0.168"
|
||||
},
|
||||
{
|
||||
"id": "2780eab7-1f1c-4cc7-a789-a8790329ca9e",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "7c81a0c5-9bc6-43bb-a01a-4de5fe656bba",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "abdc114d-1fac-4454-8b82-871ed5cdf28c",
|
||||
"name": "modelId",
|
||||
"displayName": "Model ID",
|
||||
"type": "uint",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "35733d27-4fe0-439a-be71-7c1597481659",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "44ee2491-8376-41cd-a21d-185c736152ec",
|
||||
"name": "voltagePhaseA",
|
||||
"displayName": "Voltage phase A",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "56ae3555-f874-4c2d-8833-17573dce477a",
|
||||
"name": "voltagePhaseB",
|
||||
"displayName": "Voltage phase B",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "51cbb29b-29f0-480a-9d7d-b8f4e6a205ae",
|
||||
"name": "voltagePhaseC",
|
||||
"displayName": "Voltage phase C",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "45bbdbef-1832-4870-bff5-299e580fb4da",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "b3a4fdd2-b6b8-4c58-9da3-2084ad414022",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "b3655188-3854-4336-ae3c-61d3bda6fc4d",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "d4ac7f37-e30a-44e4-93cb-ad16df18b8f1",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c5d09c63-7461-4fb8-a6fe-bc7aa919be30",
|
||||
"name": "currentPowerPhaseA",
|
||||
"displayName": "Current power phase A",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "c52d4422-b521-4804-a7a7-c4398e91e760",
|
||||
"name": "currentPowerPhaseB",
|
||||
"displayName": "Current power phase B",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "555e892c-3ca7-4100-9832-6ac13b87eb04",
|
||||
"name": "currentPowerPhaseC",
|
||||
"displayName": "Current power phase C",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "4fb0a4c1-18ed-4d02-b6d0-c07e9b96a56d",
|
||||
"name": "totalEnergyConsumed",
|
||||
"displayName": "Total energy consumed",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "76ca68d8-6781-4d2a-8663-440aec40b4de",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "b4ff2c71-f81d-4904-bbac-0c0c6e8a5a33",
|
||||
"name": "energyConsumedPhaseA",
|
||||
"displayName": "Energy consumed phase A",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "c4e5f569-ac5d-4761-a898-888880bfd59f",
|
||||
"name": "energyConsumedPhaseB",
|
||||
"displayName": "Energy consumed phase B",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "aabc02d7-8dc3-4637-8bf2-dc2e0e737ad3",
|
||||
"name": "energyConsumedPhaseC",
|
||||
"displayName": "Energy consumed phase C",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "754c3b67-768a-47f7-99d8-f66c198f0835",
|
||||
"name": "energyProducedPhaseA",
|
||||
"displayName": "Energy produced phase A",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "7eb08c45-24cf-40ce-be28-f3564f087672",
|
||||
"name": "energyProducedPhaseB",
|
||||
"displayName": "Energy produced phase B",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "1eb2bf01-5ec6-42e5-b348-ac1e95199d14",
|
||||
"name": "energyProducedPhaseC",
|
||||
"displayName": "Energy produced phase C",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "a685393c-8b7e-42c5-bb41-f9907c074626",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "b63a0669-f2ac-4769-abea-e14cafb2309a",
|
||||
"name": "speedwireInverter",
|
||||
"displayName": "SMA Inverter",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"setupMethod": "EnterPin",
|
||||
"interfaces": [ "solarinverter" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "c8098d53-69eb-4d0b-9f07-e43c4a0ea9a9",
|
||||
"name": "host",
|
||||
"displayName": "Host address",
|
||||
"type": "QString",
|
||||
"inputType": "IPv4Address",
|
||||
"defaultValue": "192.168.0.168"
|
||||
},
|
||||
{
|
||||
"id": "7df0ab60-0f11-4495-8e0d-508ba2b6d858",
|
||||
"name": "macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "e42242b4-2811-47f9-b42b-b150ed233217",
|
||||
"name": "serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "d9892f74-5b93-4c98-8da2-72aca033273a",
|
||||
"name": "modelId",
|
||||
"displayName": "Model ID",
|
||||
"type": "uint",
|
||||
"inputType": "TextLine",
|
||||
"readOnly": true,
|
||||
"defaultValue": 0
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "aaff72c3-c70a-4a2f-bed1-89f38cebe442",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false
|
||||
},
|
||||
{
|
||||
"id": "6ef4eb16-a3d6-4bc9-972d-5e7cb81173a5",
|
||||
"name": "voltagePhaseA",
|
||||
"displayName": "Voltage phase A",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "d9a5768b-1bf5-4933-810d-84dd7a688f71",
|
||||
"name": "voltagePhaseB",
|
||||
"displayName": "Voltage phase B",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "fc168dc6-eecf-40b4-b214-3e28da0dbb12",
|
||||
"name": "voltagePhaseC",
|
||||
"displayName": "Voltage phase C",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "2a6c59ca-853a-47d6-96fb-0c85edf32f52",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "4db96fec-737c-4c4b-bf07-5ef2fd62508a",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "0f23fb0e-a440-4ac2-9aff-896bc65feb2c",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "d7ceb482-5df8-4c0c-82bd-62ce7ba22c43",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "b366f680-6134-488b-8362-b1b824a8daca",
|
||||
"name": "currentPowerMpp1",
|
||||
"displayName": "DC power MPP1",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "87d9b654-5558-47a3-9db9-ffd7c23b4774",
|
||||
"name": "currentPowerMpp2",
|
||||
"displayName": "DC power MPP2",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "51cadd66-2cf1-485a-a2a9-191d11abfbd1",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "e8bc8f81-e5c5-4900-b429-93fcaa262fcb",
|
||||
"name": "energyProducedToday",
|
||||
"displayName": "Energy produced today",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "fdccf5de-7413-4480-9ca0-1151665dede8",
|
||||
"name": "frequency",
|
||||
"displayName": "Frequency",
|
||||
"type": "double",
|
||||
"unit": "Hertz",
|
||||
"defaultValue": 0.00,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "6d76cc7b-9e00-4561-be7b-4e2a6b8f7b66",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "12e0429e-e8ce-48bd-a11c-faaf0bd71856",
|
||||
"name": "modbusInverter",
|
||||
"displayName": "SMA Inverter (Modbus)",
|
||||
"createMethods": ["discovery", "user"],
|
||||
"interfaces": [ "solarinverter" ],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "3cea46a0-9535-4612-9971-19167109e63c",
|
||||
"name":"macAddress",
|
||||
"displayName": "MAC address",
|
||||
"type": "QString",
|
||||
"inputType": "MacAddress",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "18ded0c1-308e-4a13-a12c-cf9a8ed5a26c",
|
||||
"name":"port",
|
||||
"displayName": "Port",
|
||||
"type": "int",
|
||||
"defaultValue": 502
|
||||
},
|
||||
{
|
||||
"id": "6322db2a-0554-4f83-9509-39870ad89027",
|
||||
"name":"slaveId",
|
||||
"displayName": "Slave ID",
|
||||
"type": "int",
|
||||
"defaultValue": 3
|
||||
},
|
||||
{
|
||||
"id": "563f2b12-b784-4a2c-856f-57a2b5ce2e9d",
|
||||
"name":"serialNumber",
|
||||
"displayName": "Serial number",
|
||||
"type": "QString",
|
||||
"defaultValue": "",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "3c60e2a7-31f3-4b0b-a3f9-ede042e82f22",
|
||||
"name": "connected",
|
||||
"displayName": "Connected",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "5a717aff-6bdb-4679-94d6-ec1bce7fa2af",
|
||||
"name": "voltagePhaseA",
|
||||
"displayName": "Voltage phase A",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "34eb5b54-7683-42ff-8320-9b2527d6381c",
|
||||
"name": "voltagePhaseB",
|
||||
"displayName": "Voltage phase B",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "2bd0a069-9d16-4d58-9f78-df682d92005d",
|
||||
"name": "voltagePhaseC",
|
||||
"displayName": "Voltage phase C",
|
||||
"type": "double",
|
||||
"unit": "Volt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "48d4a7b7-b09a-4255-83dd-9eab8ea3a51c",
|
||||
"name": "currentPhaseA",
|
||||
"displayName": "Current phase A",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "479b27c4-01fc-45ef-a462-b8d8499b3422",
|
||||
"name": "currentPhaseB",
|
||||
"displayName": "Current phase B",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "f82bbba1-c68a-4c43-a3e5-10b00ed924d7",
|
||||
"name": "currentPhaseC",
|
||||
"displayName": "Current phase C",
|
||||
"type": "double",
|
||||
"unit": "Ampere",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "225beb67-95ca-495c-aca8-cd3fd4efedd5",
|
||||
"name": "currentPower",
|
||||
"displayName": "Current power",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "9283d5a9-b185-4678-beb1-1c6ce6f76930",
|
||||
"name": "currentPowerPhaseA",
|
||||
"displayName": "Current power phase A",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "8a87319c-f6ab-4eb1-bb17-a65f80289a56",
|
||||
"name": "currentPowerPhaseB",
|
||||
"displayName": "Current power phase B",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "1f930456-5947-476c-b74b-480f1e81a799",
|
||||
"name": "currentPowerPhaseC",
|
||||
"displayName": "Current power phase C",
|
||||
"type": "double",
|
||||
"unit": "Watt",
|
||||
"defaultValue": 0,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "5e0ed108-7e93-4724-a831-319109d9daf8",
|
||||
"name": "totalEnergyProduced",
|
||||
"displayName": "Total energy produced",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "b8fb66fa-46b5-4ed7-82a7-29fe5257caa9",
|
||||
"name": "energyProducedToday",
|
||||
"displayName": "Energy produced today",
|
||||
"type": "double",
|
||||
"unit": "KiloWattHour",
|
||||
"defaultValue": 0.00
|
||||
},
|
||||
{
|
||||
"id": "3f290cbc-0578-479a-ab98-d89b5549184d",
|
||||
"name": "firmwareVersion",
|
||||
"displayName": "Firmware version",
|
||||
"type": "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
13
sma/meta.json
Normal file
13
sma/meta.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"title": "SMA",
|
||||
"tagline": "Connect to SMA solar equipment.",
|
||||
"icon": "sma.png",
|
||||
"stability": "consumer",
|
||||
"offline": true,
|
||||
"technologies": [
|
||||
"network"
|
||||
],
|
||||
"categories": [
|
||||
"energy"
|
||||
]
|
||||
}
|
||||
176
sma/modbus/smamodbusdiscovery.cpp
Normal file
176
sma/modbus/smamodbusdiscovery.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "smamodbusdiscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "sma.h"
|
||||
|
||||
|
||||
SmaModbusDiscovery::SmaModbusDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port, quint16 modbusAddress,QObject *parent)
|
||||
: QObject{parent},
|
||||
m_networkDeviceDiscovery{networkDeviceDiscovery},
|
||||
m_port{port},
|
||||
m_modbusAddress{modbusAddress}
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::startDiscovery()
|
||||
{
|
||||
qCInfo(dcSma()) << "Discovery: Start searching for SMA modbus inverters in the network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
// Imedialty check any new device gets discovered
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SmaModbusDiscovery::checkNetworkDevice);
|
||||
|
||||
// Check what might be left on finished
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery: Network discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices";
|
||||
|
||||
// Send a report request to nework device info not sent already...
|
||||
foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
|
||||
if (!m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo)) {
|
||||
checkNetworkDevice(networkDeviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Give the last connections added right before the network discovery finished a chance to check the device...
|
||||
QTimer::singleShot(3000, this, [this](){
|
||||
qCDebug(dcSma()) << "Discovery: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
QList<SmaModbusDiscovery::SmaModbusDiscoveryResult> SmaModbusDiscovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
// Create a kostal connection and try to initialize it.
|
||||
// Only if initialized successfully and all information have been fetched correctly from
|
||||
// the device we can assume this is what we are locking for (ip, port, modbus address, correct registers).
|
||||
// We cloud tough also filter the result only for certain software versions, manufactueres or whatever...
|
||||
|
||||
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
|
||||
return;
|
||||
|
||||
SmaInverterModbusTcpConnection *connection = new SmaInverterModbusTcpConnection(networkDeviceInfo.address(), m_port, m_modbusAddress, this);
|
||||
m_connections.append(connection);
|
||||
m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
|
||||
|
||||
connect(connection, &SmaInverterModbusTcpConnection::reachableChanged, this, [=](bool reachable){
|
||||
if (!reachable) {
|
||||
// Disconnected ... done with this connection
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Modbus TCP connected...ok, let's try to initialize it!
|
||||
connect(connection, &SmaInverterModbusTcpConnection::initializationFinished, this, [=](bool success){
|
||||
if (!success) {
|
||||
qCDebug(dcSma()) << "Discovery: Initialization failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection->deviceClass() != Sma::DeviceClassSolarInverter) {
|
||||
qCDebug(dcSma()) << "Discovery: Initialization successfull for" << networkDeviceInfo.address().toString() << "but the device class is not an inverter. Continue...";;
|
||||
cleanupConnection(connection);
|
||||
return;
|
||||
}
|
||||
|
||||
SmaModbusDiscoveryResult result;
|
||||
result.productName = Sma::getModelName(connection->modelIdentifier());
|
||||
result.deviceName = connection->deviceName();
|
||||
result.serialNumber = QString::number(connection->serialNumber());
|
||||
result.port = m_port;
|
||||
result.modbusAddress = m_modbusAddress;
|
||||
result.softwareVersion = Sma::buildSoftwareVersionString(connection->softwarePackage());
|
||||
result.networkDeviceInfo = networkDeviceInfo;
|
||||
m_discoveryResults.append(result);
|
||||
|
||||
qCDebug(dcSma()) << "Discovery: --> Found" << result.productName;
|
||||
qCDebug(dcSma()) << " Device name:" << result.deviceName;
|
||||
qCDebug(dcSma()) << " Serial number:" << result.serialNumber;
|
||||
qCDebug(dcSma()) << " Software version:" << result.softwareVersion;
|
||||
qCDebug(dcSma()) << " " << result.networkDeviceInfo;
|
||||
|
||||
// Done with this connection
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
// Initializing...
|
||||
if (!connection->initialize()) {
|
||||
qCDebug(dcSma()) << "Discovery: Unable to initialize connection on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If we get any error...skip this host...
|
||||
connect(connection, &SmaInverterModbusTcpConnection::connectionErrorOccurred, this, [=](QModbusDevice::Error error){
|
||||
if (error != QModbusDevice::NoError) {
|
||||
qCDebug(dcSma()) << "Discovery: Connection error on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
}
|
||||
});
|
||||
|
||||
// If check reachability failed...skip this host...
|
||||
connect(connection, &SmaInverterModbusTcpConnection::checkReachabilityFailed, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery: Check reachability failed on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
cleanupConnection(connection);
|
||||
});
|
||||
|
||||
// Try to connect, maybe it works, maybe not...
|
||||
connection->connectDevice();
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::cleanupConnection(SmaInverterModbusTcpConnection *connection)
|
||||
{
|
||||
m_connections.removeAll(connection);
|
||||
connection->disconnectDevice();
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void SmaModbusDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
|
||||
// Cleanup any leftovers...we don't care any more
|
||||
foreach (SmaInverterModbusTcpConnection *connection, m_connections)
|
||||
cleanupConnection(connection);
|
||||
|
||||
qCInfo(dcSma()) << "Discovery: Finished the discovery process. Found" << m_discoveryResults.count() << "SMA inverters in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
emit discoveryFinished();
|
||||
}
|
||||
82
sma/modbus/smamodbusdiscovery.h
Normal file
82
sma/modbus/smamodbusdiscovery.h
Normal file
@ -0,0 +1,82 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SMAMODBUSDISCOVERY_H
|
||||
#define SMAMODBUSDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "smainvertermodbustcpconnection.h"
|
||||
|
||||
class SmaModbusDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SmaModbusDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint16 port = 502, quint16 modbusAddress = 3, QObject *parent = nullptr);
|
||||
typedef struct SmaModbusDiscoveryResult {
|
||||
QString productName;
|
||||
QString deviceName;
|
||||
QString serialNumber;
|
||||
quint16 port;
|
||||
quint16 modbusAddress;
|
||||
QString softwareVersion;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
} SmaModbusDiscoveryResult;
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
QList<SmaModbusDiscoveryResult> discoveryResults() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
quint16 m_port;
|
||||
quint16 m_modbusAddress;
|
||||
|
||||
QDateTime m_startDateTime;
|
||||
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
|
||||
|
||||
QList<SmaInverterModbusTcpConnection *> m_connections;
|
||||
|
||||
QList<SmaModbusDiscoveryResult> m_discoveryResults;
|
||||
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupConnection(SmaInverterModbusTcpConnection *connection);
|
||||
|
||||
void finishDiscovery();
|
||||
|
||||
};
|
||||
|
||||
#endif // SMAMODBUSDISCOVERY_H
|
||||
269
sma/sma-inverter-registers.json
Normal file
269
sma/sma-inverter-registers.json
Normal file
@ -0,0 +1,269 @@
|
||||
{
|
||||
"className": "SmaInverter",
|
||||
"protocol": "TCP",
|
||||
"endianness": "BigEndian",
|
||||
"errorLimitUntilNotReachable": 20,
|
||||
"checkReachableRegister": "totalYield",
|
||||
"enums": [
|
||||
{
|
||||
"name": "Condition",
|
||||
"values": [
|
||||
{
|
||||
"key": "Fault",
|
||||
"value": 35
|
||||
},
|
||||
{
|
||||
"key": "Off",
|
||||
"value": 303
|
||||
},
|
||||
{
|
||||
"key": "Ok",
|
||||
"value": 307
|
||||
},
|
||||
{
|
||||
"key": "Warning",
|
||||
"value": 455
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RecommendedAction",
|
||||
"values": [
|
||||
{
|
||||
"key": "ContactManufacturer",
|
||||
"value": 336
|
||||
},
|
||||
{
|
||||
"key": "ContactInstaller",
|
||||
"value": 337
|
||||
},
|
||||
{
|
||||
"key": "Invalid",
|
||||
"value": 338
|
||||
},
|
||||
{
|
||||
"key": "None",
|
||||
"value": 887
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{
|
||||
"id": "identification",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "deviceClass",
|
||||
"address": 30051,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device class",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "modelIdentifier",
|
||||
"address": 30053,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device type (model identifier)",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "information",
|
||||
"readSchedule": "init",
|
||||
"registers": [
|
||||
{
|
||||
"id": "serialNumber",
|
||||
"address": 30057,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Serial number",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "softwarePackage",
|
||||
"address": 30059,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Firmware version",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "yield",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "totalYield",
|
||||
"address": 30513,
|
||||
"size": 4,
|
||||
"type": "uint64",
|
||||
"readSchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Total yield",
|
||||
"unit": "Wh",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "dailyYield",
|
||||
"address": 30517,
|
||||
"size": 4,
|
||||
"type": "uint64",
|
||||
"readSccalchedule": "update",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Today yield",
|
||||
"unit": "Wh",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "currentPower",
|
||||
"address": 30775,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current power",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentPowerPhaseA",
|
||||
"address": 30777,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current power L1",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentPowerPhaseB",
|
||||
"address": 30779,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current power L2",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "currentPowerPhaseC",
|
||||
"address": 30781,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Current power L3",
|
||||
"unit": "W",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridVoltagePhaseA",
|
||||
"address": 30783,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid voltage L1",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridVoltagePhaseB",
|
||||
"address": 30785,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid voltage L2",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridVoltagePhaseC",
|
||||
"address": 30787,
|
||||
"size": 2,
|
||||
"type": "uint32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid voltage L3",
|
||||
"unit": "V",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gridCurrent",
|
||||
"readSchedule": "update",
|
||||
"registers": [
|
||||
{
|
||||
"id": "gridCurrentPhaseA",
|
||||
"address": 30977,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid current L1",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridCurrentPhaseB",
|
||||
"address": 30979,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid current L2",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
},
|
||||
{
|
||||
"id": "gridCurrentPhaseC",
|
||||
"address": 30981,
|
||||
"size": 2,
|
||||
"type": "int32",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Grid current L3",
|
||||
"unit": "A",
|
||||
"defaultValue": "0",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"registers": [
|
||||
{
|
||||
"id": "deviceName",
|
||||
"address": 40631,
|
||||
"size": 32,
|
||||
"type": "string",
|
||||
"readSchedule": "init",
|
||||
"registerType": "holdingRegister",
|
||||
"description": "Device name",
|
||||
"access": "RO"
|
||||
}
|
||||
]
|
||||
}
|
||||
239
sma/sma.h
Normal file
239
sma/sma.h
Normal file
@ -0,0 +1,239 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SMA_H
|
||||
#define SMA_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <QMetaObject>
|
||||
#include <QDataStream>
|
||||
|
||||
class Sma
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
enum DeviceClass {
|
||||
DeviceClassUnknown = 0x0000,
|
||||
DeviceClassAllDevices = 0x1f40,
|
||||
DeviceClassSolarInverter = 0x1f41,
|
||||
DeviceClassWindTurbine = 0x1f42,
|
||||
DeviceClassBatteryInverter = 0x1f47,
|
||||
DeviceClassConsumer = 0x1f61,
|
||||
DeviceClassSensorSystem = 0x1f80,
|
||||
DeviceClassElectricityMeter = 0x1f81,
|
||||
DeviceClassCommunicationProduct = 0x1fc0
|
||||
};
|
||||
Q_ENUM(DeviceClass)
|
||||
|
||||
inline static QString buildSoftwareVersionString(quint32 versionData) {
|
||||
|
||||
// Software version
|
||||
QByteArray rawData;
|
||||
QDataStream stream(&rawData, QIODevice::ReadWrite);
|
||||
stream << versionData;
|
||||
|
||||
quint8 major = static_cast<quint8>(rawData.at(0));
|
||||
quint8 minor = static_cast<quint8>(rawData.at(1));
|
||||
quint8 build = static_cast<quint8>(rawData.at(2));
|
||||
quint8 revision = static_cast<quint8>(rawData.at(3));;
|
||||
|
||||
// Revision types:
|
||||
// 0 -> N: No revision
|
||||
// 1 -> E: Experimental version
|
||||
// 2 -> A: Alpha version
|
||||
// 3 -> B: Beta version
|
||||
// 4 -> R: Release version
|
||||
// 5 -> S: Special version
|
||||
QChar revisionCharacter;
|
||||
switch (revision) {
|
||||
case 0:
|
||||
revisionCharacter = 'N';
|
||||
break;
|
||||
case 1:
|
||||
revisionCharacter = 'E';
|
||||
break;
|
||||
case 2:
|
||||
revisionCharacter = 'A';
|
||||
break;
|
||||
case 3:
|
||||
revisionCharacter = 'B';
|
||||
break;
|
||||
case 4:
|
||||
revisionCharacter = 'R';
|
||||
break;
|
||||
case 5:
|
||||
revisionCharacter = 'S';
|
||||
break;
|
||||
}
|
||||
|
||||
return QString("%1.%2.%3-%4").arg(major).arg(minor).arg(build).arg(revisionCharacter);
|
||||
}
|
||||
|
||||
inline static QString getModelName(quint16 modelIdentifier) {
|
||||
switch (modelIdentifier) {
|
||||
// Modbus
|
||||
case 9225: return "SB 5000SE-10";
|
||||
case 9226: return "SB 3600SE-10";
|
||||
case 9165: return "SB 3600TL-21";
|
||||
case 9075: return "SB 4000TL-21";
|
||||
case 9076: return "SB 5000TL-21";
|
||||
case 9162: return "SB 3500TL-JP-22";
|
||||
case 9164: return "SB 4500TL-JP-22";
|
||||
case 9198: return "SB 3000TL-US-22";
|
||||
case 9199: return "SB 3800TL-US-22";
|
||||
case 9200: return "SB 4000TL-US-22";
|
||||
case 9201: return "SB 5000TL-US-22";
|
||||
case 9274: return "SB 6000TL-US-22";
|
||||
case 9275: return "SB 7000TL-US-22";
|
||||
case 9293: return "SB 7700TL-US-22";
|
||||
case 9222: return "STP 10000TLEE-JP-10";
|
||||
case 9194: return "STP 12000TL-US-10";
|
||||
case 9195: return "STP 15000TL-US-10";
|
||||
case 9196: return "STP 20000TL-US-10";
|
||||
case 9197: return "STP 24000TL-US-10";
|
||||
case 9310: return "STP 30000TL-US-10";
|
||||
case 9271: return "STP 20000TLEE-JP-11";
|
||||
case 9272: return "STP 10000TLEE-JP-11";
|
||||
case 9354: return "STP 24500TL-JP-30";
|
||||
case 9311: return "STP 25000TL-JP-30";
|
||||
case 9223: return "SI6.0H-11";
|
||||
case 9224: return "SI8.0H-11";
|
||||
|
||||
// Speedwire / Modbus
|
||||
case 9015: return "SB 700";
|
||||
case 9016: return "SB 700U";
|
||||
case 9017: return "SB 1100";
|
||||
case 9018: return "SB 1100U";
|
||||
case 9019: return "SB 1100LV";
|
||||
case 9020: return "SB 1700";
|
||||
case 9021: return "SB 1900TLJ";
|
||||
case 9022: return "SB 2100TL";
|
||||
case 9023: return "SB 2500";
|
||||
case 9024: return "SB 2800";
|
||||
case 9025: return "SB 2800i";
|
||||
case 9026: return "SB 3000";
|
||||
case 9027: return "SB 3000US";
|
||||
case 9028: return "SB 3300";
|
||||
case 9029: return "SB 3300U";
|
||||
case 9030: return "SB 3300TL";
|
||||
case 9031: return "SB 3300TL HC";
|
||||
case 9032: return "SB 3800";
|
||||
case 9033: return "SB 3800U";
|
||||
case 9034: return "SB 4000US";
|
||||
case 9035: return "SB 4200TL";
|
||||
case 9036: return "SB 4200TL HC";
|
||||
case 9037: return "SB 5000TL";
|
||||
case 9038: return "SB 5000TLW";
|
||||
case 9039: return "SB 5000TL HC";
|
||||
case 9066: return "SB 1200";
|
||||
case 9067: return "STP 10000TL-10";
|
||||
case 9068: return "STP 12000TL-10";
|
||||
case 9069: return "STP 15000TL-10";
|
||||
case 9070: return "STP 17000TL-10";
|
||||
case 9074: return "SB 3000TL-21";
|
||||
case 9084: return "WB 3600TL-20";
|
||||
case 9085: return "WB 5000TL-20";
|
||||
case 9086: return "SB 3800US-10";
|
||||
case 9098: return "STP 5000TL-20";
|
||||
case 9099: return "STP 6000TL-20";
|
||||
case 9100: return "STP 7000TL-20";
|
||||
case 9101: return "STP 8000TL-10";
|
||||
case 9102: return "STPcase 9000TL-20";
|
||||
case 9103: return "STP 8000TL-20";
|
||||
case 9104: return "SB 3000TL-JP-21";
|
||||
case 9105: return "SB 3500TL-JP-21";
|
||||
case 9106: return "SB 4000TL-JP-21";
|
||||
case 9107: return "SB 4500TL-JP-21";
|
||||
case 9108: return "SCSMC";
|
||||
case 9109: return "SB 1600TL-10";
|
||||
case 9131: return "STP 20000TL-10";
|
||||
case 9139: return "STP 20000TLHE-10";
|
||||
case 9140: return "STP 15000TLHE-10";
|
||||
case 9157: return "Sunny Island 2012";
|
||||
case 9158: return "Sunny Island 2224";
|
||||
case 9159: return "Sunny Island 5048";
|
||||
case 9160: return "SB 3600TL-20";
|
||||
case 9168: return "SC630HE-11";
|
||||
case 9169: return "SC500HE-11";
|
||||
case 9170: return "SC400HE-11";
|
||||
case 9171: return "WB 3000TL-21";
|
||||
case 9172: return "WB 3600TL-21";
|
||||
case 9173: return "WB 4000TL-21";
|
||||
case 9174: return "WB 5000TL-21";
|
||||
case 9175: return "SC 250";
|
||||
case 9176: return "SMA Meteo Station";
|
||||
case 9177: return "SB 240-10";
|
||||
case 9179: return "Multigate-10";
|
||||
case 9180: return "Multigate-US-10";
|
||||
case 9181: return "STP 20000TLEE-10";
|
||||
case 9182: return "STP 15000TLEE-10";
|
||||
case 9183: return "SB 2000TLST-21";
|
||||
case 9184: return "SB 2500TLST-21";
|
||||
case 9185: return "SB 3000TLST-21";
|
||||
case 9186: return "WB 2000TLST-21";
|
||||
case 9187: return "WB 2500TLST-21";
|
||||
case 9188: return "WB 3000TLST-21";
|
||||
case 9189: return "WTP 5000TL-20";
|
||||
case 9190: return "WTP 6000TL-20";
|
||||
case 9191: return "WTP 7000TL-20";
|
||||
case 9192: return "WTP 8000TL-20";
|
||||
case 9193: return "WTPcase 9000TL-20";
|
||||
case 9254: return "Sunny Island 3324";
|
||||
case 9255: return "Sunny Island 4.0M";
|
||||
case 9256: return "Sunny Island 4248";
|
||||
case 9257: return "Sunny Island 4248U";
|
||||
case 9258: return "Sunny Island 4500";
|
||||
case 9259: return "Sunny Island 4548U";
|
||||
case 9260: return "Sunny Island 5.4M";
|
||||
case 9261: return "Sunny Island 5048U";
|
||||
case 9262: return "Sunny Island 6048U";
|
||||
case 9278: return "Sunny Island 3.0M";
|
||||
case 9279: return "Sunny Island 4.4M";
|
||||
case 9281: return "STP 10000TL-20";
|
||||
case 9282: return "STP 11000TL-20";
|
||||
case 9283: return "STP 12000TL-20";
|
||||
case 9284: return "STP 20000TL-30";
|
||||
case 9285: return "STP 25000TL-30";
|
||||
case 9301: return "SB1.5-1VL-40";
|
||||
case 9302: return "SB2.5-1VL-40";
|
||||
case 9303: return "SB2.0-1VL-40";
|
||||
case 9304: return "SB5.0-1SP-US-40";
|
||||
case 9305: return "SB6.0-1SP-US-40";
|
||||
case 9306: return "SB8.0-1SP-US-40";
|
||||
case 9307: return "Energy Meter";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#endif // SMA_H
|
||||
BIN
sma/sma.png
Normal file
BIN
sma/sma.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
34
sma/sma.pro
Normal file
34
sma/sma.pro
Normal file
@ -0,0 +1,34 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT += network
|
||||
|
||||
# Generate modbus connection
|
||||
MODBUS_CONNECTIONS += sma-inverter-registers.json
|
||||
MODBUS_TOOLS_CONFIG += VERBOSE
|
||||
include(../modbus.pri)
|
||||
|
||||
SOURCES += \
|
||||
integrationpluginsma.cpp \
|
||||
modbus/smamodbusdiscovery.cpp \
|
||||
speedwire/speedwirediscovery.cpp \
|
||||
speedwire/speedwireinterface.cpp \
|
||||
speedwire/speedwireinverter.cpp \
|
||||
speedwire/speedwireinverterreply.cpp \
|
||||
speedwire/speedwireinverterrequest.cpp \
|
||||
speedwire/speedwiremeter.cpp \
|
||||
sunnywebbox/sunnywebbox.cpp \
|
||||
sunnywebbox/sunnywebboxdiscovery.cpp
|
||||
|
||||
HEADERS += \
|
||||
integrationpluginsma.h \
|
||||
modbus/smamodbusdiscovery.h \
|
||||
sma.h \
|
||||
speedwire/speedwire.h \
|
||||
speedwire/speedwirediscovery.h \
|
||||
speedwire/speedwireinterface.h \
|
||||
speedwire/speedwireinverter.h \
|
||||
speedwire/speedwireinverterreply.h \
|
||||
speedwire/speedwireinverterrequest.h \
|
||||
speedwire/speedwiremeter.h \
|
||||
sunnywebbox/sunnywebbox.h \
|
||||
sunnywebbox/sunnywebboxdiscovery.h
|
||||
183
sma/speedwire/speedwire.h
Normal file
183
sma/speedwire/speedwire.h
Normal file
@ -0,0 +1,183 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIRE_H
|
||||
#define SPEEDWIRE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QDataStream>
|
||||
#include <QHostAddress>
|
||||
#include <QHash>
|
||||
|
||||
class Speedwire
|
||||
{
|
||||
Q_GADGET
|
||||
public:
|
||||
enum Command {
|
||||
CommandIdentify = 0x00000201,
|
||||
CommandQueryStatus = 0x51800200,
|
||||
CommandQueryAc = 0x51000200,
|
||||
CommandQueryDc = 0x53800200,
|
||||
CommandQueryEnergy = 0x54000200,
|
||||
CommandQueryDevice = 0x58000200,
|
||||
CommandQueryDeviceResponse = 58000201,
|
||||
CommandLogin = 0xfffd040c,
|
||||
CommandLogout = 0xfffd010e,
|
||||
CommandLoginResponse = 0x0ffdf40d
|
||||
};
|
||||
Q_ENUM(Command)
|
||||
|
||||
enum ProtocolId {
|
||||
ProtocolIdUnknown = 0x0000,
|
||||
ProtocolIdMeter = 0x6069,
|
||||
ProtocolIdInverter = 0x6065,
|
||||
ProtocolIdDiscoveryResponse = 0x0001,
|
||||
ProtocolIdDiscovery = 0xffff
|
||||
};
|
||||
Q_ENUM(ProtocolId)
|
||||
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
Header() = default;
|
||||
quint32 smaSignature = 0;
|
||||
quint16 headerLength = 0;
|
||||
quint16 tagType = 0;
|
||||
quint16 tagVersion = 0;
|
||||
quint16 group = 0;
|
||||
quint16 payloadLength = 0;
|
||||
quint16 smaNet2Version = 0;
|
||||
ProtocolId protocolId = ProtocolIdUnknown;
|
||||
|
||||
inline bool isValid() const {
|
||||
return smaSignature == Speedwire::smaSignature() && protocolId != ProtocolIdUnknown;
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct InverterPacket {
|
||||
quint8 wordCount = 0;
|
||||
quint8 control = 0;
|
||||
quint16 destinationModelId = 0;
|
||||
quint32 destinationSerialNumber = 0;
|
||||
quint16 destinationControl = 0;
|
||||
quint16 sourceModelId = 0;
|
||||
quint32 sourceSerialNumber = 0;
|
||||
quint16 sourceControl = 0;
|
||||
quint16 errorCode = 0;
|
||||
quint16 fragmentId = 0;
|
||||
quint16 packetId = 0;
|
||||
quint32 command = 0;
|
||||
} InverterPacket;
|
||||
|
||||
Speedwire() = default;
|
||||
|
||||
//static QHash<quint16, QString> deviceTypes = { {0x0000, "Unknwon"} };
|
||||
|
||||
static quint16 port() { return 9522; }
|
||||
static QHostAddress multicastAddress() { return QHostAddress("239.12.255.254"); }
|
||||
static quint32 smaSignature() { return 0x534d4100; }
|
||||
static quint16 tag0() { return 0x02a0; }
|
||||
static quint16 tagVersion() { return 0; }
|
||||
static quint16 smaNet2Version() { return 0x0010; }
|
||||
|
||||
|
||||
// Multicast device discovery request packet, according to SMA documentation.
|
||||
// However, this does not seem to be supported anymore with version 3.x devices
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, // 0xffffffff group, 0x0000 length, 0x0020 "SMA Net ?", Version ?
|
||||
// 0x00, 0x00, 0x00, 0x00 // 0x0000 protocol, 0x00 #long words, 0x00 ctrl
|
||||
|
||||
// Unicast device discovery request packet, according to SMA documentation
|
||||
// 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0
|
||||
// 0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0x00, 0x10, // 0x26 length, 0x0010 "SMA Net 2", Version 0
|
||||
// 0x60, 0x65, 0x09, 0xa0, 0xff, 0xff, 0xff, 0xff, // 0x6065 protocol, 0x09 #long words, 0xa0 ctrl, 0xffff dst susyID any, 0xffffffff dst serial any
|
||||
// 0xff, 0xff, 0x00, 0x00, 0x7d, 0x00, 0x52, 0xbe, // 0x0000 dst cntrl, 0x007d src susy id, 0x3a28be52 src serial
|
||||
// 0x28, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000 src cntrl, 0x0000 error code, 0x0000 fragment ID
|
||||
// 0x01, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // 0x8001 packet ID
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x00
|
||||
|
||||
static QByteArray discoveryDatagramMulticast() { return QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000"); }
|
||||
static QByteArray discoveryResponseDatagram() { return QByteArray::fromHex("534d4100000402A000000001000200000001"); }
|
||||
static QByteArray discoveryDatagramUnicast() { return QByteArray::fromHex("534d4100000402a00000000100260010606509a0ffffffffffff00007d0052be283a000000000000018000020000000000000000000000000000"); }
|
||||
|
||||
static Speedwire::Header parseHeader(QDataStream &stream) {
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
Header header;
|
||||
quint16 protocolId;
|
||||
stream >> header.smaSignature >> header.headerLength;
|
||||
stream >> header.tagType >> header.tagVersion >> header.group;
|
||||
stream >> header.payloadLength >> header.smaNet2Version;
|
||||
stream >> protocolId;
|
||||
header.protocolId = static_cast<ProtocolId>(protocolId);
|
||||
return header;
|
||||
};
|
||||
|
||||
static Speedwire::InverterPacket parseInverterPacket(QDataStream &stream) {
|
||||
// Make sure the data stream is little endian
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
InverterPacket packet;
|
||||
stream >> packet.wordCount;
|
||||
stream >> packet.control;
|
||||
stream >> packet.destinationModelId;
|
||||
stream >> packet.destinationSerialNumber;
|
||||
stream >> packet.destinationControl;
|
||||
stream >> packet.sourceModelId;
|
||||
stream >> packet.sourceSerialNumber;
|
||||
stream >> packet.sourceControl;
|
||||
stream >> packet.errorCode;
|
||||
stream >> packet.fragmentId;
|
||||
stream >> packet.packetId;
|
||||
stream >> packet.command;
|
||||
return packet;
|
||||
};
|
||||
};
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Speedwire::Header &header)
|
||||
{
|
||||
debug.nospace() << "SpeedwireHeader(" << header.protocolId << ", payload size: " << header.payloadLength << ", group: " << header.payloadLength << ")";
|
||||
return debug.maybeSpace();
|
||||
}
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const Speedwire::InverterPacket &packet)
|
||||
{
|
||||
debug.nospace() << "InverterPacket(" << packet.sourceSerialNumber;
|
||||
debug.nospace() << ", Model ID: " << packet.sourceModelId;
|
||||
debug.nospace() << ", command: " << packet.command;
|
||||
debug.nospace() << ", error: " << packet.errorCode;
|
||||
debug.nospace() << ", fragment: " << packet.fragmentId;
|
||||
debug.nospace() << ", packet ID: " << packet.fragmentId;
|
||||
debug.nospace() << ")";
|
||||
return debug.maybeSpace();
|
||||
}
|
||||
|
||||
|
||||
#endif // SPEEDWIRE_H
|
||||
348
sma/speedwire/speedwirediscovery.cpp
Normal file
348
sma/speedwire/speedwirediscovery.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "speedwirediscovery.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_networkDeviceDiscovery(networkDeviceDiscovery)
|
||||
{
|
||||
// More details: https://github.com/RalfOGit/libspeedwire/
|
||||
|
||||
|
||||
|
||||
// // Request: 534d4100000402a00000000100260010 606509a0 ffffffffffff0000 7d0052be283a0000 000000000180 00020000 00000000 00000000 00000000 => command = 0x00000200, first = 0x00000000; last = 0x00000000; trailer = 0x00000000
|
||||
// // Response 534d4100000402a000000001004e0010 606513a0 7d0052be283a00c0 7a01842a71b30000 000000000180 01020000 00000000 00000000 00030000 00ff0000 00000000 01007a01 842a71b3 00000a00 0c000000 00000000 00000000 01010000 00000000
|
||||
|
||||
// qCDebug(dcSma()) << "SpeedwireDiscovery: Create speed wire interface for multicast" << m_multicastAddress.toString() << "on port" << m_port;
|
||||
// QByteArray exampleData = QByteArray::fromHex("534d4100000402a000000001024400106069010e714369aee618a41600010400000000000001080000000021391229100002040000004415000208000000001575a137d800030400000000000003080000000003debed0e800040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e900000000102085200000000");
|
||||
// processDatagram(QHostAddress("127.0.0.1"), m_port, exampleData);
|
||||
|
||||
m_multicastSocket = new QUdpSocket(this);
|
||||
connect(m_multicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsMulticast);
|
||||
connect(m_multicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged);
|
||||
connect(m_multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
m_unicastSocket = new QUdpSocket(this);
|
||||
connect(m_unicastSocket, &QUdpSocket::readyRead, this, &SpeedwireDiscovery::readPendingDatagramsUnicast);
|
||||
connect(m_unicastSocket, &QUdpSocket::stateChanged, this, &SpeedwireDiscovery::onSocketStateChanged);
|
||||
connect(m_unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError)));
|
||||
|
||||
m_discoveryTimer.setInterval(1000);
|
||||
m_discoveryTimer.setSingleShot(false);
|
||||
connect(&m_discoveryTimer, &QTimer::timeout, this, &SpeedwireDiscovery::sendDiscoveryRequest);
|
||||
}
|
||||
|
||||
SpeedwireDiscovery::~SpeedwireDiscovery()
|
||||
{
|
||||
if (m_initialized) {
|
||||
if (!m_multicastSocket->leaveMulticastGroup(m_multicastAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << m_multicastAddress.toString();
|
||||
}
|
||||
|
||||
m_multicastSocket->close();
|
||||
}
|
||||
}
|
||||
|
||||
bool SpeedwireDiscovery::initialize()
|
||||
{
|
||||
m_multicastSocket->close();
|
||||
m_initialized = false;
|
||||
|
||||
// Setup multicast socket
|
||||
if (!m_multicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind multicast socket to port" << m_port << m_multicastSocket->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_multicastSocket->joinMulticastGroup(m_multicastAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_multicastSocket->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup unicast socket
|
||||
if (!m_unicastSocket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind to port" << m_port << m_multicastSocket->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Interface initialized successfully.";
|
||||
m_initialized = true;
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
bool SpeedwireDiscovery::initialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
bool SpeedwireDiscovery::startDiscovery()
|
||||
{
|
||||
// 1. Discover all network devices
|
||||
// 2. Send upd multicast and unicast messages to verify if it is a SMA speedwire device
|
||||
|
||||
if (m_discoveryRunning)
|
||||
return true;
|
||||
|
||||
if (!m_initialized) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Failed to start discovery because the socket has not been initialized successfully.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// CLean up
|
||||
m_results.clear();
|
||||
m_networkDeviceInfos.clear();
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Start discovering network...";
|
||||
NetworkDeviceDiscoveryReply *discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, discoveryReply, &NetworkDeviceDiscoveryReply::deleteLater);
|
||||
connect(discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "devices";
|
||||
m_networkDeviceInfos = discoveryReply->networkDeviceInfos();
|
||||
|
||||
foreach (const NetworkDeviceInfo &networkDeviceInfo, discoveryReply->networkDeviceInfos()) {
|
||||
// 2. Send unicast to all results and start requesting on multicast address
|
||||
sendUnicastDiscoveryRequest(networkDeviceInfo.address());
|
||||
}
|
||||
|
||||
startMulticastDiscovery();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpeedwireDiscovery::discoveryRunning() const
|
||||
{
|
||||
return m_discoveryRunning;
|
||||
}
|
||||
|
||||
QList<SpeedwireDiscovery::SpeedwireDiscoveryResult> SpeedwireDiscovery::discoveryResult() const
|
||||
{
|
||||
return m_results.values();
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::startMulticastDiscovery()
|
||||
{
|
||||
// Start sending multicast messages
|
||||
sendDiscoveryRequest();
|
||||
|
||||
m_discoveryRunning = true;
|
||||
QTimer::singleShot(5000, this, &SpeedwireDiscovery::onDiscoveryProcessFinished);
|
||||
|
||||
m_discoveryTimer.start();
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress)
|
||||
{
|
||||
if (m_unicastSocket->writeDatagram(Speedwire::discoveryDatagramUnicast(), targetHostAddress, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send unicast discovery datagram to address" << targetHostAddress.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Sent successfully the discovery request to unicast address" << targetHostAddress.toString();
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::readPendingDatagramsMulticast()
|
||||
{
|
||||
QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());
|
||||
|
||||
QByteArray datagram;
|
||||
QHostAddress senderAddress;
|
||||
quint16 senderPort;
|
||||
|
||||
while (socket->hasPendingDatagrams()) {
|
||||
datagram.resize(socket->pendingDatagramSize());
|
||||
socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Received multicast data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
||||
//qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex();
|
||||
processDatagram(senderAddress, senderPort, datagram);
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::readPendingDatagramsUnicast()
|
||||
{
|
||||
QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());
|
||||
|
||||
QByteArray datagram;
|
||||
QHostAddress senderAddress;
|
||||
quint16 senderPort;
|
||||
|
||||
while (socket->hasPendingDatagrams()) {
|
||||
datagram.resize(socket->pendingDatagramSize());
|
||||
socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Received unicast data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
||||
//qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex();
|
||||
processDatagram(senderAddress, senderPort, datagram);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SpeedwireDiscovery::onSocketError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Socket error" << error;
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::onSocketStateChanged(QAbstractSocket::SocketState socketState)
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Socket state changed" << socketState;
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &datagram)
|
||||
{
|
||||
// Check min size of SMA datagrams
|
||||
if (datagram.size() < 18) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Received datagram is to short to be a SMA speedwire message. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore discovery requests
|
||||
if (datagram == Speedwire::discoveryDatagramMulticast() || datagram == Speedwire::discoveryDatagramUnicast())
|
||||
return;
|
||||
|
||||
QDataStream stream(datagram);
|
||||
Speedwire::Header header = Speedwire::parseHeader(stream);
|
||||
if (!header.isValid()) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Datagram header is not valid. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery:" << header;
|
||||
|
||||
if (header.protocolId == Speedwire::ProtocolIdDiscoveryResponse) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Received discovery response from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
||||
|
||||
// "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0003 0004 0020 0000 0001 0004 0030 c0a8 b219 0004 0040 0000 0000 0002 0070 ef0c 00000000"
|
||||
// "534d4100 0004 02a0 0000 0001 0002 0000 0001 0004 0010 0001 0001 0004 0020 0000 0001 0004 0030 c0a8 b216 0004 0040 0000 0001 00000000"
|
||||
|
||||
if (!datagram.startsWith(Speedwire::discoveryResponseDatagram())) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Received discovery reply but the message start does not match the required schema. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_results.contains(senderAddress)) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: --> Found SMA device on" << senderAddress.toString();
|
||||
if (!m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Found SMA using UDP discovery but the host is not in the network discovery result list. Not adding to results" << senderAddress.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
SpeedwireDiscoveryResult result;
|
||||
result.address = senderAddress;
|
||||
if (m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
result.networkDeviceInfo = m_networkDeviceInfos.get(senderAddress);
|
||||
}
|
||||
result.deviceType = SpeedwireInterface::DeviceTypeUnknown;
|
||||
m_results.insert(senderAddress, result);
|
||||
} else {
|
||||
if (m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We received SMA data, let's parse depending on the protocol id
|
||||
|
||||
if (header.protocolId == Speedwire::ProtocolIdMeter) {
|
||||
// Example: 010e 714369ae
|
||||
quint16 modelId;
|
||||
quint32 serialNumber;
|
||||
stream >> modelId >> serialNumber;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Meter identifier: Model ID:" << modelId << "Serial number:" << serialNumber;
|
||||
|
||||
if (!m_results.contains(senderAddress)) {
|
||||
SpeedwireDiscoveryResult result;
|
||||
result.address = senderAddress;
|
||||
result.deviceType = SpeedwireInterface::DeviceTypeMeter;
|
||||
m_results.insert(senderAddress, result);
|
||||
}
|
||||
|
||||
if (m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress);
|
||||
}
|
||||
|
||||
m_results[senderAddress].modelId = modelId;
|
||||
m_results[senderAddress].serialNumber = serialNumber;
|
||||
} else if (header.protocolId == Speedwire::ProtocolIdInverter) {
|
||||
Speedwire::InverterPacket inverterPacket = Speedwire::parseInverterPacket(stream);
|
||||
// Response from inverter 534d4100 0004 02a0 0000 0001 004e 0010 6065 1390 7d00 52be283a 0000 b500 c2c12e12 0000 0000 00000 1800102000000000000000000000003000000ff0000ecd5ff1f0100b500c2c12e1200000a000c00000000000000030000000101000000000000
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery:" << inverterPacket;
|
||||
|
||||
if (!m_results.contains(senderAddress)) {
|
||||
SpeedwireDiscoveryResult result;
|
||||
result.address = senderAddress;
|
||||
result.deviceType = SpeedwireInterface::DeviceTypeInverter;
|
||||
m_results.insert(senderAddress, result);
|
||||
}
|
||||
|
||||
if (m_networkDeviceInfos.hasHostAddress(senderAddress)) {
|
||||
m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress);
|
||||
}
|
||||
|
||||
m_results[senderAddress].modelId = inverterPacket.sourceModelId;
|
||||
m_results[senderAddress].serialNumber = inverterPacket.sourceSerialNumber;
|
||||
} else {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::sendDiscoveryRequest()
|
||||
{
|
||||
if (m_multicastSocket->writeDatagram(Speedwire::discoveryDatagramMulticast(), m_multicastAddress, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send discovery datagram to multicast address" << m_multicastAddress.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Sent successfully the discovery request to multicast address" << m_multicastAddress.toString();
|
||||
}
|
||||
|
||||
void SpeedwireDiscovery::onDiscoveryProcessFinished()
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Discovey finished. Found" << m_results.count() << "SMA devices in the network";
|
||||
m_discoveryTimer.stop();
|
||||
m_discoveryRunning = false;
|
||||
|
||||
foreach (const SpeedwireDiscoveryResult &result, m_results) {
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: ============================================";
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Device type:" << result.deviceType;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Address:" << result.address.toString();
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Hostname:" << result.networkDeviceInfo.hostName();
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: MAC:" << result.networkDeviceInfo.macAddress();
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: MAC manufacturer:" << result.networkDeviceInfo.macAddressManufacturer();
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << result.modelId;
|
||||
qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << result.serialNumber;
|
||||
}
|
||||
|
||||
emit discoveryFinished();
|
||||
}
|
||||
100
sma/speedwire/speedwirediscovery.h
Normal file
100
sma/speedwire/speedwirediscovery.h
Normal file
@ -0,0 +1,100 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIREDISCOVERY_H
|
||||
#define SPEEDWIREDISCOVERY_H
|
||||
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
#include "speedwire.h"
|
||||
#include "speedwireinterface.h"
|
||||
|
||||
class SpeedwireDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef struct SpeedwireDiscoveryResult {
|
||||
QHostAddress address;
|
||||
NetworkDeviceInfo networkDeviceInfo;
|
||||
SpeedwireInterface::DeviceType deviceType = SpeedwireInterface::DeviceTypeUnknown;
|
||||
quint16 modelId = 0;
|
||||
quint32 serialNumber = 0;
|
||||
} SpeedwireDiscoveryResult;
|
||||
|
||||
explicit SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
|
||||
~SpeedwireDiscovery();
|
||||
|
||||
bool initialize();
|
||||
bool initialized() const;
|
||||
|
||||
bool startDiscovery();
|
||||
bool discoveryRunning() const;
|
||||
|
||||
QList<SpeedwireDiscoveryResult> discoveryResult() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private:
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
QUdpSocket *m_multicastSocket = nullptr;
|
||||
QUdpSocket *m_unicastSocket = nullptr;
|
||||
QHostAddress m_multicastAddress = Speedwire::multicastAddress();
|
||||
quint16 m_port = Speedwire::port();
|
||||
bool m_initialized = false;
|
||||
|
||||
// Discovery
|
||||
QTimer m_discoveryTimer;
|
||||
bool m_discoveryRunning = false;
|
||||
NetworkDeviceInfos m_networkDeviceInfos;
|
||||
QHash<QHostAddress, SpeedwireDiscoveryResult> m_results;
|
||||
|
||||
void startMulticastDiscovery();
|
||||
void sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress);
|
||||
|
||||
private slots:
|
||||
void readPendingDatagramsMulticast();
|
||||
void readPendingDatagramsUnicast();
|
||||
void onSocketError(QAbstractSocket::SocketError error);
|
||||
void onSocketStateChanged(QAbstractSocket::SocketState socketState);
|
||||
|
||||
void processDatagram(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &datagram);
|
||||
|
||||
void sendDiscoveryRequest();
|
||||
|
||||
void onDiscoveryProcessFinished();
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREDISCOVERY_H
|
||||
134
sma/speedwire/speedwireinterface.cpp
Normal file
134
sma/speedwire/speedwireinterface.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "speedwireinterface.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
SpeedwireInterface::SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_address(address),
|
||||
m_multicast(multicast)
|
||||
{
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: Create interface for" << address.toString() << (multicast ? "multicast" : "unicast");
|
||||
m_socket = new QUdpSocket(this);
|
||||
connect(m_socket, &QUdpSocket::readyRead, this, &SpeedwireInterface::readPendingDatagrams);
|
||||
connect(m_socket, &QUdpSocket::stateChanged, this, &SpeedwireInterface::onSocketStateChanged);
|
||||
connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError)));
|
||||
}
|
||||
|
||||
SpeedwireInterface::~SpeedwireInterface()
|
||||
{
|
||||
deinitialize();
|
||||
}
|
||||
|
||||
bool SpeedwireInterface::initialize()
|
||||
{
|
||||
if (!m_socket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not bind to port" << m_port;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_multicast && !m_socket->joinMulticastGroup(m_multicastAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_socket->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: Interface initialized successfully.";
|
||||
m_initialized = true;
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
void SpeedwireInterface::deinitialize()
|
||||
{
|
||||
if (m_initialized) {
|
||||
if (m_multicast) {
|
||||
if (!m_socket->leaveMulticastGroup(m_multicastAddress)) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString();
|
||||
}
|
||||
}
|
||||
|
||||
m_socket->close();
|
||||
m_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpeedwireInterface::initialized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
quint16 SpeedwireInterface::sourceModelId() const
|
||||
{
|
||||
return m_sourceModelId;
|
||||
}
|
||||
|
||||
quint32 SpeedwireInterface::sourceSerialNumber() const
|
||||
{
|
||||
return m_sourceSerialNumber;
|
||||
}
|
||||
|
||||
void SpeedwireInterface::sendData(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: -->" << m_address.toString() << m_port << data.toHex();
|
||||
if (m_socket->writeDatagram(data, m_address, m_port) < 0) {
|
||||
qCWarning(dcSma()) << "SpeedwireInterface: failed to send data" << m_socket->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireInterface::readPendingDatagrams()
|
||||
{
|
||||
QByteArray datagram;
|
||||
QHostAddress senderAddress;
|
||||
quint16 senderPort;
|
||||
|
||||
while (m_socket->hasPendingDatagrams()) {
|
||||
datagram.resize(m_socket->pendingDatagramSize());
|
||||
m_socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);
|
||||
|
||||
// Process only data coming from our target address
|
||||
if (senderAddress != m_address)
|
||||
continue;
|
||||
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: Received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
|
||||
//qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
|
||||
emit dataReceived(datagram);
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireInterface::onSocketError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: Socket error" << error;
|
||||
}
|
||||
|
||||
void SpeedwireInterface::onSocketStateChanged(QAbstractSocket::SocketState socketState)
|
||||
{
|
||||
qCDebug(dcSma()) << "SpeedwireInterface: Socket state changed" << socketState;
|
||||
}
|
||||
88
sma/speedwire/speedwireinterface.h
Normal file
88
sma/speedwire/speedwireinterface.h
Normal file
@ -0,0 +1,88 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIREINTERFACE_H
|
||||
#define SPEEDWIREINTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QUdpSocket>
|
||||
#include <QDataStream>
|
||||
|
||||
#include "speedwire.h"
|
||||
|
||||
class SpeedwireInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum DeviceType {
|
||||
DeviceTypeUnknown,
|
||||
DeviceTypeMeter,
|
||||
DeviceTypeInverter
|
||||
};
|
||||
Q_ENUM(DeviceType)
|
||||
|
||||
explicit SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent = nullptr);
|
||||
~SpeedwireInterface();
|
||||
|
||||
bool initialize();
|
||||
void deinitialize();
|
||||
|
||||
bool initialized() const;
|
||||
|
||||
quint16 sourceModelId() const;
|
||||
quint32 sourceSerialNumber() const;
|
||||
|
||||
public slots:
|
||||
void sendData(const QByteArray &data);
|
||||
|
||||
signals:
|
||||
void dataReceived(const QByteArray &data);
|
||||
|
||||
private:
|
||||
QUdpSocket *m_socket = nullptr;
|
||||
QHostAddress m_address;
|
||||
quint16 m_port = Speedwire::port();
|
||||
QHostAddress m_multicastAddress = Speedwire::multicastAddress();
|
||||
bool m_multicast = false;
|
||||
bool m_initialized = false;
|
||||
|
||||
// Requester
|
||||
quint16 m_sourceModelId = 0x007d;
|
||||
quint32 m_sourceSerialNumber = 0x3a28be52;
|
||||
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
void onSocketError(QAbstractSocket::SocketError error);
|
||||
void onSocketStateChanged(QAbstractSocket::SocketState socketState);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // SPEEDWIREINTERFACE_H
|
||||
1273
sma/speedwire/speedwireinverter.cpp
Normal file
1273
sma/speedwire/speedwireinverter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
203
sma/speedwire/speedwireinverter.h
Normal file
203
sma/speedwire/speedwireinverter.h
Normal file
@ -0,0 +1,203 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIREINVERTER_H
|
||||
#define SPEEDWIREINVERTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
|
||||
#include "sma.h"
|
||||
#include "speedwire.h"
|
||||
#include "speedwireinterface.h"
|
||||
#include "speedwireinverterreply.h"
|
||||
#include "speedwireinverterrequest.h"
|
||||
|
||||
class SpeedwireInverter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum State {
|
||||
StateIdle,
|
||||
StateDisconnected,
|
||||
StateInitializing,
|
||||
StateLogin,
|
||||
StateGetInformation,
|
||||
StateQueryData
|
||||
};
|
||||
Q_ENUM(State)
|
||||
|
||||
explicit SpeedwireInverter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr);
|
||||
|
||||
bool initialize();
|
||||
bool initialized() const;
|
||||
|
||||
State state() const;
|
||||
|
||||
bool reachable() const;
|
||||
|
||||
Sma::DeviceClass deviceClass() const;
|
||||
QString modelName() const;
|
||||
|
||||
double totalAcPower() const;
|
||||
|
||||
double gridFrequency() const;
|
||||
|
||||
double totalEnergyProduced() const;
|
||||
double todayEnergyProduced() const;
|
||||
|
||||
double voltageAcPhase1() const;
|
||||
double voltageAcPhase2() const;
|
||||
double voltageAcPhase3() const;
|
||||
|
||||
double currentAcPhase1() const;
|
||||
double currentAcPhase2() const;
|
||||
double currentAcPhase3() const;
|
||||
|
||||
double powerAcPhase1() const;
|
||||
double powerAcPhase2() const;
|
||||
double powerAcPhase3() const;
|
||||
|
||||
double powerDcMpp1() const;
|
||||
double powerDcMpp2() const;
|
||||
|
||||
double voltageDcMpp1() const;
|
||||
double voltageDcMpp2() const;
|
||||
|
||||
double currentDcMpp1() const;
|
||||
double currentDcMpp2() const;
|
||||
|
||||
// Query methods
|
||||
SpeedwireInverterReply *sendIdentifyRequest();
|
||||
SpeedwireInverterReply *sendLoginRequest(const QString &password = "0000", bool loginAsUser = true);
|
||||
SpeedwireInverterReply *sendLogoutRequest();
|
||||
SpeedwireInverterReply *sendSoftwareVersionRequest();
|
||||
SpeedwireInverterReply *sendDeviceTypeRequest();
|
||||
|
||||
// Start connecting
|
||||
void startConnecting(const QString &password = "0000");
|
||||
|
||||
public slots:
|
||||
void refresh();
|
||||
|
||||
signals:
|
||||
void reachableChanged(bool reachable);
|
||||
void loginFinished(bool success);
|
||||
void stateChanged(State state);
|
||||
void valuesUpdated();
|
||||
|
||||
private:
|
||||
SpeedwireInterface *m_interface = nullptr;
|
||||
QHostAddress m_address;
|
||||
QString m_password;
|
||||
|
||||
bool m_initialized = false;
|
||||
quint16 m_modelId = 0;
|
||||
quint32 m_serialNumber = 0;
|
||||
|
||||
bool m_reachable = false;
|
||||
State m_state = StateDisconnected;
|
||||
quint16 m_packetId = 1;
|
||||
|
||||
bool m_deviceInformationFetched = false;
|
||||
|
||||
SpeedwireInverterReply *m_currentReply = nullptr;
|
||||
QQueue<SpeedwireInverterReply *> m_replyQueue;
|
||||
|
||||
// Properties
|
||||
Sma::DeviceClass m_deviceClass = Sma::DeviceClassUnknown;
|
||||
QString m_modelName;
|
||||
QString m_softwareVersion;
|
||||
|
||||
double m_totalAcPower = 0;
|
||||
double m_totalEnergyProduced = 0;
|
||||
double m_todayEnergyProduced = 0;
|
||||
|
||||
double m_gridFrequency = 0;
|
||||
|
||||
double m_voltageAcPhase1 = 0;
|
||||
double m_voltageAcPhase2 = 0;
|
||||
double m_voltageAcPhase3 = 0;
|
||||
|
||||
double m_currentAcPhase1 = 0;
|
||||
double m_currentAcPhase2 = 0;
|
||||
double m_currentAcPhase3 = 0;
|
||||
|
||||
double m_powerAcPhase1 = 0;
|
||||
double m_powerAcPhase2 = 0;
|
||||
double m_powerAcPhase3 = 0;
|
||||
|
||||
double m_powerDcMpp1 = 0;
|
||||
double m_powerDcMpp2 = 0;
|
||||
|
||||
double m_voltageDcMpp1 = 0;
|
||||
double m_voltageDcMpp2 = 0;
|
||||
|
||||
double m_currentDcMpp1 = 0;
|
||||
double m_currentDcMpp2 = 0;
|
||||
|
||||
void setState(State state);
|
||||
|
||||
void sendNextReply();
|
||||
SpeedwireInverterReply *createReply(const SpeedwireInverterRequest &request);
|
||||
|
||||
// Request builder function
|
||||
void buildDefaultHeader(QDataStream &stream, quint16 payloadSize = 38, quint8 control = 0xa0);
|
||||
void buildPacket(QDataStream &stream, quint32 command, quint16 packetId);
|
||||
|
||||
// Send generic request for internal use
|
||||
SpeedwireInverterReply *sendQueryRequest(Speedwire::Command command, quint32 firstWord, quint32 secondWord);
|
||||
|
||||
// Response process methods
|
||||
void processSoftwareVersionResponse(const QByteArray &response);
|
||||
void processDeviceTypeResponse(const QByteArray &response);
|
||||
void processAcPowerResponse(const QByteArray &response);
|
||||
void processAcVoltageCurrentResponse(const QByteArray &response);
|
||||
void processAcTotalPowerResponse(const QByteArray &response);
|
||||
void processDcPowerResponse(const QByteArray &response);
|
||||
void processDcVoltageCurrentResponse(const QByteArray &response);
|
||||
void processEnergyProductionResponse(const QByteArray &response);
|
||||
void processGridFrequencyResponse(const QByteArray &response);
|
||||
void processInverterStatusResponse(const QByteArray &response);
|
||||
|
||||
void readUntilEndOfMeasurement(QDataStream &stream);
|
||||
double readValue(quint32 value, double divisor = 1.0);
|
||||
|
||||
void setReachable(bool reachable);
|
||||
|
||||
private slots:
|
||||
void processData(const QByteArray &data);
|
||||
|
||||
void onReplyTimeout();
|
||||
void onReplyFinished();
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTER_H
|
||||
85
sma/speedwire/speedwireinverterreply.cpp
Normal file
85
sma/speedwire/speedwireinverterreply.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "speedwireinverterreply.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
SpeedwireInverterReply::SpeedwireInverterReply(const SpeedwireInverterRequest &request, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_request(request)
|
||||
{
|
||||
m_maxRetries = m_request.retries();
|
||||
|
||||
m_timer.setInterval(m_timeout);
|
||||
m_timer.setSingleShot(true);
|
||||
connect(&m_timer, &QTimer::timeout, this, &SpeedwireInverterReply::timeout);
|
||||
}
|
||||
|
||||
SpeedwireInverterRequest SpeedwireInverterReply::request() const
|
||||
{
|
||||
return m_request;
|
||||
}
|
||||
|
||||
SpeedwireInverterReply::Error SpeedwireInverterReply::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterReply::responseData() const
|
||||
{
|
||||
return m_responseData;
|
||||
}
|
||||
|
||||
Speedwire::Header SpeedwireInverterReply::responseHeader() const
|
||||
{
|
||||
return m_responseHeader;
|
||||
}
|
||||
|
||||
Speedwire::InverterPacket SpeedwireInverterReply::responsePacket() const
|
||||
{
|
||||
return m_responsePacket;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterReply::responsePayload() const
|
||||
{
|
||||
return m_responsePayload;
|
||||
}
|
||||
|
||||
void SpeedwireInverterReply::startWaiting()
|
||||
{
|
||||
m_timer.start();
|
||||
}
|
||||
|
||||
void SpeedwireInverterReply::finishReply(Error error)
|
||||
{
|
||||
m_timer.stop();
|
||||
m_error = error;
|
||||
emit finished();
|
||||
}
|
||||
89
sma/speedwire/speedwireinverterreply.h
Normal file
89
sma/speedwire/speedwireinverterreply.h
Normal file
@ -0,0 +1,89 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIREINVERTERREPLY_H
|
||||
#define SPEEDWIREINVERTERREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "speedwireinverterrequest.h"
|
||||
|
||||
class SpeedwireInverterReply : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend class SpeedwireInverter;
|
||||
|
||||
public:
|
||||
enum Error {
|
||||
ErrorNoError, // Response on, no error
|
||||
ErrorInverterError, // Inverter returned error
|
||||
ErrorTimeout // Request timeouted
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
// Request
|
||||
SpeedwireInverterRequest request() const;
|
||||
|
||||
Error error() const;
|
||||
|
||||
// Response
|
||||
QByteArray responseData() const;
|
||||
Speedwire::Header responseHeader() const;
|
||||
Speedwire::InverterPacket responsePacket() const;
|
||||
QByteArray responsePayload() const;
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
void timeout();
|
||||
|
||||
private:
|
||||
explicit SpeedwireInverterReply(const SpeedwireInverterRequest &request, QObject *parent = nullptr);
|
||||
|
||||
QTimer m_timer;
|
||||
Error m_error = ErrorNoError;
|
||||
SpeedwireInverterRequest m_request;
|
||||
quint8 m_retries = 0;
|
||||
quint8 m_maxRetries = 3;
|
||||
int m_timeout = 3000;
|
||||
|
||||
QByteArray m_responseData;
|
||||
Speedwire::Header m_responseHeader;
|
||||
Speedwire::InverterPacket m_responsePacket;
|
||||
QByteArray m_responsePayload;
|
||||
|
||||
|
||||
void finishReply(Error error);
|
||||
void startWaiting();
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTERREPLY_H
|
||||
76
sma/speedwire/speedwireinverterrequest.cpp
Normal file
76
sma/speedwire/speedwireinverterrequest.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "speedwireinverterrequest.h"
|
||||
|
||||
SpeedwireInverterRequest::SpeedwireInverterRequest()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Speedwire::Command SpeedwireInverterRequest::command() const
|
||||
{
|
||||
return m_command;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setCommand(Speedwire::Command command)
|
||||
{
|
||||
m_command = command;
|
||||
}
|
||||
|
||||
quint16 SpeedwireInverterRequest::packetId() const
|
||||
{
|
||||
return m_packetId;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setPacketId(quint16 packetId)
|
||||
{
|
||||
m_packetId = packetId;
|
||||
}
|
||||
|
||||
QByteArray SpeedwireInverterRequest::requestData() const
|
||||
{
|
||||
return m_requestData;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setRequestData(const QByteArray &requestData)
|
||||
{
|
||||
m_requestData = requestData;
|
||||
}
|
||||
|
||||
quint8 SpeedwireInverterRequest::retries() const
|
||||
{
|
||||
return m_retries;
|
||||
}
|
||||
|
||||
void SpeedwireInverterRequest::setRetries(quint8 retries)
|
||||
{
|
||||
m_retries = retries;
|
||||
}
|
||||
62
sma/speedwire/speedwireinverterrequest.h
Normal file
62
sma/speedwire/speedwireinverterrequest.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SPEEDWIREINVERTERREQUEST_H
|
||||
#define SPEEDWIREINVERTERREQUEST_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "speedwire.h"
|
||||
|
||||
class SpeedwireInverterRequest
|
||||
{
|
||||
public:
|
||||
explicit SpeedwireInverterRequest();
|
||||
|
||||
Speedwire::Command command() const;
|
||||
void setCommand(Speedwire::Command command);
|
||||
|
||||
quint16 packetId() const;
|
||||
void setPacketId(quint16 packetId);
|
||||
|
||||
QByteArray requestData() const;
|
||||
void setRequestData(const QByteArray &requestData);
|
||||
|
||||
quint8 retries() const;
|
||||
void setRetries(quint8 retries);
|
||||
|
||||
private:
|
||||
Speedwire::Command m_command;
|
||||
quint16 m_packetId = 0;
|
||||
QByteArray m_requestData;
|
||||
quint8 m_retries = 2; // Default try 2 times before timeout
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREINVERTERREQUEST_H
|
||||
339
sma/speedwire/speedwiremeter.cpp
Normal file
339
sma/speedwire/speedwiremeter.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, 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 "speedwiremeter.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "sma.h"
|
||||
|
||||
SpeedwireMeter::SpeedwireMeter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_address(address),
|
||||
m_modelId(modelId),
|
||||
m_serialNumber(serialNumber)
|
||||
{
|
||||
m_interface = new SpeedwireInterface(m_address, true, this);
|
||||
connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireMeter::processData);
|
||||
|
||||
// Reachable timestamp
|
||||
m_timer.setInterval(5000);
|
||||
m_timer.setSingleShot(false);
|
||||
connect(&m_timer, &QTimer::timeout, this, &SpeedwireMeter::evaluateReachable);
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::initialize()
|
||||
{
|
||||
bool initSuccess = m_interface->initialize();
|
||||
if (initSuccess)
|
||||
m_timer.start();
|
||||
|
||||
return initSuccess;
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::initialized() const
|
||||
{
|
||||
return m_interface->initialized();
|
||||
}
|
||||
|
||||
bool SpeedwireMeter::reachable() const
|
||||
{
|
||||
return m_reachable;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::currentPower() const
|
||||
{
|
||||
return m_currentPower;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::totalEnergyProduced() const
|
||||
{
|
||||
return m_totalEnergyProduced;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::totalEnergyConsumed() const
|
||||
{
|
||||
return m_totalEnergyConsumed;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyConsumedPhaseA() const
|
||||
{
|
||||
return m_energyConsumedPhaseA;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyConsumedPhaseB() const
|
||||
{
|
||||
return m_energyConsumedPhaseB;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyConsumedPhaseC() const
|
||||
{
|
||||
return m_energyConsumedPhaseC;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyProducedPhaseA() const
|
||||
{
|
||||
return m_energyProducedPhaseA;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyProducedPhaseB() const
|
||||
{
|
||||
return m_energyProducedPhaseB;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::energyProducedPhaseC() const
|
||||
{
|
||||
return m_energyProducedPhaseC;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::currentPowerPhaseA() const
|
||||
{
|
||||
return m_currentPowerPhaseA;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::currentPowerPhaseB() const
|
||||
{
|
||||
return m_currentPowerPhaseB;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::currentPowerPhaseC() const
|
||||
{
|
||||
return m_currentPowerPhaseC;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::voltagePhaseA() const
|
||||
{
|
||||
return m_voltagePhaseA;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::voltagePhaseB() const
|
||||
{
|
||||
return m_voltagePhaseB;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::voltagePhaseC() const
|
||||
{
|
||||
return m_voltagePhaseC;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::amperePhaseA() const
|
||||
{
|
||||
return m_amperePhaseA;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::amperePhaseB() const
|
||||
{
|
||||
return m_amperePhaseB;
|
||||
}
|
||||
|
||||
double SpeedwireMeter::amperePhaseC() const
|
||||
{
|
||||
return m_amperePhaseC;
|
||||
}
|
||||
|
||||
QString SpeedwireMeter::softwareVersion() const
|
||||
{
|
||||
return m_softwareVersion;
|
||||
}
|
||||
|
||||
void SpeedwireMeter::evaluateReachable()
|
||||
{
|
||||
// Note: the meter sends every second the data on the multicast
|
||||
qint64 currentTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000;
|
||||
// If the meter has not sent data within the last 5 seconds it seems not to be reachable
|
||||
bool reachable = false;
|
||||
if (currentTimestamp - m_lastSeenTimestamp < 10) {
|
||||
reachable = true;
|
||||
}
|
||||
|
||||
if (m_reachable != reachable) {
|
||||
qCDebug(dcSma()) << "Meter: reachable changed to" << reachable;
|
||||
m_reachable = reachable;
|
||||
emit reachableChanged(m_reachable);
|
||||
}
|
||||
|
||||
// Restart the timer
|
||||
if (m_reachable) {
|
||||
m_timer.start();
|
||||
} else {
|
||||
// Reachable will be triggered automatically once data arrives
|
||||
// No need to run the timer all the time
|
||||
m_timer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedwireMeter::processData(const QByteArray &data)
|
||||
{
|
||||
qCDebug(dcSma()) << "Meter: data received" << data.toHex();
|
||||
QDataStream stream(data);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
Speedwire::Header header = Speedwire::parseHeader(stream);
|
||||
if (!header.isValid()) {
|
||||
qCDebug(dcSma()) << "Meter: Datagram header is not valid. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.protocolId != Speedwire::ProtocolIdMeter) {
|
||||
qCDebug(dcSma()) << "Meter: received header protocol which is not from the meter protocol. Ignoring data...";
|
||||
return;
|
||||
}
|
||||
|
||||
quint16 modelId;
|
||||
quint32 serialNumber;
|
||||
stream >> modelId >> serialNumber;
|
||||
if (m_modelId != modelId && serialNumber != m_serialNumber) {
|
||||
qCDebug(dcSma()) << "Meter: received meter data from an other meter. Ignoring data...";
|
||||
}
|
||||
|
||||
qCDebug(dcSma()) << "Meter: Model ID:" << modelId;
|
||||
qCDebug(dcSma()) << "Meter: Serial number:" << serialNumber;
|
||||
|
||||
// Parse the packet data
|
||||
// Timestamp e618a416
|
||||
qCDebug(dcSma()) << "Meter: ======================= Meter measurements";
|
||||
quint32 timestamp;
|
||||
stream >> timestamp;
|
||||
qCDebug(dcSma()) << "Meter: Timestamp:" << timestamp;
|
||||
|
||||
// Obis data
|
||||
//00 01 04 00 00000000 00 01 08 00 0000002139122910 00 02 04 00 00004415 00 02 08 00 0000001575a137d8 00 03 04 00 00000000 00 03 08 00 00000003debed0e8 00040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e90000000 01020852 00000000
|
||||
while (!stream.atEnd()) {
|
||||
quint8 measurementChannel;
|
||||
quint8 measurementIndex;
|
||||
quint8 measurmentType;
|
||||
quint8 measurmentTariff;
|
||||
|
||||
stream >> measurementChannel >> measurementIndex >> measurmentType >> measurmentTariff;
|
||||
|
||||
if (measurmentType == 4) {
|
||||
qint32 measurement;
|
||||
stream >> measurement;
|
||||
|
||||
if (measurementIndex == 1 && measurement != 0) {
|
||||
m_currentPower = measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power" << m_currentPower << "W";
|
||||
} else if (measurementIndex == 2 && measurement != 0) {
|
||||
m_currentPower = -measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power" << m_currentPower << "W";
|
||||
} else if (measurementIndex == 21 && measurement != 0) {
|
||||
m_currentPowerPhaseA = measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase A" << m_currentPowerPhaseA << "W";
|
||||
} else if (measurementIndex == 22 && measurement != 0) {
|
||||
m_currentPowerPhaseA = -measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase A" << m_currentPowerPhaseA << "W";
|
||||
} else if (measurementIndex == 41 && measurement != 0) {
|
||||
m_currentPowerPhaseB = measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase B" << m_currentPowerPhaseB << "W";
|
||||
} else if (measurementIndex == 42 && measurement != 0) {
|
||||
m_currentPowerPhaseB = -measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase B" << m_currentPowerPhaseB << "W";
|
||||
} else if (measurementIndex == 61 && measurement != 0) {
|
||||
m_currentPowerPhaseC = measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase C" << m_currentPowerPhaseC << "W";
|
||||
} else if (measurementIndex == 62 && measurement != 0) {
|
||||
m_currentPowerPhaseC = -measurement / 10.0;
|
||||
qCDebug(dcSma()) << "Meter: Current power phase C" << m_currentPowerPhaseC << "W";
|
||||
} else if (measurementIndex == 31) {
|
||||
m_amperePhaseA = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Ampere phase A" << m_amperePhaseA << "A";
|
||||
} else if (measurementIndex == 51) {
|
||||
m_amperePhaseB = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Ampere phase B" << m_amperePhaseB << "A";
|
||||
} else if (measurementIndex == 71) {
|
||||
m_amperePhaseC = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Ampere phase C" << m_amperePhaseC << "A";
|
||||
} else if (measurementIndex == 32) {
|
||||
m_voltagePhaseA = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Voltage phase A" << m_voltagePhaseA << "V";
|
||||
} else if (measurementIndex == 52) {
|
||||
m_voltagePhaseB = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Voltage phase B" << m_voltagePhaseB << "V";
|
||||
} else if (measurementIndex == 72) {
|
||||
m_voltagePhaseC = measurement / 1000.0;
|
||||
qCDebug(dcSma()) << "Meter: Voltage phase C" << m_voltagePhaseC << "V";
|
||||
} else {
|
||||
// qCDebug(dcSma()) << "Meter: --> Channel:" << measurementChannel << "Index:" << measurementIndex << "Type:" << measurmentType << "Rate:" << measurmentTariff;
|
||||
// qCDebug(dcSma()) << "Meter: Value:" << measurement;
|
||||
}
|
||||
|
||||
|
||||
} else if (measurmentType == 8) {
|
||||
qint64 measurement;
|
||||
stream >> measurement;
|
||||
|
||||
if (measurementIndex == 1 && measurement != 0) {
|
||||
m_totalEnergyConsumed = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Total energy consumed" << m_totalEnergyConsumed << "kWh";
|
||||
} else if (measurementIndex == 2 && measurement != 0) {
|
||||
m_totalEnergyProduced = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Total energy produced" << m_totalEnergyProduced << "kWh";
|
||||
} else if (measurementIndex == 21 && measurement != 0) {
|
||||
m_energyConsumedPhaseA = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy consumed phase A" << m_energyConsumedPhaseA << "kWh";
|
||||
} else if (measurementIndex == 41 && measurement != 0) {
|
||||
m_energyConsumedPhaseB = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy consumed phase B" << m_energyConsumedPhaseB << "kWh";
|
||||
} else if (measurementIndex == 61 && measurement != 0) {
|
||||
m_energyConsumedPhaseC = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy consumed phase C" << m_energyConsumedPhaseC << "kWh";
|
||||
} else if (measurementIndex == 22 && measurement != 0) {
|
||||
m_energyProducedPhaseA = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy produced phase A" << m_energyProducedPhaseA << "kWh";
|
||||
} else if (measurementIndex == 42 && measurement != 0) {
|
||||
m_energyProducedPhaseB = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy produced phase B" << m_energyProducedPhaseB << "kWh";
|
||||
} else if (measurementIndex == 62 && measurement != 0) {
|
||||
m_energyProducedPhaseC = measurement / 3600000.0;
|
||||
qCDebug(dcSma()) << "Meter: Energy produced phase C" << m_energyProducedPhaseC << "kWh";
|
||||
} else {
|
||||
// qCDebug(dcSma()) << "Meter: --> Channel:" << measurementChannel << "Index:" << measurementIndex << "Type:" << measurmentType << "Rate:" << measurmentTariff;
|
||||
// qCDebug(dcSma()) << "Meter: Value:" << measurement;
|
||||
}
|
||||
|
||||
} if (measurementChannel == 144 && measurementIndex == 0 && measurmentType == 0 && measurmentTariff == 0) {
|
||||
// Software version
|
||||
// 90000000 01 02 08 52
|
||||
quint32 versionData;
|
||||
stream >> versionData;
|
||||
m_softwareVersion = Sma::buildSoftwareVersionString(versionData);
|
||||
|
||||
qCDebug(dcSma()) << "Meter: Software version" << m_softwareVersion;
|
||||
} else if (measurementChannel == 0 && measurementIndex == 0 && measurmentType == 0 && measurmentTariff == 0) {
|
||||
// 00 00 00 00
|
||||
//qCDebug(dcSma()) << "Meter: End of data reached.";
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current timestamp for reachable evaluation
|
||||
m_lastSeenTimestamp = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000;
|
||||
evaluateReachable();
|
||||
|
||||
emit valuesUpdated();
|
||||
}
|
||||
125
sma/speedwire/speedwiremeter.h
Normal file
125
sma/speedwire/speedwiremeter.h
Normal file
@ -0,0 +1,125 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2021, 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 SPEEDWIREMETER_H
|
||||
#define SPEEDWIREMETER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "speedwireinterface.h"
|
||||
|
||||
class SpeedwireMeter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpeedwireMeter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr);
|
||||
|
||||
bool initialize();
|
||||
bool initialized() const;
|
||||
|
||||
bool reachable() const;
|
||||
|
||||
double currentPower() const;
|
||||
double totalEnergyProduced() const;
|
||||
double totalEnergyConsumed() const;
|
||||
|
||||
double energyConsumedPhaseA() const;
|
||||
double energyConsumedPhaseB() const;
|
||||
double energyConsumedPhaseC() const;
|
||||
|
||||
double energyProducedPhaseA() const;
|
||||
double energyProducedPhaseB() const;
|
||||
double energyProducedPhaseC() const;
|
||||
|
||||
double currentPowerPhaseA() const;
|
||||
double currentPowerPhaseB() const;
|
||||
double currentPowerPhaseC() const;
|
||||
|
||||
double voltagePhaseA() const;
|
||||
double voltagePhaseB() const;
|
||||
double voltagePhaseC() const;
|
||||
|
||||
double amperePhaseA() const;
|
||||
double amperePhaseB() const;
|
||||
double amperePhaseC() const;
|
||||
|
||||
QString softwareVersion() const;
|
||||
|
||||
signals:
|
||||
void reachableChanged(bool reachable);
|
||||
void valuesUpdated();
|
||||
|
||||
private:
|
||||
SpeedwireInterface *m_interface = nullptr;
|
||||
QHostAddress m_address;
|
||||
bool m_initialized = false;
|
||||
quint16 m_modelId = 0;
|
||||
quint32 m_serialNumber = 0;
|
||||
|
||||
QTimer m_timer;
|
||||
bool m_reachable = false;
|
||||
qint64 m_lastSeenTimestamp = 0;
|
||||
|
||||
double m_currentPower = 0;
|
||||
double m_totalEnergyProduced = 0;
|
||||
double m_totalEnergyConsumed = 0;
|
||||
|
||||
double m_energyConsumedPhaseA = 0;
|
||||
double m_energyConsumedPhaseB = 0;
|
||||
double m_energyConsumedPhaseC = 0;
|
||||
|
||||
double m_energyProducedPhaseA = 0;
|
||||
double m_energyProducedPhaseB = 0;
|
||||
double m_energyProducedPhaseC = 0;
|
||||
|
||||
double m_currentPowerPhaseA = 0;
|
||||
double m_currentPowerPhaseB = 0;
|
||||
double m_currentPowerPhaseC = 0;
|
||||
|
||||
double m_voltagePhaseA = 0;
|
||||
double m_voltagePhaseB = 0;
|
||||
double m_voltagePhaseC = 0;
|
||||
|
||||
double m_amperePhaseA = 0;
|
||||
double m_amperePhaseB = 0;
|
||||
double m_amperePhaseC = 0;
|
||||
|
||||
QString m_softwareVersion;
|
||||
|
||||
|
||||
private slots:
|
||||
void evaluateReachable();
|
||||
void processData(const QByteArray &data);
|
||||
|
||||
};
|
||||
|
||||
#endif // SPEEDWIREMETER_H
|
||||
345
sma/sunnywebbox/sunnywebbox.cpp
Normal file
345
sma/sunnywebbox/sunnywebbox.cpp
Normal file
@ -0,0 +1,345 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 "sunnywebbox.h"
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include "QJsonDocument"
|
||||
#include "QJsonObject"
|
||||
#include "QJsonArray"
|
||||
|
||||
SunnyWebBox::SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHostAddress &hostAddress, QObject *parrent) :
|
||||
QObject(parrent),
|
||||
m_hostAddresss(hostAddress),
|
||||
m_networkManager(networkAccessManager)
|
||||
{
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Creating Sunny Web Box connection";
|
||||
}
|
||||
|
||||
SunnyWebBox::~SunnyWebBox()
|
||||
{
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Deleting Sunny Web Box connection";
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getPlantOverview()
|
||||
{
|
||||
return sendMessage(m_hostAddresss, "GetPlantOverview");
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getDevices()
|
||||
{
|
||||
return sendMessage(m_hostAddresss, "GetDevices");
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getProcessDataChannels(const QString &deviceId)
|
||||
{
|
||||
QJsonObject params;
|
||||
params["device"] = deviceId;
|
||||
return sendMessage(m_hostAddresss, "GetProcessDataChannels", params);
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getProcessData(const QStringList &deviceKeys)
|
||||
{
|
||||
QJsonObject paramsObj;
|
||||
QJsonArray devicesArray;
|
||||
foreach (const QString &key, deviceKeys) {
|
||||
QJsonObject deviceObj;
|
||||
deviceObj["key"] = key;
|
||||
devicesArray.append(deviceObj);
|
||||
}
|
||||
paramsObj["devices"] = devicesArray;
|
||||
return sendMessage(m_hostAddresss, "GetProcessData", paramsObj);
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getParameterChannels(const QString &deviceKey)
|
||||
{
|
||||
QJsonObject paramsObj;
|
||||
QJsonArray devicesArray;
|
||||
QJsonObject deviceObj;
|
||||
deviceObj["key"] = deviceKey;
|
||||
devicesArray.append(deviceObj);
|
||||
paramsObj["devices"] = devicesArray;
|
||||
return sendMessage(m_hostAddresss, "GetParameterChannels", paramsObj);
|
||||
}
|
||||
|
||||
QString SunnyWebBox::getParameters(const QStringList &deviceKeys)
|
||||
{
|
||||
QJsonObject paramsObj;
|
||||
QJsonArray devicesArray;
|
||||
foreach (const QString &key, deviceKeys) {
|
||||
QJsonObject deviceObj;
|
||||
deviceObj["key"] = key;
|
||||
devicesArray.append(deviceObj);
|
||||
}
|
||||
paramsObj["devices"] = devicesArray;
|
||||
return sendMessage(m_hostAddresss, "GetParameter", paramsObj);
|
||||
}
|
||||
|
||||
QString SunnyWebBox::setParameters(const QString &deviceKey, const QHash<QString, QVariant> &channels)
|
||||
{
|
||||
QJsonObject paramsObj;
|
||||
QJsonArray devicesArray;
|
||||
QJsonObject deviceObj;
|
||||
deviceObj["key"] = deviceKey;
|
||||
QJsonArray channelsArray;
|
||||
foreach (const QString &key, channels.keys()) {
|
||||
QJsonObject channelObj;
|
||||
channelObj["meta"] = key;
|
||||
channelObj["value"] = channels.value(key).toString();
|
||||
channelsArray.append(channelObj);
|
||||
}
|
||||
deviceObj["channels"] = channelsArray;
|
||||
devicesArray.append(deviceObj);
|
||||
paramsObj["devices"] = devicesArray;
|
||||
return sendMessage(m_hostAddresss, "SetParameter", paramsObj);
|
||||
}
|
||||
|
||||
QHostAddress SunnyWebBox::hostAddress() const
|
||||
{
|
||||
return m_hostAddresss;
|
||||
}
|
||||
|
||||
void SunnyWebBox::setHostAddress(const QHostAddress &address)
|
||||
{
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Setting host address to" << address.toString();
|
||||
m_hostAddresss = address;
|
||||
}
|
||||
|
||||
QString SunnyWebBox::macAddress() const
|
||||
{
|
||||
return m_macAddress;
|
||||
}
|
||||
|
||||
void SunnyWebBox::setMacAddress(const QString &macAddress)
|
||||
{
|
||||
m_macAddress = macAddress;
|
||||
}
|
||||
|
||||
QNetworkReply *SunnyWebBox::sendRequest(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms, const QString &requestId)
|
||||
{
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Send message to" << address.toString() << "Procedure:" << procedure << "Params:" << params;
|
||||
|
||||
QString finalRequestId = requestId;
|
||||
if (finalRequestId.isEmpty())
|
||||
finalRequestId = generateRequestId();
|
||||
|
||||
QJsonDocument doc;
|
||||
QJsonObject obj;
|
||||
obj["format"] = "JSON";
|
||||
obj["id"] = requestId;
|
||||
obj["proc"] = procedure;
|
||||
obj["version"] = "1.0";
|
||||
|
||||
if (!params.isEmpty()) {
|
||||
obj.insert("params", params);
|
||||
}
|
||||
doc.setObject(obj);
|
||||
|
||||
QUrl url;
|
||||
url.setHost(address.toString());
|
||||
url.setPath("/rpc");
|
||||
url.setPort(80);
|
||||
url.setScheme("http");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
QByteArray data = doc.toJson(QJsonDocument::JsonFormat::Compact);
|
||||
data.prepend("RPC=");
|
||||
return m_networkManager->post(request, data);
|
||||
}
|
||||
|
||||
QString SunnyWebBox::generateRequestId()
|
||||
{
|
||||
return QUuid::createUuid().toString().remove('{').remove('-').left(14);
|
||||
}
|
||||
|
||||
void SunnyWebBox::parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result)
|
||||
{
|
||||
if (messageType == "GetPlantOverview") {
|
||||
Overview overview;
|
||||
QVariantList overviewList = result.value("overview").toList();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: GetPlantOverview";
|
||||
foreach (const QVariant &value, overviewList) {
|
||||
QVariantMap map = value.toMap();
|
||||
|
||||
if (map["meta"].toString() == "GriPwr") {
|
||||
overview.power = map["value"].toString().toInt();
|
||||
QString unit = map["unit"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Power" << overview.power << unit;
|
||||
} else if (map["meta"].toString() == "GriEgyTdy") {
|
||||
overview.dailyYield = map["value"].toString().toDouble();
|
||||
QString unit = map["unit"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Daily yield" << overview.dailyYield << unit;
|
||||
} else if (map["meta"].toString() == "GriEgyTot") {
|
||||
overview.totalYield = map["value"].toString().toDouble();
|
||||
QString unit = map["unit"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Total yield" << overview.totalYield << unit;
|
||||
} else if (map["meta"].toString() == "OpStt") {
|
||||
overview.status = map["value"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Status" << overview.status;
|
||||
} else if (map["meta"].toString() == "Msg") {
|
||||
overview.error = map["value"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Error" << overview.error;
|
||||
}
|
||||
}
|
||||
emit plantOverviewReceived(messageId, overview);
|
||||
|
||||
} else if (messageType == "GetDevices") {
|
||||
QList<Device> devices;
|
||||
QVariantList deviceList = result.value("devices").toList();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: GetDevices" << result.value("totalDevicesReturned").toInt();
|
||||
foreach (const QVariant &value, deviceList) {
|
||||
Device device;
|
||||
QVariantMap map = value.toMap();
|
||||
device.name = map["name"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Name" << device.name;
|
||||
device.key = map["key"].toString();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: - Key" << device.key;
|
||||
QVariantList childrenList = map["children"].toList();
|
||||
foreach (const QVariant &childValue, childrenList) {
|
||||
Device child;
|
||||
QVariantMap childMap = childValue.toMap();
|
||||
device.name = childMap["name"].toString();
|
||||
device.key = childMap["key"].toString();
|
||||
device.childrens.append(child);
|
||||
}
|
||||
devices.append(device);
|
||||
}
|
||||
if (!devices.isEmpty()) {
|
||||
emit devicesReceived(messageId, devices);
|
||||
}
|
||||
} else if (messageType == "GetProcessDataChannels" ||
|
||||
messageType == "GetProDataChannels") {
|
||||
foreach (const QString &deviceKey, result.keys()) {
|
||||
QStringList processDataChannels = result.value(deviceKey).toStringList();
|
||||
if (!processDataChannels.isEmpty())
|
||||
emit processDataChannelsReceived(messageId, deviceKey, processDataChannels);
|
||||
}
|
||||
} else if (messageType == "GetProcessData") {
|
||||
QVariantList devicesList = result.value("devices").toList();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: GetProcessData response received";
|
||||
foreach (const QVariant &value, devicesList) {
|
||||
|
||||
QString key = value.toMap().value("key").toString();
|
||||
QVariantList channelsList = value.toMap().value("channels").toList();
|
||||
QHash<QString, QVariant> channels;
|
||||
foreach (const QVariant &channel, channelsList) {
|
||||
channels.insert(channel.toMap().value("meta").toString(), channel.toMap().value("value"));
|
||||
}
|
||||
emit processDataReceived(messageId, key, channels);
|
||||
}
|
||||
} else if (messageType == "GetParameterChannels") {
|
||||
foreach (const QString &deviceKey, result.keys()) {
|
||||
QStringList parameterChannels = result.value(deviceKey).toStringList();
|
||||
if (!parameterChannels.isEmpty())
|
||||
emit parameterChannelsReceived(messageId, deviceKey, parameterChannels);
|
||||
}
|
||||
} else if (messageType == "GetParameter"|| messageType == "SetParameter") {
|
||||
QVariantList devicesList = result.value("devices").toList();
|
||||
foreach (const QVariant &value, devicesList) {
|
||||
|
||||
QString key = value.toMap().value("key").toString();
|
||||
QVariantList channelsList = value.toMap().value("channels").toList();
|
||||
QList<Parameter> parameters;
|
||||
foreach (const QVariant &channel, channelsList) {
|
||||
Parameter parameter;
|
||||
parameter.meta = channel.toMap().value("meta").toString();
|
||||
parameter.name = channel.toMap().value("name").toString();
|
||||
parameter.unit = channel.toMap().value("unit").toString();
|
||||
parameter.min = channel.toMap().value("min").toDouble();
|
||||
parameter.max = channel.toMap().value("max").toDouble();
|
||||
parameter.value = channel.toMap().value("value").toDouble();
|
||||
parameters.append(parameter);
|
||||
}
|
||||
emit parametersReceived(messageId, key, parameters);
|
||||
}
|
||||
} else {
|
||||
qCWarning(dcSma()) << "SunnyWebBox: Unknown message type" << messageType;
|
||||
}
|
||||
}
|
||||
|
||||
void SunnyWebBox::setConnectionStatus(bool connected)
|
||||
{
|
||||
if (m_connected != connected) {
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Connection status changed" << connected;
|
||||
m_connected = connected;
|
||||
emit connectedChanged(m_connected);
|
||||
}
|
||||
}
|
||||
|
||||
QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure)
|
||||
{
|
||||
return sendMessage(address, procedure, QJsonObject());
|
||||
}
|
||||
|
||||
QString SunnyWebBox::sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms)
|
||||
{
|
||||
QString requestId = generateRequestId();
|
||||
QNetworkReply *reply = sendRequest(m_hostAddresss, procedure, params, requestId);
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, address, requestId, reply]{
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
setConnectionStatus(false);
|
||||
return;
|
||||
}
|
||||
setConnectionStatus(true);
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
qCDebug(dcSma()) << "SunnyWebBox: Received reply" << data;
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcSma()) << "SunnyWebBox: Could not parse JSON" << error.errorString();
|
||||
return;
|
||||
}
|
||||
if (!doc.isObject()) {
|
||||
qCWarning(dcSma()) << "SunnyWebBox: JSON is not an Object";
|
||||
return;
|
||||
}
|
||||
QVariantMap map = doc.toVariant().toMap();
|
||||
if (map["version"] != "1.0") {
|
||||
qCWarning(dcSma()) << "SunnyWebBox: API version not supported" << map["version"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.contains("proc") && map.contains("result")) {
|
||||
QString requestType = map["proc"].toString();
|
||||
QString requestId = map["id"].toString();
|
||||
QVariantMap result = map.value("result").toMap();
|
||||
parseMessage(requestId, requestType, result);
|
||||
} else if (map.contains("proc") && map.contains("error")) {
|
||||
} else {
|
||||
qCWarning(dcSma()) << "SunnyWebBox: Missing proc or result value";
|
||||
}
|
||||
});
|
||||
return requestId;
|
||||
}
|
||||
|
||||
120
sma/sunnywebbox/sunnywebbox.h
Normal file
120
sma/sunnywebbox/sunnywebbox.h
Normal file
@ -0,0 +1,120 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2020, nymea GmbH
|
||||
* Contact: contact@nymea.io
|
||||
*
|
||||
* This file is part of nymea.
|
||||
* This project including source code and documentation is protected by
|
||||
* copyright law, and remains the property of nymea GmbH. All rights, including
|
||||
* reproduction, publication, editing and translation, are reserved. The use of
|
||||
* this project is subject to the terms of a license agreement to be concluded
|
||||
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
||||
* under https://nymea.io/license
|
||||
*
|
||||
* GNU Lesser General Public License Usage
|
||||
* Alternatively, this project may be redistributed and/or modified under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; version 3. This project is distributed in the hope that
|
||||
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this project. If not, see <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 SUNNYWEBBOX_H
|
||||
#define SUNNYWEBBOX_H
|
||||
|
||||
#include "integrations/thing.h"
|
||||
#include "network/networkaccessmanager.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
#include <QHostAddress>
|
||||
#include <QUdpSocket>
|
||||
|
||||
class SunnyWebBox : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Overview {
|
||||
int power;
|
||||
double dailyYield;
|
||||
int totalYield;
|
||||
QString status;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct Device {
|
||||
QString key;
|
||||
QString name;
|
||||
QList<Device> childrens;
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
QString meta;
|
||||
QString name;
|
||||
QVariant value;
|
||||
QString unit;
|
||||
};
|
||||
|
||||
struct Parameter {
|
||||
QString meta;
|
||||
QString name;
|
||||
QString unit;
|
||||
double min;
|
||||
double max;
|
||||
double value;
|
||||
};
|
||||
|
||||
explicit SunnyWebBox(NetworkAccessManager *networkAccessManager, const QHostAddress &hostAddress, QObject *parrent = 0);
|
||||
~SunnyWebBox();
|
||||
|
||||
QString getPlantOverview(); // Returns an object with the following plant data: PAC, E-TODAY, E-TOTAL, MODE, ERROR
|
||||
QString getDevices(); // Returns a hierarchical list of all detected plant devices.
|
||||
QString getProcessDataChannels(const QString &deviceKey); //Returns a list with the meta names of the available process data channels for a particular device type.
|
||||
QString getProcessData(const QStringList &deviceKeys); //Returns process data for up to 5 devices per request.
|
||||
QString getParameterChannels(const QString &deviceKey); //Returns a list with the meta names of the available parameter channels for a particular device type
|
||||
QString getParameters(const QStringList &deviceKeys); //Returns the parameter values of up to 5 devices
|
||||
QString setParameters(const QString &deviceKeys, const QHash<QString, QVariant> &channels); //Sets parameter values
|
||||
|
||||
QHostAddress hostAddress() const;
|
||||
void setHostAddress(const QHostAddress &address);
|
||||
|
||||
QString macAddress() const;
|
||||
void setMacAddress(const QString &macAddress);
|
||||
|
||||
QNetworkReply *sendRequest(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms = QJsonObject(), const QString &requestId = QString());
|
||||
|
||||
static QString generateRequestId();
|
||||
|
||||
private:
|
||||
bool m_connected = false;
|
||||
QHostAddress m_hostAddresss;
|
||||
QString m_macAddress;
|
||||
NetworkAccessManager *m_networkManager = nullptr;
|
||||
|
||||
QString sendMessage(const QHostAddress &address, const QString &procedure);
|
||||
QString sendMessage(const QHostAddress &address, const QString &procedure, const QJsonObject ¶ms);
|
||||
void parseMessage(const QString &messageId, const QString &messageType, const QVariantMap &result);
|
||||
void setConnectionStatus(bool connected);
|
||||
|
||||
signals:
|
||||
void connectedChanged(bool connected);
|
||||
|
||||
void plantOverviewReceived(const QString &messageId, Overview overview);
|
||||
void devicesReceived(const QString &messageId, QList<Device> devices);
|
||||
void processDataChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList processDataChanels);
|
||||
void processDataReceived(const QString &messageId, const QString &deviceKey, const QHash<QString, QVariant> &channels);
|
||||
void parameterChannelsReceived(const QString &messageId, const QString &deviceKey, QStringList parameterChannels);
|
||||
void parametersReceived(const QString &messageId, const QString &deviceKey, const QList<Parameter> ¶meters);
|
||||
};
|
||||
|
||||
#endif // SUNNYWEBBOX_H
|
||||
160
sma/sunnywebbox/sunnywebboxdiscovery.cpp
Normal file
160
sma/sunnywebbox/sunnywebboxdiscovery.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 "sunnywebboxdiscovery.h"
|
||||
#include "sunnywebbox.h"
|
||||
|
||||
#include "extern-plugininfo.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
SunnyWebBoxDiscovery::SunnyWebBoxDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_networkAccessManager(networkAccessManager),
|
||||
m_networkDeviceDiscovery(networkDeviceDiscovery)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SunnyWebBoxDiscovery::startDiscovery()
|
||||
{
|
||||
// Clean up
|
||||
m_discoveryResults.clear();
|
||||
m_verifiedNetworkDeviceInfos.clear();
|
||||
|
||||
m_startDateTime = QDateTime::currentDateTime();
|
||||
|
||||
qCInfo(dcSma()) << "Discovery: SunnyWebBox: Starting network discovery...";
|
||||
m_discoveryReply = m_networkDeviceDiscovery->discover();
|
||||
|
||||
// Test any network device beeing discovered
|
||||
connect(m_discoveryReply, &NetworkDeviceDiscoveryReply::networkDeviceInfoAdded, this, &SunnyWebBoxDiscovery::checkNetworkDevice);
|
||||
|
||||
// When the network discovery has finished, we process the rest and give some time to finish the pending replies
|
||||
connect(m_discoveryReply, &NetworkDeviceDiscoveryReply::finished, this, [=](){
|
||||
// The network device discovery is done
|
||||
m_discoveredNetworkDeviceInfos = m_discoveryReply->networkDeviceInfos();
|
||||
m_discoveryReply->deleteLater();
|
||||
m_discoveryReply = nullptr;
|
||||
|
||||
// Check if all network device infos have been verified
|
||||
foreach (const NetworkDeviceInfo &networkDeviceInfo, m_discoveredNetworkDeviceInfos) {
|
||||
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
|
||||
continue;
|
||||
|
||||
checkNetworkDevice(networkDeviceInfo);
|
||||
}
|
||||
|
||||
// If there might be some response after the grace period time,
|
||||
// we don't care any more since there might just waiting for some timeouts...
|
||||
// If there would be a device, if would have responded.
|
||||
QTimer::singleShot(3000, this, [this](){
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: Grace period timer triggered.";
|
||||
finishDiscovery();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
NetworkDeviceInfos SunnyWebBoxDiscovery::discoveryResults() const
|
||||
{
|
||||
return m_discoveryResults;
|
||||
}
|
||||
|
||||
void SunnyWebBoxDiscovery::checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo)
|
||||
{
|
||||
if (m_verifiedNetworkDeviceInfos.contains(networkDeviceInfo))
|
||||
return;
|
||||
|
||||
m_verifiedNetworkDeviceInfos.append(networkDeviceInfo);
|
||||
|
||||
// Make a simple request and verify if it worked and the expected data gets returned.
|
||||
SunnyWebBox webBox(m_networkAccessManager, networkDeviceInfo.address(), this);
|
||||
QNetworkReply *reply = webBox.sendRequest(networkDeviceInfo.address(), "GetPlantOverview");
|
||||
m_pendingReplies.append(reply);
|
||||
connect(reply, &QNetworkReply::finished, this, [=](){
|
||||
m_pendingReplies.removeAll(reply);
|
||||
reply->deleteLater();
|
||||
|
||||
// Check HTTP reply
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: Checked" << networkDeviceInfo.address().toString()
|
||||
<< "and a HTTP error occurred:" << reply->errorString() << "Continue...";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
|
||||
// Check JSON
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: Checked" << networkDeviceInfo.address().toString()
|
||||
<< "and received invalid JSON data:" << error.errorString() << "Continue...";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsonDoc.isObject()) {
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: Response JSON is not an Object" << networkDeviceInfo.address().toString() << "Continue...";
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap map = jsonDoc.toVariant().toMap();
|
||||
if (map["version"] != "1.0") {
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: API version not supported on" << networkDeviceInfo.address().toString() << "Continue...";;
|
||||
return;
|
||||
}
|
||||
|
||||
if (map.contains("proc") && map.contains("result")) {
|
||||
// Ok, seems to be a Sunny WebBox we are talking to...add to the discovery results...
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: --> Found Sunny WebBox on" << networkDeviceInfo;
|
||||
m_discoveryResults.append(networkDeviceInfo);
|
||||
} else {
|
||||
qCDebug(dcSma()) << "Discovery: SunnyWebBox: Missing proc or result value in response from" << networkDeviceInfo.address().toString() << "Continue...";
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SunnyWebBoxDiscovery::cleanupPendingReplies()
|
||||
{
|
||||
foreach (QNetworkReply *reply, m_pendingReplies) {
|
||||
reply->abort();
|
||||
}
|
||||
}
|
||||
|
||||
void SunnyWebBoxDiscovery::finishDiscovery()
|
||||
{
|
||||
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startDateTime.toMSecsSinceEpoch();
|
||||
qCInfo(dcSma()) << "Discovery: SunnyWebBox: Finished the discovery process. Found" << m_discoveryResults.count()
|
||||
<< "Sunny WebBoxes in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
|
||||
|
||||
cleanupPendingReplies();
|
||||
emit discoveryFinished();
|
||||
}
|
||||
71
sma/sunnywebbox/sunnywebboxdiscovery.h
Normal file
71
sma/sunnywebbox/sunnywebboxdiscovery.h
Normal file
@ -0,0 +1,71 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
*
|
||||
* Copyright 2013 - 2022, 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 SUNNYWEBBOXDISCOVERY_H
|
||||
#define SUNNYWEBBOXDISCOVERY_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <network/networkaccessmanager.h>
|
||||
#include <network/networkdevicediscovery.h>
|
||||
|
||||
class SunnyWebBoxDiscovery : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SunnyWebBoxDiscovery(NetworkAccessManager *networkAccessManager, NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr);
|
||||
|
||||
void startDiscovery();
|
||||
|
||||
NetworkDeviceInfos discoveryResults() const;
|
||||
|
||||
signals:
|
||||
void discoveryFinished();
|
||||
|
||||
private slots:
|
||||
void checkNetworkDevice(const NetworkDeviceInfo &networkDeviceInfo);
|
||||
void cleanupPendingReplies();
|
||||
void finishDiscovery();
|
||||
|
||||
private:
|
||||
NetworkAccessManager *m_networkAccessManager = nullptr;
|
||||
NetworkDeviceDiscovery *m_networkDeviceDiscovery = nullptr;
|
||||
NetworkDeviceDiscoveryReply *m_discoveryReply = nullptr;
|
||||
|
||||
NetworkDeviceInfos m_discoveryResults;
|
||||
NetworkDeviceInfos m_discoveredNetworkDeviceInfos;
|
||||
NetworkDeviceInfos m_verifiedNetworkDeviceInfos;
|
||||
|
||||
QDateTime m_startDateTime;
|
||||
QList<QNetworkReply *> m_pendingReplies;
|
||||
|
||||
};
|
||||
|
||||
#endif // SUNNYWEBBOXDISCOVERY_H
|
||||
309
sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts
Normal file
309
sma/translations/b8442bbf-9d3f-4aa2-9443-b3a31ae09bac-en_US.ts
Normal file
@ -0,0 +1,309 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1">
|
||||
<context>
|
||||
<name>IntegrationPluginSma</name>
|
||||
<message>
|
||||
<location filename="../integrationpluginsma.cpp" line="52"/>
|
||||
<source>Unable to discover devices in your network.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsma.cpp" line="97"/>
|
||||
<location filename="../integrationpluginsma.cpp" line="139"/>
|
||||
<source>Unable to discover the network.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsma.cpp" line="182"/>
|
||||
<source>Please enter the password of your inverter. If no password has been explicitly set, leave it empty to use the default password for SMA inverters.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsma.cpp" line="191"/>
|
||||
<source>The password can not be longer than 12 characters.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../integrationpluginsma.cpp" line="331"/>
|
||||
<source>Failed to log in with the given password. Please try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>sma</name>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="80"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="83"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="86"/>
|
||||
<source>Connected</source>
|
||||
<extracomment>The name of the StateType ({aaff72c3-c70a-4a2f-bed1-89f38cebe442}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({35733d27-4fe0-439a-be71-7c1597481659}) of ThingClass speedwireMeter
|
||||
----------
|
||||
The name of the StateType ({c05e6a1a-252c-4f2b-8b31-09cf113d01c1}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="89"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="92"/>
|
||||
<source>Current phase A</source>
|
||||
<extracomment>The name of the StateType ({2a6c59ca-853a-47d6-96fb-0c85edf32f52}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({45bbdbef-1832-4870-bff5-299e580fb4da}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="95"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="98"/>
|
||||
<source>Current phase B</source>
|
||||
<extracomment>The name of the StateType ({4db96fec-737c-4c4b-bf07-5ef2fd62508a}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({b3a4fdd2-b6b8-4c58-9da3-2084ad414022}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="101"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="104"/>
|
||||
<source>Current phase C</source>
|
||||
<extracomment>The name of the StateType ({0f23fb0e-a440-4ac2-9aff-896bc65feb2c}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({b3655188-3854-4336-ae3c-61d3bda6fc4d}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="107"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="110"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="113"/>
|
||||
<source>Current power</source>
|
||||
<extracomment>The name of the StateType ({d7ceb482-5df8-4c0c-82bd-62ce7ba22c43}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({d4ac7f37-e30a-44e4-93cb-ad16df18b8f1}) of ThingClass speedwireMeter
|
||||
----------
|
||||
The name of the StateType ({ff4ff872-2f0f-4ca4-9fe2-220eeaf16cc2}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="116"/>
|
||||
<source>Current power phase A</source>
|
||||
<extracomment>The name of the StateType ({c5d09c63-7461-4fb8-a6fe-bc7aa919be30}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="119"/>
|
||||
<source>Current power phase B</source>
|
||||
<extracomment>The name of the StateType ({c52d4422-b521-4804-a7a7-c4398e91e760}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="122"/>
|
||||
<source>Current power phase C</source>
|
||||
<extracomment>The name of the StateType ({555e892c-3ca7-4100-9832-6ac13b87eb04}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="125"/>
|
||||
<source>DC power MPP1</source>
|
||||
<extracomment>The name of the StateType ({b366f680-6134-488b-8362-b1b824a8daca}) of ThingClass speedwireInverter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="128"/>
|
||||
<source>DC power MPP2</source>
|
||||
<extracomment>The name of the StateType ({87d9b654-5558-47a3-9db9-ffd7c23b4774}) of ThingClass speedwireInverter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="131"/>
|
||||
<source>Day energy produced</source>
|
||||
<extracomment>The name of the StateType ({16f34c5c-8dbb-4dcc-9faa-4b782d57226c}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="134"/>
|
||||
<source>Energy consumed phase A</source>
|
||||
<extracomment>The name of the StateType ({b4ff2c71-f81d-4904-bbac-0c0c6e8a5a33}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="137"/>
|
||||
<source>Energy consumed phase B</source>
|
||||
<extracomment>The name of the StateType ({c4e5f569-ac5d-4761-a898-888880bfd59f}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="140"/>
|
||||
<source>Energy consumed phase C</source>
|
||||
<extracomment>The name of the StateType ({aabc02d7-8dc3-4637-8bf2-dc2e0e737ad3}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="143"/>
|
||||
<source>Energy produced phase A</source>
|
||||
<extracomment>The name of the StateType ({754c3b67-768a-47f7-99d8-f66c198f0835}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="146"/>
|
||||
<source>Energy produced phase B</source>
|
||||
<extracomment>The name of the StateType ({7eb08c45-24cf-40ce-be28-f3564f087672}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="149"/>
|
||||
<source>Energy produced phase C</source>
|
||||
<extracomment>The name of the StateType ({1eb2bf01-5ec6-42e5-b348-ac1e95199d14}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="152"/>
|
||||
<source>Energy produced today</source>
|
||||
<extracomment>The name of the StateType ({e8bc8f81-e5c5-4900-b429-93fcaa262fcb}) of ThingClass speedwireInverter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="155"/>
|
||||
<source>Error</source>
|
||||
<extracomment>The name of the StateType ({4e64f9ca-7e5a-4897-8035-6f2ae88fde89}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="158"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="161"/>
|
||||
<source>Firmware version</source>
|
||||
<extracomment>The name of the StateType ({6d76cc7b-9e00-4561-be7b-4e2a6b8f7b66}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({a685393c-8b7e-42c5-bb41-f9907c074626}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="164"/>
|
||||
<source>Frequency</source>
|
||||
<extracomment>The name of the StateType ({fdccf5de-7413-4480-9ca0-1151665dede8}) of ThingClass speedwireInverter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="167"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="170"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="173"/>
|
||||
<source>Host address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: speedwireInverter, Type: thing, ID: {c8098d53-69eb-4d0b-9f07-e43c4a0ea9a9})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: speedwireMeter, Type: thing, ID: {d90193e6-a996-4e49-bf6d-564d596d7e74})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {864d4162-e3ce-48b8-b8ac-c1b971b52d42})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="176"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="179"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="182"/>
|
||||
<source>MAC address</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: speedwireInverter, Type: thing, ID: {7df0ab60-0f11-4495-8e0d-508ba2b6d858})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: speedwireMeter, Type: thing, ID: {2780eab7-1f1c-4cc7-a789-a8790329ca9e})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: sunnyWebBox, Type: thing, ID: {03f32361-4e13-4597-a346-af8d16a986b3})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="185"/>
|
||||
<source>Mode</source>
|
||||
<extracomment>The name of the StateType ({1974550b-6059-4b0e-83f4-70177e20dac3}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="188"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="191"/>
|
||||
<source>Model ID</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: speedwireInverter, Type: thing, ID: {d9892f74-5b93-4c98-8da2-72aca033273a})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: speedwireMeter, Type: thing, ID: {abdc114d-1fac-4454-8b82-871ed5cdf28c})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="194"/>
|
||||
<source>SMA</source>
|
||||
<extracomment>The name of the plugin sma ({b8442bbf-9d3f-4aa2-9443-b3a31ae09bac})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="197"/>
|
||||
<source>SMA Energy Meter</source>
|
||||
<extracomment>The name of the ThingClass ({0c5097af-e136-4430-9fb4-0ccbb30c3e1c})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="200"/>
|
||||
<source>SMA Inverter</source>
|
||||
<extracomment>The name of the ThingClass ({b63a0669-f2ac-4769-abea-e14cafb2309a})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="203"/>
|
||||
<source>SMA Solar Technology AG</source>
|
||||
<extracomment>The name of the vendor ({16d5a4a3-36d5-46c0-b7dd-df166ddf5981})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="206"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="209"/>
|
||||
<source>Serial number</source>
|
||||
<extracomment>The name of the ParamType (ThingClass: speedwireInverter, Type: thing, ID: {e42242b4-2811-47f9-b42b-b150ed233217})
|
||||
----------
|
||||
The name of the ParamType (ThingClass: speedwireMeter, Type: thing, ID: {7c81a0c5-9bc6-43bb-a01a-4de5fe656bba})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="212"/>
|
||||
<source>Sunny WebBox</source>
|
||||
<extracomment>The name of the ThingClass ({49304127-ce9b-45dd-8511-05030a4ac003})</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="215"/>
|
||||
<source>Total energy consumed</source>
|
||||
<extracomment>The name of the StateType ({4fb0a4c1-18ed-4d02-b6d0-c07e9b96a56d}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="218"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="221"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="224"/>
|
||||
<source>Total energy produced</source>
|
||||
<extracomment>The name of the StateType ({51cadd66-2cf1-485a-a2a9-191d11abfbd1}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({76ca68d8-6781-4d2a-8663-440aec40b4de}) of ThingClass speedwireMeter
|
||||
----------
|
||||
The name of the StateType ({0bb4e227-7e38-49ca-9b32-ce4621c9305b}) of ThingClass sunnyWebBox</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="227"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="230"/>
|
||||
<source>Voltage phase A</source>
|
||||
<extracomment>The name of the StateType ({6ef4eb16-a3d6-4bc9-972d-5e7cb81173a5}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({44ee2491-8376-41cd-a21d-185c736152ec}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="233"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="236"/>
|
||||
<source>Voltage phase B</source>
|
||||
<extracomment>The name of the StateType ({d9a5768b-1bf5-4933-810d-84dd7a688f71}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({56ae3555-f874-4c2d-8833-17573dce477a}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="239"/>
|
||||
<location filename="../../../build-nymea-plugins-Desktop-Debug/sma/plugininfo.h" line="242"/>
|
||||
<source>Voltage phase C</source>
|
||||
<extracomment>The name of the StateType ({fc168dc6-eecf-40b4-b214-3e28da0dbb12}) of ThingClass speedwireInverter
|
||||
----------
|
||||
The name of the StateType ({51cbb29b-29f0-480a-9d7d-b8f4e6a205ae}) of ThingClass speedwireMeter</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
Reference in New Issue
Block a user