Implement first meter version

This commit is contained in:
Simon Stürz 2021-12-06 18:23:24 +01:00
parent cd9c048ebd
commit a30d338de9
13 changed files with 1228 additions and 78 deletions

View File

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

View File

@ -31,13 +31,19 @@
#include "integrationpluginsma.h"
#include "plugininfo.h"
#include "network/networkdevicediscovery.h"
#include <network/networkdevicediscovery.h>
#include "speedwirediscovery.h"
IntegrationPluginSma::IntegrationPluginSma()
{
}
void IntegrationPluginSma::init()
{
}
void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
{
if (info->thingClassId() == sunnyWebBoxThingClassId) {
@ -84,18 +90,44 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info)
info->addThingDescriptors(descriptors);
info->finish(Thing::ThingErrorNoError);
});
} else if (info->thingClassId() == speedwireInverterThingClassId) {
SpeedwireInterface *speedwireDiscovery = new SpeedwireInterface(info);
} 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, &SpeedwireInterface::discoveryFinished, this, [=](){
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);
});
@ -145,7 +177,49 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info)
connect(m_refreshTimer, &PluginTimer::timeout, this, &IntegrationPluginSma::onRefreshTimer);
}
});
} else {
} 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()) {
info->finish(Thing::ThingErrorHardwareFailure);
return;
}
// TODO: reachable state
connect(meter, &SpeedwireMeter::valuesUpdated, this, [=](){
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 {
Q_ASSERT_X(false, "setupThing", QString("Unhandled thingClassId: %1").arg(thing->thingClassId().toString()).toUtf8());
}
}

View File

@ -35,7 +35,7 @@
#include "plugintimer.h"
#include "sunnywebbox.h"
#include "speedwireinterface.h"
#include "speedwiremeter.h"
class IntegrationPluginSma: public IntegrationPlugin {
Q_OBJECT
@ -45,6 +45,7 @@ class IntegrationPluginSma: public IntegrationPlugin {
public:
explicit IntegrationPluginSma();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
@ -59,6 +60,7 @@ private slots:
private:
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, SunnyWebBox *> m_sunnyWebBoxes;
QHash<Thing *, SpeedwireMeter *> m_speedwireMeters;
};
#endif // INTEGRATIONPLUGINSMA_H

View File

@ -26,7 +26,7 @@
{
"id": "03f32361-4e13-4597-a346-af8d16a986b3",
"name": "macAddress",
"displayName": "hardware address",
"displayName": "MAC address",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
@ -88,10 +88,10 @@
},
{
"id": "0c5097af-e136-4430-9fb4-0ccbb30c3e1c",
"name": "speedwireInverter",
"displayName": "SMA Inverter Speedwire",
"name": "speedwireMeter",
"displayName": "SMA Energy Meter",
"createMethods": ["discovery", "user"],
"interfaces": ["solarinverter"],
"interfaces": [ "energymeter" ],
"paramTypes": [
{
"id": "d90193e6-a996-4e49-bf6d-564d596d7e74",
@ -104,10 +104,26 @@
{
"id": "2780eab7-1f1c-4cc7-a789-a8790329ca9e",
"name": "macAddress",
"displayName": "hardware address",
"displayName": "MAC address",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
},
{
"id": "7c81a0c5-9bc6-43bb-a01a-4de5fe656bba",
"name": "serialNumber",
"displayName": "Serial number",
"type": "QString",
"inputType": "TextLine",
"readOnly": true
},
{
"id": "abdc114d-1fac-4454-8b82-871ed5cdf28c",
"name": "modelId",
"displayName": "Model ID",
"type": "uint",
"inputType": "TextLine",
"readOnly": true
}
],
"stateTypes": [
@ -120,7 +136,61 @@
"defaultValue": false
},
{
"id": "015868bd-cc35-44cf-b631-78ea7c73b967",
"id": "44ee2491-8376-41cd-a21d-185c736152ec",
"name": "voltagePhaseA",
"displayName": "Voltage phase A",
"displayNameEvent": "Voltage phase A changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "56ae3555-f874-4c2d-8833-17573dce477a",
"name": "voltagePhaseB",
"displayName": "Voltage phase B",
"displayNameEvent": "Voltage phase B changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "51cbb29b-29f0-480a-9d7d-b8f4e6a205ae",
"name": "voltagePhaseC",
"displayName": "Voltage phase C",
"displayNameEvent": "Voltage phase C changed",
"type": "double",
"unit": "Volt",
"defaultValue": 0
},
{
"id": "45bbdbef-1832-4870-bff5-299e580fb4da",
"name": "currentPhaseA",
"displayName": "Current phase A",
"displayNameEvent": "Current phase A changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "b3a4fdd2-b6b8-4c58-9da3-2084ad414022",
"name": "currentPhaseB",
"displayName": "Current phase B",
"displayNameEvent": "Current phase B changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "b3655188-3854-4336-ae3c-61d3bda6fc4d",
"name": "currentPhaseC",
"displayName": "Current phase C",
"displayNameEvent": "Current phase C changed",
"type": "double",
"unit": "Ampere",
"defaultValue": 0
},
{
"id": "d4ac7f37-e30a-44e4-93cb-ad16df18b8f1",
"name": "currentPower",
"displayName": "Current power",
"displayNameEvent": "Current power changed",
@ -129,13 +199,111 @@
"defaultValue": 0
},
{
"id": "f29b6283-873b-45f5-8a14-622d34f11d4f",
"id": "c5d09c63-7461-4fb8-a6fe-bc7aa919be30",
"name": "currentPowerPhaseA",
"displayName": "Current power phase A",
"displayNameEvent": "Current power phase A changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "c52d4422-b521-4804-a7a7-c4398e91e760",
"name": "currentPowerPhaseB",
"displayName": "Current power phase B",
"displayNameEvent": "Current power phase B changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "555e892c-3ca7-4100-9832-6ac13b87eb04",
"name": "currentPowerPhaseC",
"displayName": "Current power phase C",
"displayNameEvent": "Current power phase C changed",
"type": "double",
"unit": "Watt",
"defaultValue": 0
},
{
"id": "4fb0a4c1-18ed-4d02-b6d0-c07e9b96a56d",
"name": "totalEnergyConsumed",
"displayName": "Total energy consumed",
"displayNameEvent": "Total energy consumed changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "76ca68d8-6781-4d2a-8663-440aec40b4de",
"name": "totalEnergyProduced",
"displayName": "Total energy produced",
"displayNameEvent": "Total energy produced changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0
"defaultValue": 0.00
},
{
"id": "b4ff2c71-f81d-4904-bbac-0c0c6e8a5a33",
"name": "energyConsumedPhaseA",
"displayName": "Energy consumed phase A",
"displayNameEvent": "Energy consumed phase A changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "c4e5f569-ac5d-4761-a898-888880bfd59f",
"name": "energyConsumedPhaseB",
"displayName": "Energy consumed phase B",
"displayNameEvent": "Energy consumed phase B changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "aabc02d7-8dc3-4637-8bf2-dc2e0e737ad3",
"name": "energyConsumedPhaseC",
"displayName": "Energy consumed phase C",
"displayNameEvent": "Energy consumed phase C changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "754c3b67-768a-47f7-99d8-f66c198f0835",
"name": "energyProducedPhaseA",
"displayName": "Energy produced phase A",
"displayNameEvent": "Energy produced phase A changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "7eb08c45-24cf-40ce-be28-f3564f087672",
"name": "energyProducedPhaseB",
"displayName": "Energy produced phase B",
"displayNameEvent": "Energy produced phase B changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "1eb2bf01-5ec6-42e5-b348-ac1e95199d14",
"name": "energyProducedPhaseC",
"displayName": "Energy produced phase C",
"displayNameEvent": "Energy produced phase C changed",
"type": "double",
"unit": "KiloWattHour",
"defaultValue": 0.00
},
{
"id": "a685393c-8b7e-42c5-bb41-f9907c074626",
"name": "firmwareVersion",
"displayName": "Firmware version",
"displayNameEvent": "Firmware version changed",
"type": "QString",
"defaultValue": ""
}
]
}

6
sma/obisdata.cpp Normal file
View File

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

11
sma/obisdata.h Normal file
View File

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

View File

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

365
sma/speedwirediscovery.cpp Normal file
View File

@ -0,0 +1,365 @@
#include "speedwirediscovery.h"
#include "extern-plugininfo.h"
#include <QDataStream>
#include <speedwirediscovery.h>
SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) :
QObject(parent),
m_networkDeviceDiscovery(networkDeviceDiscovery)
{
// More details: https://github.com/RalfOGit/libspeedwire/
// //! Multicast device discovery request packet, according to SMA documentation.
// //! However, this does not seem to be supported anymore with version 3.x devices
// const unsigned char multicast_request[] = {
// 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
// const unsigned char unicast_request[] = {
// 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
// };
// // 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, 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(m_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 == m_discoveryDatagramMulticast || datagram == m_discoveryDatagramUnicast)
return;
QDataStream stream(datagram);
stream.setByteOrder(QDataStream::BigEndian);
SpeedwireInterface::SpeedwireHeader header = SpeedwireInterface::parseHeader(stream);
if (!header.isValid()) {
qCWarning(dcSma()) << "SpeedwireDiscovery: Datagram header is not valid. Ignoring data...";
return;
}
// Example data:
// 534d4100 0004 02a0 0000 0001 0244 0010 6069 010e 7143 69ae e618a416 00010400000000000001080000000021391229100002040000004415000208000000001575a137d800030400000000000003080000000003debed0e800040400000017c6000408000000001008c2070000090400000000000009080000000027c77bed20000a04000000481d000a08000000001722823410000d0400000003b00015040000000000001508000000000d1e1e0e3000160400000015120016080000000006c5a2d8b800170400000000000017080000000001bd6f680000180400000007990018080000000004def712b8001d040000000000001d08000000000eeefaafd0001e040000001666001e0800000000074b38bf88001f040000000a300020040000037bcb00210400000003ad0029040000000000002908000000000a9b1afec8002a040000001a81002a08000000000803e62b88002b040000000000002b080000000001511459b8002c0400000006d5002c0800000000052c8455b80031040000000000003108000000000cf83b37100032040000001b5f0032080000000008a6e257f80033040000000c3f003404000003747900350400000003c8003d040000000000003d08000000000a53d0ba08003e040000001482003e080000000007800fd188003f040000000000003f080000000001185820c8004004000000095800400800000000064563b1900045040000000000004508000000000d26d3eae0004604000000168900460800000000082b4fc5a80047040000000a440048040000037ed1004904000000038e900000000102085200000000
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Header";
qCDebug(dcSma()) << "SpeedwireDiscovery: Length:" << header.headerLength;
qCDebug(dcSma()) << "SpeedwireDiscovery: Tag0:" << header.tagType;
qCDebug(dcSma()) << "SpeedwireDiscovery: Tag0 version:" << header.tagVersion;
qCDebug(dcSma()) << "SpeedwireDiscovery: Group:" << header.group << (header.group == 1 ? "(default group)" : "");
qCDebug(dcSma()) << "SpeedwireDiscovery: Data length:" << header.payloadLength << datagram.length();
qCDebug(dcSma()) << "SpeedwireDiscovery: SMA Net 2 Version" << header.smaNet2Version;
qCDebug(dcSma()) << "SpeedwireDiscovery: Protocol ID" << header.protocolId;
if (header.protocolId == SpeedwireInterface::ProtocolIdDiscoveryResponse) {
qCDebug(dcSma()) << "SpeedwireDiscovery: Received discovery response from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
// Response: 534d4100 0004 02a0 0000 0001 0002 0000 0001
// "192.168.178.25:9522" "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"
// "192.168.178.22:9522" "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(m_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 == SpeedwireInterface::ProtocolIdMeter) {
// Example: 010e 714369ae
quint16 modelId;
quint32 serialNumber;
stream >> modelId >> serialNumber;
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Meter identifier";
qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << modelId;
qCDebug(dcSma()) << "SpeedwireDiscovery: 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 == SpeedwireInterface::ProtocolIdInverter) {
quint16 modelId;
quint32 serialNumber;
stream >> modelId >> serialNumber;
qCDebug(dcSma()) << "SpeedwireDiscovery: ======================= Inverter identifier";
qCDebug(dcSma()) << "SpeedwireDiscovery: Model ID:" << modelId;
qCDebug(dcSma()) << "SpeedwireDiscovery: Serial number:" << serialNumber;
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 = modelId;
m_results[senderAddress].serialNumber = serialNumber;
} else {
qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex();
return;
}
}
void SpeedwireDiscovery::sendDiscoveryRequest()
{
if (m_multicastSocket->writeDatagram(m_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();
}

75
sma/speedwirediscovery.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef SPEEDWIREDISCOVERY_H
#define SPEEDWIREDISCOVERY_H
#include <QTimer>
#include <QObject>
#include <QUdpSocket>
#include <network/networkdevicediscovery.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 = QHostAddress("239.12.255.254");
quint16 m_port = 9522;
bool m_initialized = false;
// Discovery
QTimer m_discoveryTimer;
bool m_discoveryRunning = false;
NetworkDeviceInfos m_networkDeviceInfos;
QHash<QHostAddress, SpeedwireDiscoveryResult> m_results;
// Static discovery datagrams for speedwire
QByteArray m_discoveryDatagramMulticast = QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000");
QByteArray m_discoveryResponseDatagram = QByteArray::fromHex("534d4100000402A000000001000200000001");
QByteArray m_discoveryDatagramUnicast = QByteArray::fromHex("534d4100000402a00000000100260010606509a0ffffffffffff00007d0052be283a000000000000018000020000000000000000000000000000");
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

View File

@ -1,49 +1,85 @@
#include "speedwireinterface.h"
#include "extern-plugininfo.h"
#include <QTimer>
SpeedwireInterface::SpeedwireInterface(QObject *parent) :
QObject(parent)
SpeedwireInterface::SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent) :
QObject(parent),
m_address(address),
m_multicast(multicast)
{
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()
{
if (m_initialized) {
if (!m_socket->leaveMulticastGroup(m_multicastAddress)) {
qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString();
}
m_socket->close();
}
deinitialize();
}
bool SpeedwireInterface::initialize()
{
// If we already initialized the socket, we are done
if (m_initialized)
return true;
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()) << "SpeedwireDiscovery: 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()) << "SpeedwireDiscovery: Failed to leave multicast group" << m_multicastAddress.toString();
}
m_socket->close();
m_initialized = false;
if (!m_socket->bind(QHostAddress::AnyIPv4, m_port, QAbstractSocket::ShareAddress)) {
qCWarning(dcSma()) << "SpeedwireInterface: Cannot bind to port" << m_port << m_socket->errorString();
return false;
}
if (!m_socket->joinMulticastGroup(m_multicastAddress)) {
qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << m_multicastAddress.toString() << m_socket->errorString();
return false;
}
m_initialized = true;
return true;
}
bool SpeedwireInterface::initialized() const
@ -51,52 +87,50 @@ bool SpeedwireInterface::initialized() const
return m_initialized;
}
bool SpeedwireInterface::startDiscovery()
quint16 SpeedwireInterface::sourceModelId() const
{
if (m_discoveryRunning)
return true;
qCDebug(dcSma()) << "SpeedwireInterface: Start discovering network...";
if (!m_initialized) {
qCDebug(dcSma()) << "SpeedwireInterface: Failed to start discovery because the socket has not been initialized successfully.";
return false;
}
// Discovery message
QByteArray discoveryDatagram = QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000");
if (m_socket->write(discoveryDatagram) < 0) {
qCWarning(dcSma()) << "SpeedwireInterface: Failed to send discovery datagram to multicast address" << m_multicastAddress.toString();
return false;
}
m_discoveryRunning = true;
QTimer::singleShot(10000, this, [=](){
qCDebug(dcSma()) << "SpeedwireInterface: Discovey finished.";
m_discoveryRunning = false;
emit discoveryFinished();
});
return true;
return m_sourceModelId;
}
bool SpeedwireInterface::discoveryRunning() const
quint32 SpeedwireInterface::sourceSerialNumber() const
{
return m_discoveryRunning;
return m_sourceSerialNumber;
}
SpeedwireInterface::SpeedwireHeader SpeedwireInterface::parseHeader(QDataStream &stream)
{
SpeedwireHeader 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;
}
void SpeedwireInterface::sendData(const QByteArray &data)
{
m_socket->writeDatagram(data, m_address, m_port);
}
void SpeedwireInterface::readPendingDatagrams()
{
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()) << "SpeedwireInterface: Data received from" << senderAddress.toString() << datagram.toHex();
//emit datagramReceived(senderAddress, datagram);
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);
}
}

View File

@ -3,29 +3,81 @@
#include <QObject>
#include <QUdpSocket>
#include <QDataStream>
class SpeedwireInterface : public QObject
{
Q_OBJECT
public:
explicit SpeedwireInterface(QObject *parent = nullptr);
enum ProtocolId {
ProtocolIdUnknown = 0x0000,
ProtocolIdMeter = 0x6069,
ProtocolIdInverter = 0x6065,
ProtocolIdDiscoveryResponse = 0x0001,
ProtocolIdDiscovery = 0xffff
};
Q_ENUM(ProtocolId)
enum DeviceType {
DeviceTypeUnknown,
DeviceTypeMeter,
DeviceTypeInverter
};
Q_ENUM(DeviceType)
class SpeedwireHeader
{
public:
SpeedwireHeader() = 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 == 0x534d4100 && protocolId != ProtocolIdUnknown;
}
};
explicit SpeedwireInterface(const QHostAddress &address, bool multicast, QObject *parent = nullptr);
~SpeedwireInterface();
bool initialize();
void deinitialize();
bool initialized() const;
bool startDiscovery();
bool discoveryRunning() const;
quint16 sourceModelId() const;
quint32 sourceSerialNumber() const;
static SpeedwireInterface::SpeedwireHeader parseHeader(QDataStream &stream);
public slots:
void sendData(const QByteArray &data);
signals:
void discoveryFinished();
void dataReceived(const QByteArray &data);
private:
QUdpSocket *m_socket = nullptr;
QHostAddress m_multicastAddress = QHostAddress("239.12.255.254");
QHostAddress m_address;
quint16 m_port = 9522;
QHostAddress m_multicastAddress = QHostAddress("239.12.255.254");
bool m_multicast = false;
bool m_initialized = false;
bool m_discoveryRunning = false;
// Requester
quint16 m_sourceModelId = 0x007d;
quint32 m_sourceSerialNumber = 0x3a28be42;
private slots:
void readPendingDatagrams();

270
sma/speedwiremeter.cpp Normal file
View File

@ -0,0 +1,270 @@
#include "speedwiremeter.h"
#include "extern-plugininfo.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);
}
bool SpeedwireMeter::initialize()
{
return m_interface->initialize();
}
bool SpeedwireMeter::initialized() const
{
return m_interface->initialized();
}
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::processData(const QByteArray &data)
{
qCDebug(dcSma()) << "Meter: data received" << data.toHex();
QDataStream stream(data);
stream.setByteOrder(QDataStream::BigEndian);
SpeedwireInterface::SpeedwireHeader header = SpeedwireInterface::parseHeader(stream);
if (!header.isValid()) {
qCDebug(dcSma()) << "Meter: Datagram header is not valid. Ignoring data...";
return;
}
if (header.protocolId != SpeedwireInterface::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()) << "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";
} else if (measurementIndex == 21 && measurement != 0) {
m_energyConsumedPhaseA = measurement / 3600000.0;
qCDebug(dcSma()) << "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";
} else if (measurementIndex == 61 && measurement != 0) {
m_energyConsumedPhaseC = measurement / 3600000.0;
qCDebug(dcSma()) << "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";
} else if (measurementIndex == 42 && measurement != 0) {
m_energyProducedPhaseB = measurement / 3600000.0;
qCDebug(dcSma()) << "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";
} 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
quint8 major, minor, build, revision;
stream >> major >> minor >> build >> revision;
// Revision types:
// S: Special version
// A: Alpha version
// B: Beta version
// R: Release version
// E: Experimental version
// N: No revision
m_softwareVersion = QString("%1.%2.%3-%4").arg(major).arg(minor).arg(build).arg(QChar(revision));
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.";
}
}
emit valuesUpdated();
}

86
sma/speedwiremeter.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef SPEEDWIREMETER_H
#define SPEEDWIREMETER_H
#include <QObject>
#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;
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 valuesUpdated();
private:
SpeedwireInterface *m_interface = nullptr;
QHostAddress m_address;
bool m_initialized = false;
quint16 m_modelId = 0;
quint32 m_serialNumber = 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 processData(const QByteArray &data);
};
#endif // SPEEDWIREMETER_H