Add basics for inverter communication

This commit is contained in:
Simon Stürz 2021-12-09 07:00:32 +01:00
parent a30d338de9
commit 8b9c38b04d
13 changed files with 532 additions and 75 deletions

View File

@ -118,7 +118,7 @@ isEmpty(WITH_PLUGINS) {
}
PLUGINS-=$${WITHOUT_PLUGINS}
#FIXME: PLUGINS=sma
# FIXME: PLUGINS=sma
message("Building plugins:")
for(plugin, PLUGINS) {
exists($${plugin}) {

View File

@ -131,6 +131,48 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
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 Inverter", "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();
}
}
@ -188,6 +230,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
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;
}
@ -219,7 +262,28 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
m_speedwireMeters.insert(thing, meter);
info->finish(Thing::ThingErrorNoError);
}else {
} 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;
}
// TODO: reachable state
m_speedwireInverters.insert(thing, inverter);
info->finish(Thing::ThingErrorNoError);
inverter->sendLoginRequest();
} else {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}

View File

@ -31,11 +31,12 @@
#ifndef INTEGRATIONPLUGINSMA_H
#define INTEGRATIONPLUGINSMA_H
#include "integrations/integrationplugin.h"
#include "plugintimer.h"
#include "sunnywebbox.h"
#include <integrations/integrationplugin.h>
#include <plugintimer.h>
#include "sunnywebbox.h"
#include "speedwiremeter.h"
#include "speedwireinverter.h"
class IntegrationPluginSma: public IntegrationPlugin {
Q_OBJECT
@ -61,6 +62,7 @@ private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
QHash<Thing *, SpeedwireMeter *> m_speedwireMeters;
QHash<Thing *, SpeedwireInverter *> m_speedwireInverters;
};
#endif // INTEGRATIONPLUGINSMA_H

View File

@ -306,6 +306,155 @@
"defaultValue": ""
}
]
},
{
"id": "b63a0669-f2ac-4769-abea-e14cafb2309a",
"name": "speedwireInverter",
"displayName": "SMA Inverter",
"createMethods": ["discovery", "user"],
"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
},
{
"id": "e42242b4-2811-47f9-b42b-b150ed233217",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
},
{
"id": "d9892f74-5b93-4c98-8da2-72aca033273a",
"name": "modelId",
"displayName": "Model ID",
"type": "uint",
"inputType": "TextLine",
"readOnly": true
}
],
"stateTypes": [
{
"id": "aaff72c3-c70a-4a2f-bed1-89f38cebe442",
"name": "connected",
"displayName": "Connected",
"displayNameEvent": "Connected changed",
"type": "bool",
"defaultValue": false
},
{
"id": "6ef4eb16-a3d6-4bc9-972d-5e7cb81173a5",
"name": "voltagePhaseA",
"displayName": "Voltage phase A",
"displayNameEvent": "Voltage phase A changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "d9a5768b-1bf5-4933-810d-84dd7a688f71",
"name": "voltagePhaseB",
"displayName": "Voltage phase B",
"displayNameEvent": "Voltage phase B changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "fc168dc6-eecf-40b4-b214-3e28da0dbb12",
"name": "voltagePhaseC",
"displayName": "Voltage phase C",
"displayNameEvent": "Voltage phase C changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "2a6c59ca-853a-47d6-96fb-0c85edf32f52",
"name": "currentPhaseA",
"displayName": "Current phase A",
"displayNameEvent": "Current phase A changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "4db96fec-737c-4c4b-bf07-5ef2fd62508a",
"name": "currentPhaseB",
"displayName": "Current phase B",
"displayNameEvent": "Current phase B changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "0f23fb0e-a440-4ac2-9aff-896bc65feb2c",
"name": "currentPhaseC",
"displayName": "Current phase C",
"displayNameEvent": "Current phase C changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "d7ceb482-5df8-4c0c-82bd-62ce7ba22c43",
"name": "currentPower",
"displayName": "Current power",
"displayNameEvent": "Current power changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "b366f680-6134-488b-8362-b1b824a8daca",
"name": "currentPowerMpp1",
"displayName": "Current power MPP1",
"displayNameEvent": "Current power MPP1 changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "87d9b654-5558-47a3-9db9-ffd7c23b4774",
"name": "currentPowerMpp2",
"displayName": "Current power MPP2",
"displayNameEvent": "Current power MPP2 changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "51cadd66-2cf1-485a-a2a9-191d11abfbd1",
"name": "totalEnergyProduced",
"displayName": "Total energy produced",
"displayNameEvent": "Total energy produced changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "6d76cc7b-9e00-4561-be7b-4e2a6b8f7b66",
"name": "firmwareVersion",
"displayName": "Firmware version",
"displayNameEvent": "Firmware version changed",
"type": "QString",
"defaultValue": ""
}
]
}
]
}

View File

@ -1,6 +0,0 @@
#include "obisdata.h"
ObisData::ObisData()
{
}

View File

@ -1,11 +0,0 @@
#ifndef OBISDATA_H
#define OBISDATA_H
class ObisData
{
public:
ObisData();
};
#endif // OBISDATA_H

View File

@ -4,16 +4,16 @@ QT += network
SOURCES += \
integrationpluginsma.cpp \
obisdata.cpp \
speedwirediscovery.cpp \
speedwireinterface.cpp \
speedwireinverter.cpp \
speedwiremeter.cpp \
sunnywebbox.cpp
HEADERS += \
integrationpluginsma.h \
obisdata.h \
speedwirediscovery.h \
speedwireinterface.h \
speedwireinverter.h \
speedwiremeter.h \
sunnywebbox.h

View File

@ -6,44 +6,12 @@ SpeedwireInterface::SpeedwireInterface(const QHostAddress &address, bool multica
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)));
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00020058 001e8200 ff208200 00000000 => query device type
// Response 534d4100000402a000000001009e0010 606527a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01020058 01000000 03000000 011e8210 6f89e95f 534e3a20 33303130 35333831 31360000 00000000 00000000 00000000 00000000
// 011f8208 6f89e95f 411f0001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 => 1f41 solar inverter
// 01208208 6f89e95f 96240000 80240000 81240001 82240000 feffff00 00000000 00000000 00000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00028053 001e2500 ff1e2500 00000000 => query spot dc power
// Response 534d4100000402a000000001005e0010 606517a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01028053 00000000 01000000 011e2540 61a7e95f 57000000 57000000 57000000 57000000 01000000
// 021e2540 61a7e95f 5e000000 5e000000 5e000000 5e000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000580 00028053 001f4500 ff214500 00000000 => query spot dc voltage/current
// Response 534d4100000402a00000000100960010 606525a0 7d0042be283a00a1 7a01842a71b30001 000000000580 01028053 02000000 05000000 011f4540 61a7e95f 05610000 05610000 05610000 05610000 01000000
// 021f4540 61a7e95f 505b0000 505b0000 505b0000 505b0000 01000000
// 01214540 61a7e95f 60010000 60010000 60010000 60010000 01000000
// 02214540 61a7e95f 95010000 95010000 95010000 95010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000680 00020051 00404600 ff424600 00000000 => query spot ac power
// Response 534d4100000402a000000001007a0010 60651ea0 7d0042be283a00a1 7a01842a71b30001 000000000680 01020051 09000000 0b000000 01404640 61a7e95f 38000000 38000000 38000000 38000000 01000000
// 01414640 61a7e95f 37000000 37000000 37000000 37000000 01000000
// 01424640 61a7e95f 39000000 39000000 39000000 39000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000780 00020051 00484600 ff554600 00000000 => query spot ac voltage/current
// Response 534d4100000402a000000001013e0010 60654fa0 7d0042be283a00a1 7a01842a71b30001 000000000780 01020051 0c000000 15000000 01484600 61a7e95f 5a590000 5a590000 5a590000 5a590000 01000000
// 01494600 61a7e95f cf590000 cf590000 cf590000 cf590000 01000000
// 014a4600 61a7e95f 7a590000 7a590000 7a590000 7a590000 01000000
// 014b4600 61a7e95f f19a0000 f19a0000 f19a0000 f19a0000 01000000
// 014c4600 61a7e95f 3c9b0000 3c9b0000 3c9b0000 3c9b0000 01000000
// 014d4600 61a7e95f 189b0000 189b0000 189b0000 189b0000 01000000
// 014e4600 51a7e95f 1d000000 1d000000 1d000000 1d000000 01000000
// 01534640 61a7e95f 24010000 24010000 24010000 24010000 01000000
// 01544640 61a7e95f 1e010000 1e010000 1e010000 1e010000 01000000
// 01554640 61a7e95f 23010000 23010000 23010000 23010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000980 00028051 00482100 ff482100 00000000 => query device status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000980 01028051 00000000 00000000 01482108 59c5e95f 33010001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000a80 00028051 00644100 ff644100 00000000 => query grid relay status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000a80 01028051 07000000 07000000 01644108 59c5e95f 33000001 37010000 fdffff00 feffff00 00000000 00000000 00000000 00000000 00000000
}
SpeedwireInterface::~SpeedwireInterface()
@ -59,7 +27,7 @@ bool SpeedwireInterface::initialize()
}
if (m_multicast && !m_socket->joinMulticastGroup(m_multicastAddress)) {
qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_socket->errorString();
qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_socket->errorString();
return false;
}
@ -73,7 +41,7 @@ void SpeedwireInterface::deinitialize()
if (m_initialized) {
if (m_multicast) {
if (!m_socket->leaveMulticastGroup(m_multicastAddress)) {
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << m_multicastAddress.toString();
qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString();
}
m_socket->close();
@ -111,6 +79,7 @@ SpeedwireInterface::SpeedwireHeader SpeedwireInterface::parseHeader(QDataStream
void SpeedwireInterface::sendData(const QByteArray &data)
{
//qCDebug(dcSma()) << "Send data:" << data.toHex();
m_socket->writeDatagram(data, m_address, m_port);
}
@ -129,7 +98,7 @@ void SpeedwireInterface::readPendingDatagrams()
continue;
qCDebug(dcSma()) << "SpeedwireInterface: Received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
//qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
emit dataReceived(datagram);
}
}

View File

@ -5,9 +5,6 @@
#include <QUdpSocket>
#include <QDataStream>
class SpeedwireInterface : public QObject
{
Q_OBJECT
@ -44,10 +41,8 @@ public:
inline bool isValid() const {
return smaSignature == 0x534d4100 && protocolId != ProtocolIdUnknown;
}
};
explicit SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent = nullptr);
~SpeedwireInterface();

221
sma/speedwireinverter.cpp Normal file
View File

@ -0,0 +1,221 @@
#include "speedwireinverter.h"
#include "extern-plugininfo.h"
#include <QDateTime>
SpeedwireInverter::SpeedwireInverter(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, &SpeedwireInverter::processData);
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00028053 001e2500 ff1e2500 00000000 => query spot dc power
// Response 534d4100000402a000000001005e0010 606517a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01028053 00000000 01000000 011e2540 61a7e95f 57000000 57000000 57000000 57000000 01000000
// 021e2540 61a7e95f 5e000000 5e000000 5e000000 5e000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000580 00028053 001f4500 ff214500 00000000 => query spot dc voltage/current
// Response 534d4100000402a00000000100960010 606525a0 7d0042be283a00a1 7a01842a71b30001 000000000580 01028053 02000000 05000000 011f4540 61a7e95f 05610000 05610000 05610000 05610000 01000000
// 021f4540 61a7e95f 505b0000 505b0000 505b0000 505b0000 01000000
// 01214540 61a7e95f 60010000 60010000 60010000 60010000 01000000
// 02214540 61a7e95f 95010000 95010000 95010000 95010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000680 00020051 00404600 ff424600 00000000 => query spot ac power
// Response 534d4100000402a000000001007a0010 60651ea0 7d0042be283a00a1 7a01842a71b30001 000000000680 01020051 09000000 0b000000 01404640 61a7e95f 38000000 38000000 38000000 38000000 01000000
// 01414640 61a7e95f 37000000 37000000 37000000 37000000 01000000
// 01424640 61a7e95f 39000000 39000000 39000000 39000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000780 00020051 00484600 ff554600 00000000 => query spot ac voltage/current
// Response 534d4100000402a000000001013e0010 60654fa0 7d0042be283a00a1 7a01842a71b30001 000000000780 01020051 0c000000 15000000 01484600 61a7e95f 5a590000 5a590000 5a590000 5a590000 01000000
// 01494600 61a7e95f cf590000 cf590000 cf590000 cf590000 01000000
// 014a4600 61a7e95f 7a590000 7a590000 7a590000 7a590000 01000000
// 014b4600 61a7e95f f19a0000 f19a0000 f19a0000 f19a0000 01000000
// 014c4600 61a7e95f 3c9b0000 3c9b0000 3c9b0000 3c9b0000 01000000
// 014d4600 61a7e95f 189b0000 189b0000 189b0000 189b0000 01000000
// 014e4600 51a7e95f 1d000000 1d000000 1d000000 1d000000 01000000
// 01534640 61a7e95f 24010000 24010000 24010000 24010000 01000000
// 01544640 61a7e95f 1e010000 1e010000 1e010000 1e010000 01000000
// 01554640 61a7e95f 23010000 23010000 23010000 23010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000980 00028051 00482100 ff482100 00000000 => query device status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000980 01028051 00000000 00000000 01482108 59c5e95f 33010001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000a80 00028051 00644100 ff644100 00000000 => query grid relay status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000a80 01028051 07000000 07000000 01644108 59c5e95f 33000001 37010000 fdffff00 feffff00 00000000 00000000 00000000 00000000 00000000
}
bool SpeedwireInverter::initialize()
{
return m_interface->initialize();
}
bool SpeedwireInverter::initialized() const
{
return m_interface->initialized();
}
void SpeedwireInverter::sendLoginRequest(const QString &password, bool loginAsUser)
{
// Request 534d4100000402a000000001003a0010 60650ea0 7a01842a71b30001 7d0042be283a0001 000000000280 0c04fdff 07000000 84030000 00d8e85f 00000000 c1c1c1c18888888888888888 00000000 => login command = 0xfffd040c, first = 0x00000007 (user 7, installer a), last = 0x00000384 (hier timeout), time = 0x5fdf9ae8, 0x00000000, pw 12 bytes
// Response 534d4100000402a000000001002e0010 60650be0 7d0042be283a0001 7a01842a71b30001 000000000280 0d04fdff 07000000 84030000 00d8e85f 00000000 00000000 => login OK
// Response 534d4100000402a000000001002e0010 60650be0 7d0042be283a0001 7a01842a71b30001 000100000280 0d04fdff 07000000 84030000 fddbe85f 00000000 00000000 => login INVALID PASSWORD
// command 0xfffd040c => 0x400 set? 0x00c bytecount=12?
// Build the header
QByteArray header = QByteArray::fromHex("534d4100000402a000000001003a001060650ea0");
// The payload is little endian encoded
QByteArray payload;
QDataStream payloadStream(&payload, QIODevice::WriteOnly);
payloadStream.setByteOrder(QDataStream::LittleEndian);
// Destination
payloadStream << m_modelId << m_serialNumber << static_cast<quint16>(0x0100);
// Source
payloadStream << m_interface->sourceModelId() << m_interface->sourceSerialNumber() << static_cast<quint16>(0x0100);
// Packet information
quint16 errorCode = 0;
quint16 fragmentId = 0;
quint16 packetId = m_packetId++ | 0x8000;
payloadStream << errorCode << fragmentId << packetId;
// Command
payloadStream << static_cast<quint32>(CommandQueryLogin);
// User type: 7 = user, a = installer
payloadStream << (loginAsUser ? static_cast<quint32>(0x00000007) : static_cast<quint32>(0x0000000a));
// Timeout
payloadStream << static_cast<quint32>(900); // 1s
// Current time
payloadStream << static_cast<quint32>(QDateTime::currentMSecsSinceEpoch() / 1000.0);
// Zeros
payloadStream << static_cast<quint32>(0);
// Password
QByteArray passwordData = password.toUtf8();
QByteArray encodedPassword(12, loginAsUser ? 0x88 : 0xBB);
for (int i = 0; i < password.count(); i++) {
encodedPassword[i] = passwordData.at(i) + (loginAsUser ? 0x88 : 0xBB);
}
for (int i = 0; i < encodedPassword.count(); i++) {
payloadStream << static_cast<quint8>(encodedPassword.at(i));
}
// End of data
payloadStream << static_cast<quint32>(0);
QByteArray data = header + payload;
qCDebug(dcSma()) << "Inverter: Send login request" << data.toHex();
m_interface->sendData(data);
// 534d4100000402a000000001003a001060650ea0 7a01 842a71b3 0001 7d00 42be283a 0001 000000000280 0c04fdff 07000000 84030000 00d8e85f 00000000 c1c1c1c18888888888888888 00000000 => login command = 0xfffd040c, first = 0x00000007 (user 7, installer a), last = 0x00000384 (hier timeout), time = 0x5fdf9ae8, 0x00000000, pw 12 bytes
// 534d4100000402a000000001003a001060650ea0 9013 be52007d 0001 7d00 42be283a 0001 000000000280 0c04fdff 07000000 84030000 cc96b061 00000000 c1c1c1c18888888888888888 00000000
// 534d4100000402a000000001003a001060650ea0 9013 be52007d 0001 7d00 42be283a 0001 000000000180 0c04fdff 07000000 84030000 ae9db061 00000000 c1c1c1c18888888888888888 00000000
}
void SpeedwireInverter::querySoftwareVersion()
{
// Request 534d4100000402a00000000100260010 606509a0 7a01 842a71b30001 7d00 42be283a0001 000000000380 00020058 00348200 ff348200 00000000 => query software version
// Response 534d4100000402a000000001004e0010 606513a0 7d00 42be283a00a1 7a01 842a71b30001 000000000380 01020058 0a000000 0a000000 01348200 2ae5e65f 00000000 00000000 feffffff feffffff 040a1003 040a1003 00000000 00000000 00000000 code = 0x00823401 3 (BCD).10 (BCD).10 (BIN) Typ R (Enum)
// ========= header
// == 534d4100000402a00000000100260010
// 534d4100 : SMA\0 signature
// 0004 : header length
// 02a0 : Tag0 type
// 0000 : Tag version
// 0001 : Group
// 0026 : payload length
// 0010 : SMA Net 2 version
// == 606509a0
// 6065 : inverter protocol id
// 09 : length of long words = payload length / 4
// a0 : control ?
// ========= payload (big endian)
// == 7a01842a71b30001
// 7a01 : destination model id
// 842a71b3 : destination serial number
// 0001 : destination control field
// == 7d0042be283a0001
// 7d00 : source model id
// 42be283a: source serial number
// 0001 : source control field
// == 0000 0000 0380 00020058
// 0000 : error code
// 0000 : fragment id
// 0380 : packet id
// 00020058 : command id = CommandQueryDevice
// 00348200 : first register
// ff348200 : last register
// 00000000 : end of data
qCDebug(dcSma()) << "Inverter: Query software version...";
// The payload is little endian encoded
QByteArray payload;
QDataStream payloadStream(&payload, QIODevice::WriteOnly);
payloadStream.setByteOrder(QDataStream::LittleEndian);
// Destination
payloadStream << m_modelId << m_serialNumber << static_cast<quint16>(0x1000);
// Source
payloadStream << m_interface->sourceModelId() << m_interface->sourceSerialNumber() << static_cast<quint16>(0x1000);
// Packet information
quint16 errorCode = 0;
quint16 fragmentId = 0;
m_packetId += 1;
quint16 packetId = m_packetId | 0x8000;
payloadStream << errorCode << fragmentId << packetId;
// Request information
payloadStream << static_cast<quint32>(CommandQueryDevice);
payloadStream << static_cast<quint32>(0x00823400); // Software version first
payloadStream << static_cast<quint32>(0x008234ff); // Software version last
// End of data
payloadStream << static_cast<quint32>(0);
// Build the header
QByteArray header = QByteArray::fromHex("534d4100000402a00000000100260010606509a0");
QByteArray data = header + payload;
m_interface->sendData(data);
}
void SpeedwireInverter::queryDeviceType()
{
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00020058 001e8200 ff208200 00000000 => query device type
// Response 534d4100000402a000000001009e0010 606527a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01020058 01000000 03000000 011e8210 6f89e95f 534e3a20 33303130 35333831 31360000 00000000 00000000 00000000 00000000
// 011f8208 6f89e95f 411f0001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 => 1f41 solar inverter
// 01208208 6f89e95f 96240000 80240000 81240001 82240000 feffff00 00000000 00000000 00000000 00000000
}
void SpeedwireInverter::processData(const QByteArray &data)
{
qCDebug(dcSma()) << "Inverter: data received" << data.toHex();
}

75
sma/speedwireinverter.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef SPEEDWIREINVERTER_H
#define SPEEDWIREINVERTER_H
#include <QObject>
#include "speedwireinterface.h"
class SpeedwireInverter : public QObject
{
Q_OBJECT
public:
enum Command {
CommandQueryAc = 0x51000200,
CommandQueryStatus = 0x51800200,
CommandQueryDevice = 0x58000200,
CommandQueryDc = 0x53800200,
CommandQueryLogin = 0xfffd040c
};
Q_ENUM(Command)
class Request
{
public:
Request();
SpeedwireInverter::Command command() const;
quint16 requestId() const;
private:
SpeedwireInverter::Command m_command;
quint16 m_requestId = 0;
};
explicit SpeedwireInverter(const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent = nullptr);
bool initialize();
bool initialized() const;
double currentPower() const;
double totalEnergyProduced() const;
// Query methods
void sendLoginRequest(const QString &password = "0000", bool loginAsUser = true);
void querySoftwareVersion();
void queryDeviceType();
signals:
void valuesUpdated();
private:
SpeedwireInterface *m_interface = nullptr;
QHostAddress m_address;
bool m_initialized = false;
quint16 m_modelId = 0;
quint32 m_serialNumber = 0;
quint16 m_packetId = 0;
// Properties
double m_currentPower = 0;
double m_totalEnergyProduced = 0;
QString m_softwareVersion;
private slots:
void processData(const QByteArray &data);
};
#endif // SPEEDWIREINVERTER_H

View File

@ -218,28 +218,28 @@ void SpeedwireMeter::processData(const QByteArray &data)
if (measurementIndex == 1 && measurement != 0) {
m_totalEnergyConsumed = measurement / 3600000.0;
qCDebug(dcSma()) << "Total energy consumed" << m_totalEnergyConsumed << "kWh";
qCDebug(dcSma()) << "Meter: Total energy consumed" << m_totalEnergyConsumed << "kWh";
} else if (measurementIndex == 2 && measurement != 0) {
m_totalEnergyProduced = measurement / 3600000.0;
qCDebug(dcSma()) << "Total energy produced" << m_totalEnergyProduced << "kWh";
qCDebug(dcSma()) << "Meter: Total energy produced" << m_totalEnergyProduced << "kWh";
} else if (measurementIndex == 21 && measurement != 0) {
m_energyConsumedPhaseA = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy consumed phase A" << m_energyConsumedPhaseA << "kWh";
qCDebug(dcSma()) << "Meter: Energy consumed phase A" << m_energyConsumedPhaseA << "kWh";
} else if (measurementIndex == 41 && measurement != 0) {
m_energyConsumedPhaseB = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy consumed phase B" << m_energyConsumedPhaseB << "kWh";
qCDebug(dcSma()) << "Meter: Energy consumed phase B" << m_energyConsumedPhaseB << "kWh";
} else if (measurementIndex == 61 && measurement != 0) {
m_energyConsumedPhaseC = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy consumed phase C" << m_energyConsumedPhaseC << "kWh";
qCDebug(dcSma()) << "Meter: Energy consumed phase C" << m_energyConsumedPhaseC << "kWh";
} else if (measurementIndex == 22 && measurement != 0) {
m_energyProducedPhaseA = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy produced phase A" << m_energyProducedPhaseA << "kWh";
qCDebug(dcSma()) << "Meter: Energy produced phase A" << m_energyProducedPhaseA << "kWh";
} else if (measurementIndex == 42 && measurement != 0) {
m_energyProducedPhaseB = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy produced phase B" << m_energyProducedPhaseB << "kWh";
qCDebug(dcSma()) << "Meter: Energy produced phase B" << m_energyProducedPhaseB << "kWh";
} else if (measurementIndex == 62 && measurement != 0) {
m_energyProducedPhaseC = measurement / 3600000.0;
qCDebug(dcSma()) << "Energy produced phase C" << m_energyProducedPhaseC << "kWh";
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;

View File

@ -14,7 +14,6 @@ public:
bool initialize();
bool initialized() const;
double currentPower() const;
double totalEnergyProduced() const;
double totalEnergyConsumed() const;