From 8b9c38b04d3f80ed3960a741a33682e766073eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 9 Dec 2021 07:00:32 +0100 Subject: [PATCH] Add basics for inverter communication --- nymea-plugins.pro | 2 +- sma/integrationpluginsma.cpp | 66 +++++++++- sma/integrationpluginsma.h | 8 +- sma/integrationpluginsma.json | 149 +++++++++++++++++++++++ sma/obisdata.cpp | 6 - sma/obisdata.h | 11 -- sma/sma.pro | 4 +- sma/speedwireinterface.cpp | 43 +------ sma/speedwireinterface.h | 5 - sma/speedwireinverter.cpp | 221 ++++++++++++++++++++++++++++++++++ sma/speedwireinverter.h | 75 ++++++++++++ sma/speedwiremeter.cpp | 16 +-- sma/speedwiremeter.h | 1 - 13 files changed, 532 insertions(+), 75 deletions(-) delete mode 100644 sma/obisdata.cpp delete mode 100644 sma/obisdata.h create mode 100644 sma/speedwireinverter.cpp create mode 100644 sma/speedwireinverter.h diff --git a/nymea-plugins.pro b/nymea-plugins.pro index 24070d4f..61d4205a 100644 --- a/nymea-plugins.pro +++ b/nymea-plugins.pro @@ -118,7 +118,7 @@ isEmpty(WITH_PLUGINS) { } PLUGINS-=$${WITHOUT_PLUGINS} -#FIXME: PLUGINS=sma +# FIXME: PLUGINS=sma message("Building plugins:") for(plugin, PLUGINS) { exists($${plugin}) { diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index cdf92103..a92624c0 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -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(thing->paramValue(speedwireInverterThingSerialNumberParamTypeId).toUInt()); + quint16 modelId = static_cast(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()); } } diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 516a5e1b..169fc1e5 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -31,11 +31,12 @@ #ifndef INTEGRATIONPLUGINSMA_H #define INTEGRATIONPLUGINSMA_H -#include "integrations/integrationplugin.h" -#include "plugintimer.h" -#include "sunnywebbox.h" +#include +#include +#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 m_sunnyWebBoxes; QHash m_speedwireMeters; + QHash m_speedwireInverters; }; #endif // INTEGRATIONPLUGINSMA_H diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index b751c93d..a2a7311f 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -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": "" + } + ] } ] } diff --git a/sma/obisdata.cpp b/sma/obisdata.cpp deleted file mode 100644 index 5866185f..00000000 --- a/sma/obisdata.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "obisdata.h" - -ObisData::ObisData() -{ - -} diff --git a/sma/obisdata.h b/sma/obisdata.h deleted file mode 100644 index 0ebe468e..00000000 --- a/sma/obisdata.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef OBISDATA_H -#define OBISDATA_H - - -class ObisData -{ -public: - ObisData(); -}; - -#endif // OBISDATA_H diff --git a/sma/sma.pro b/sma/sma.pro index 313dbae1..a64270a2 100644 --- a/sma/sma.pro +++ b/sma/sma.pro @@ -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 diff --git a/sma/speedwireinterface.cpp b/sma/speedwireinterface.cpp index e30db2f8..23001852 100644 --- a/sma/speedwireinterface.cpp +++ b/sma/speedwireinterface.cpp @@ -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); } } diff --git a/sma/speedwireinterface.h b/sma/speedwireinterface.h index e619a803..bd240edd 100644 --- a/sma/speedwireinterface.h +++ b/sma/speedwireinterface.h @@ -5,9 +5,6 @@ #include #include - - - 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(); diff --git a/sma/speedwireinverter.cpp b/sma/speedwireinverter.cpp new file mode 100644 index 00000000..1184abfe --- /dev/null +++ b/sma/speedwireinverter.cpp @@ -0,0 +1,221 @@ +#include "speedwireinverter.h" +#include "extern-plugininfo.h" + +#include + +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(0x0100); + + // Source + payloadStream << m_interface->sourceModelId() << m_interface->sourceSerialNumber() << static_cast(0x0100); + + // Packet information + quint16 errorCode = 0; + quint16 fragmentId = 0; + quint16 packetId = m_packetId++ | 0x8000; + payloadStream << errorCode << fragmentId << packetId; + + // Command + payloadStream << static_cast(CommandQueryLogin); + + // User type: 7 = user, a = installer + payloadStream << (loginAsUser ? static_cast(0x00000007) : static_cast(0x0000000a)); + + // Timeout + payloadStream << static_cast(900); // 1s + + // Current time + payloadStream << static_cast(QDateTime::currentMSecsSinceEpoch() / 1000.0); + + // Zeros + payloadStream << static_cast(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(encodedPassword.at(i)); + } + + // End of data + payloadStream << static_cast(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(0x1000); + + // Source + payloadStream << m_interface->sourceModelId() << m_interface->sourceSerialNumber() << static_cast(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(CommandQueryDevice); + payloadStream << static_cast(0x00823400); // Software version first + payloadStream << static_cast(0x008234ff); // Software version last + + // End of data + payloadStream << static_cast(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(); + +} diff --git a/sma/speedwireinverter.h b/sma/speedwireinverter.h new file mode 100644 index 00000000..2d1c3ee5 --- /dev/null +++ b/sma/speedwireinverter.h @@ -0,0 +1,75 @@ +#ifndef SPEEDWIREINVERTER_H +#define SPEEDWIREINVERTER_H + +#include + +#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 diff --git a/sma/speedwiremeter.cpp b/sma/speedwiremeter.cpp index 330ec6f7..b460bd63 100644 --- a/sma/speedwiremeter.cpp +++ b/sma/speedwiremeter.cpp @@ -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; diff --git a/sma/speedwiremeter.h b/sma/speedwiremeter.h index 97e6e4f7..82f251c9 100644 --- a/sma/speedwiremeter.h +++ b/sma/speedwiremeter.h @@ -14,7 +14,6 @@ public: bool initialize(); bool initialized() const; - double currentPower() const; double totalEnergyProduced() const; double totalEnergyConsumed() const;