From e4721de1c59208a341b0d7fca3b420ceb7acfed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 5 Apr 2023 12:15:18 +0200 Subject: [PATCH 1/4] SMA: Improve speedwire communication and discovery. --- sma/integrationpluginsma.cpp | 37 ++++- sma/integrationpluginsma.h | 4 + sma/speedwire/speedwire.h | 54 +++++-- sma/speedwire/speedwirediscovery.cpp | 157 +++++++++++-------- sma/speedwire/speedwirediscovery.h | 14 +- sma/speedwire/speedwireinterface.cpp | 28 ++-- sma/speedwire/speedwireinterface.h | 7 +- sma/speedwire/speedwireinverter.cpp | 216 ++++++++++++++++++++------- sma/speedwire/speedwireinverter.h | 4 +- 9 files changed, 362 insertions(+), 159 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 4488a26..8131d05 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -96,8 +96,8 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) } else if (info->thingClassId() == speedwireMeterThingClassId) { // Note: does not require the network device discovery... - SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info); - if (!speedwireDiscovery->initialize(SpeedwireInterface::DeviceTypeMeter)) { + SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), getLocalSerialNumber(), 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; @@ -145,8 +145,8 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) return; } - SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), info); - if (!speedwireDiscovery->initialize(SpeedwireInterface::DeviceTypeInverter)) { + SpeedwireDiscovery *speedwireDiscovery = new SpeedwireDiscovery(hardwareManager()->networkDeviceDiscovery(), getLocalSerialNumber(), 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; @@ -302,7 +302,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) // Create the multicast interface if not created already. if (!m_multicastInterface) - m_multicastInterface = new SpeedwireInterface(true, this); + m_multicastInterface = new SpeedwireInterface(true, getLocalSerialNumber(), this); quint32 serialNumber = static_cast(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt()); quint16 modelId = static_cast(thing->paramValue(speedwireMeterThingModelIdParamTypeId).toUInt()); @@ -541,6 +541,11 @@ void IntegrationPluginSma::thingRemoved(Thing *thing) } if (thing->thingClassId() == speedwireInverterThingClassId && m_speedwireInverters.contains(thing)) { + // Remove invalid password from settings + pluginStorage()->beginGroup(thing->id().toString()); + pluginStorage()->remove(""); + pluginStorage()->endGroup(); + m_speedwireInverters.take(thing)->deleteLater(); } @@ -796,6 +801,28 @@ void IntegrationPluginSma::markModbusInverterAsDisconnected(Thing *thing) thing->setStateValue(modbusInverterCurrentPowerStateTypeId, 0); } +quint64 IntegrationPluginSma::getLocalSerialNumber() +{ + m_localSerialNumber = pluginStorage()->value("localSerialNumber", 0).toUInt(); + + if (m_localSerialNumber == 0) { + srand(QDateTime::currentMSecsSinceEpoch() / 1000); + // Generate one and save it for the next time, each instance should have it's own serial number + QByteArray data; + QDataStream inStream(&data, QIODevice::ReadWrite); + for (int i = 0; i < 4; i++) { + inStream << static_cast(rand() % 256); + } + + QDataStream outStream(data); + outStream >> m_localSerialNumber; + pluginStorage()->setValue("localSerialNumber", m_localSerialNumber); + } + + qCInfo(dcSma()) << "Using local serial number" << m_localSerialNumber; + return m_localSerialNumber; +} + bool IntegrationPluginSma::isModbusValueValid(quint32 value) { return value != 0xffffffff; diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 397e005..627e33f 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -80,6 +80,8 @@ private: QHash m_speedwireInverters; QHash m_modbusInverters; + quint32 m_localSerialNumber = 0; + // Shared interface accross meters SpeedwireInterface *m_multicastInterface = nullptr; @@ -87,6 +89,8 @@ private: void markSpeedwireInverterAsDisconnected(Thing *thing); void markModbusInverterAsDisconnected(Thing *thing); + quint64 getLocalSerialNumber(); + // Sma modbus data validation bool isModbusValueValid(quint32 value); bool isModbusValueValid(qint32 value); diff --git a/sma/speedwire/speedwire.h b/sma/speedwire/speedwire.h index b1f1734..916a34b 100644 --- a/sma/speedwire/speedwire.h +++ b/sma/speedwire/speedwire.h @@ -101,6 +101,7 @@ public: //static QHash deviceTypes = { {0x0000, "Unknwon"} }; + static quint16 sourceModelId() { return 120; } static quint16 port() { return 9522; } static QHostAddress multicastAddress() { return QHostAddress("239.12.255.254"); } static quint32 smaSignature() { return 0x534d4100; } @@ -115,19 +116,44 @@ public: // 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, // 0xffffffff group, 0x0000 length, 0x0020 "SMA Net ?", Version ? // 0x00, 0x00, 0x00, 0x00 // 0x0000 protocol, 0x00 #long words, 0x00 ctrl - // Unicast device discovery request packet, according to SMA documentation - // 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0 - // 0x00, 0x00, 0x00, 0x01, 0x00, 0x26, 0x00, 0x10, // 0x26 length, 0x0010 "SMA Net 2", Version 0 - // 0x60, 0x65, 0x09, 0xa0, 0xff, 0xff, 0xff, 0xff, // 0x6065 protocol, 0x09 #long words, 0xa0 ctrl, 0xffff dst susyID any, 0xffffffff dst serial any - // 0xff, 0xff, 0x00, 0x00, 0x7d, 0x00, 0x52, 0xbe, // 0x0000 dst cntrl, 0x007d src susy id, 0x3a28be52 src serial - // 0x28, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000 src cntrl, 0x0000 error code, 0x0000 fragment ID - // 0x01, 0x80, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // 0x8001 packet ID - // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x00, 0x00 - static QByteArray discoveryDatagramMulticast() { return QByteArray::fromHex("534d4100000402a0ffffffff0000002000000000"); } static QByteArray discoveryResponseDatagram() { return QByteArray::fromHex("534d4100000402A000000001000200000001"); } - static QByteArray discoveryDatagramUnicast() { return QByteArray::fromHex("534d4100000402a00000000100260010606509a0ffffffffffff00007d0052be283a000000000000018000020000000000000000000000000000"); } + + static QByteArray pingRequest( quint16 sourceSusyId, quint32 sourceSerialNumber) { + QByteArray requestData; + QDataStream stream(&requestData, QIODevice::WriteOnly); + stream << smaSignature(); + stream << static_cast(0x0004); // header length + stream << tag0(); + stream << static_cast(0x00000001); // group + stream << static_cast(0x0026); // entry length + stream << smaNet2Version(); + stream << static_cast(ProtocolIdInverter); + + // From now on little endian + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(0x09); // length + stream << static_cast(0xa0); // control + stream << static_cast(0xffff); // destination susyID + stream << static_cast(0xffffffff); // destination serial + stream << static_cast(0x00); // job id + stream << static_cast(sourceSusyId); // source susyID + stream << static_cast(sourceSerialNumber); // source susyID + stream << static_cast(0x00); // job id + stream << static_cast(0x00); // status + stream << static_cast(0x00); // packet count + stream << static_cast(0x8001); // packet id + stream << static_cast(0x00); // command + stream << static_cast(0x02); // param count + stream << static_cast(0x00); // Object + + stream << static_cast(0x0); // Param 1 + stream << static_cast(0x0); // Param 2 + + stream << static_cast(0x0); // Packet end + + return requestData; + } static Speedwire::Header parseHeader(QDataStream &stream) { stream.setByteOrder(QDataStream::BigEndian); @@ -139,7 +165,7 @@ public: stream >> protocolId; header.protocolId = static_cast(protocolId); return header; - }; + } static Speedwire::InverterPacket parseInverterPacket(QDataStream &stream) { // Make sure the data stream is little endian @@ -158,7 +184,7 @@ public: stream >> packet.packetId; stream >> packet.command; return packet; - }; + } }; inline QDebug operator<<(QDebug debug, const Speedwire::Header &header) @@ -174,7 +200,7 @@ inline QDebug operator<<(QDebug debug, const Speedwire::InverterPacket &packet) debug.nospace() << ", command: " << packet.command; debug.nospace() << ", error: " << packet.errorCode; debug.nospace() << ", fragment: " << packet.fragmentId; - debug.nospace() << ", packet ID: " << packet.fragmentId; + debug.nospace() << ", packet ID: " << packet.packetId; debug.nospace() << ")"; return debug.maybeSpace(); } diff --git a/sma/speedwire/speedwirediscovery.cpp b/sma/speedwire/speedwirediscovery.cpp index 06d399c..24132e6 100644 --- a/sma/speedwire/speedwirediscovery.cpp +++ b/sma/speedwire/speedwirediscovery.cpp @@ -33,9 +33,10 @@ #include -SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent) : +SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint32 localSerialNumber, QObject *parent) : QObject(parent), - m_networkDeviceDiscovery(networkDeviceDiscovery) + m_networkDeviceDiscovery(networkDeviceDiscovery), + m_localSerialNumber(localSerialNumber) { // More details: https://github.com/RalfOGit/libspeedwire/ @@ -50,8 +51,8 @@ SpeedwireDiscovery::SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDisc SpeedwireDiscovery::~SpeedwireDiscovery() { if (m_initialized && m_multicastSocket) { - if (!m_multicastSocket->leaveMulticastGroup(m_multicastAddress)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << m_multicastAddress.toString(); + if (!m_multicastSocket->leaveMulticastGroup(Speedwire::multicastAddress())) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << Speedwire::multicastAddress().toString(); } m_multicastSocket->close(); @@ -123,7 +124,7 @@ bool SpeedwireDiscovery::discoveryRunning() const QList SpeedwireDiscovery::discoveryResult() const { - return m_results.values(); + return m_results; } bool SpeedwireDiscovery::setupMulticastSocket() @@ -137,18 +138,33 @@ bool SpeedwireDiscovery::setupMulticastSocket() connect(m_multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); // 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(); + if (!m_multicastSocket->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind multicast socket to port" << Speedwire::port() << m_multicastSocket->errorString(); m_multicastSocket->deleteLater(); m_multicastSocket = nullptr; return false; } - if (!m_multicastSocket->joinMulticastGroup(m_multicastAddress)) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_multicastSocket->errorString(); - m_multicastSocket->deleteLater(); - m_multicastSocket = nullptr; - return false; + + // Join all available interfaces the multicast group + foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { + if(interface.isValid() && !interface.flags().testFlag(QNetworkInterface::IsLoopBack) + && interface.flags().testFlag(QNetworkInterface::CanMulticast) + && interface.flags().testFlag(QNetworkInterface::IsRunning)) { + + QList addressEntries = interface.addressEntries(); + for (int i = 0; i < addressEntries.length(); i++) { + + + if (addressEntries.at(i).ip().protocol() == QAbstractSocket::IPv4Protocol) { + if (!m_multicastSocket->joinMulticastGroup(Speedwire::multicastAddress(), interface)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Could not join multicast group" << Speedwire::multicastAddress().toString() << "on interface" << interface << m_multicastSocket->errorString(); + } else { + qCDebug(dcSma()) << "SpeedwireDiscovery: Joined successfully multicast group on" << interface; + } + } + } + } } return true; @@ -158,17 +174,10 @@ void SpeedwireDiscovery::startMulticastDiscovery() { qCDebug(dcSma()) << "SpeedwireDiscovery: Start multicast discovery..."; m_multicastRunning = true; - - // Start sending multicast messages - sendDiscoveryRequest(); - - // Give 5 seconds time, on one of the requests sent in this period the meter would have responded... - QTimer::singleShot(5000, this, [this](){ - m_multicastRunning = false; - evaluateDiscoveryFinished(); - }); - m_multicastSearchRequestTimer.start(); + + // Start sending multicast messages periodically + sendDiscoveryRequest(); } bool SpeedwireDiscovery::setupUnicastSocket() @@ -182,8 +191,8 @@ bool SpeedwireDiscovery::setupUnicastSocket() connect(m_unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSocketError(QAbstractSocket::SocketError))); // 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(); + if (!m_unicastSocket->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Initialization failed. Could not bind to port" << Speedwire::port() << m_multicastSocket->errorString(); m_unicastSocket->deleteLater(); m_unicastSocket = nullptr; return false; @@ -208,6 +217,9 @@ void SpeedwireDiscovery::startUnicastDiscovery() qCDebug(dcSma()) << "Discovery finished. Found" << discoveryReply->networkDeviceInfos().count() << "network devices for unicast requests."; // Wait some extra second in otder to give the last hosts joined some time to respond. QTimer::singleShot(3000, this, [this](){ + m_multicastSearchRequestTimer.stop(); + m_multicastRunning = false; + m_unicastRunning = false; evaluateDiscoveryFinished(); }); @@ -216,7 +228,7 @@ void SpeedwireDiscovery::startUnicastDiscovery() void SpeedwireDiscovery::sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress) { - if (m_unicastSocket->writeDatagram(Speedwire::discoveryDatagramUnicast(), targetHostAddress, m_port) < 0) { + if (m_unicastSocket->writeDatagram(Speedwire::pingRequest(Speedwire::sourceModelId(), m_localSerialNumber), targetHostAddress, Speedwire::port()) < 0) { qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send unicast discovery datagram to address" << targetHostAddress.toString(); return; } @@ -235,8 +247,13 @@ void SpeedwireDiscovery::readPendingDatagramsMulticast() while (socket->hasPendingDatagrams()) { datagram.resize(socket->pendingDatagramSize()); socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); + + // Ignore our own requests + if (QNetworkInterface::allAddresses().contains(senderAddress)) + continue; + qCDebug(dcSma()) << "SpeedwireDiscovery: Received multicast data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort); - //qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex(); + qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex(); processDatagram(senderAddress, senderPort, datagram); } } @@ -252,8 +269,13 @@ void SpeedwireDiscovery::readPendingDatagramsUnicast() while (socket->hasPendingDatagrams()) { datagram.resize(socket->pendingDatagramSize()); socket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); + + // Ignore our own requests + if (QNetworkInterface::allAddresses().contains(senderAddress)) + continue; + qCDebug(dcSma()) << "SpeedwireDiscovery: Received unicast data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort); - //qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex(); + qCDebug(dcSma()) << "SpeedwireDiscovery: " << datagram.toHex(); processDatagram(senderAddress, senderPort, datagram); } } @@ -276,10 +298,6 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin return; } - // Ignore discovery requests - if (datagram == Speedwire::discoveryDatagramMulticast() || datagram == Speedwire::discoveryDatagramUnicast()) - return; - QDataStream stream(datagram); Speedwire::Header header = Speedwire::parseHeader(stream); if (!header.isValid()) { @@ -300,24 +318,7 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin return; } - if (!m_results.contains(senderAddress)) { - qCDebug(dcSma()) << "SpeedwireDiscovery: --> Found SMA device on" << senderAddress.toString(); - if (!m_networkDeviceInfos.hasHostAddress(senderAddress)) { - qCDebug(dcSma()) << "SpeedwireDiscovery: Found SMA using UDP discovery but the host is not in the network discovery result list. Not adding to results" << senderAddress.toString(); - } - - 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); - } - } + qCDebug(dcSma()) << "SpeedwireDiscovery: --> Found SMA device on" << senderAddress.toString(); return; } @@ -330,37 +331,62 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin stream >> modelId >> serialNumber; qCDebug(dcSma()) << "SpeedwireDiscovery: Meter identifier: Model ID:" << modelId << "Serial number:" << serialNumber; - if (!m_results.contains(senderAddress)) { + if (!m_resultMeters.contains(senderAddress)) { SpeedwireDiscoveryResult result; result.address = senderAddress; result.deviceType = SpeedwireInterface::DeviceTypeMeter; - m_results.insert(senderAddress, result); + m_resultMeters.insert(senderAddress, result); } if (m_networkDeviceInfos.hasHostAddress(senderAddress)) { - m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress); + m_resultMeters[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress); } - m_results[senderAddress].modelId = modelId; - m_results[senderAddress].serialNumber = serialNumber; + m_resultMeters[senderAddress].modelId = modelId; + m_resultMeters[senderAddress].serialNumber = serialNumber; } else if (header.protocolId == Speedwire::ProtocolIdInverter) { Speedwire::InverterPacket inverterPacket = Speedwire::parseInverterPacket(stream); // Response from inverter 534d4100 0004 02a0 0000 0001 004e 0010 6065 1390 7d00 52be283a 0000 b500 c2c12e12 0000 0000 00000 1800102000000000000000000000003000000ff0000ecd5ff1f0100b500c2c12e1200000a000c00000000000000030000000101000000000000 qCDebug(dcSma()) << "SpeedwireDiscovery:" << inverterPacket; - if (!m_results.contains(senderAddress)) { + if (!m_resultInverters.contains(senderAddress)) { SpeedwireDiscoveryResult result; result.address = senderAddress; result.deviceType = SpeedwireInterface::DeviceTypeInverter; - m_results.insert(senderAddress, result); + m_resultInverters.insert(senderAddress, result); } if (m_networkDeviceInfos.hasHostAddress(senderAddress)) { - m_results[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress); + m_resultInverters[senderAddress].networkDeviceInfo = m_networkDeviceInfos.get(senderAddress); } - m_results[senderAddress].modelId = inverterPacket.sourceModelId; - m_results[senderAddress].serialNumber = inverterPacket.sourceSerialNumber; + m_resultInverters[senderAddress].modelId = inverterPacket.sourceModelId; + m_resultInverters[senderAddress].serialNumber = inverterPacket.sourceSerialNumber; + + // Send the default login request, maybe it activates the Energy meter measurment data streaming + + SpeedwireInverter *inverter = nullptr; + if (m_inverters.contains(senderAddress)) { + inverter = m_inverters.value(senderAddress); + } else { + inverter = new SpeedwireInverter(senderAddress, Speedwire::sourceModelId(), m_localSerialNumber, this); + m_inverters.insert(senderAddress, inverter); + } + + SpeedwireInverterReply *reply = inverter->sendIdentifyRequest(); + qCDebug(dcSma()) << "SpeedwireDiscovery: send identify request to" << senderAddress.toString(); + connect(reply, &SpeedwireInverterReply::finished, this, [this, inverter, senderAddress, reply](){ + qCDebug(dcSma()) << "SpeedwireDiscovery: ############### identify request finished from" << senderAddress.toString() << reply->error(); + + SpeedwireInverterReply *loginReply = inverter->sendLoginRequest(); + qCDebug(dcSma()) << "SpeedwireDiscovery: make login attempt using the default password."; + connect(loginReply, &SpeedwireInverterReply::finished, this, [loginReply, senderAddress](){ + qCDebug(dcSma()) << "SpeedwireDiscovery: ########################## login attempt finished" << senderAddress.toString() << loginReply->error(); + }); + }); + + + } else { qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex(); return; @@ -369,12 +395,12 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin void SpeedwireDiscovery::sendDiscoveryRequest() { - if (m_multicastSocket->writeDatagram(Speedwire::discoveryDatagramMulticast(), m_multicastAddress, m_port) < 0) { - qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send discovery datagram to multicast address" << m_multicastAddress.toString(); + if (m_multicastSocket->writeDatagram(Speedwire::discoveryDatagramMulticast(), Speedwire::multicastAddress(), Speedwire::port()) < 0) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to send discovery datagram to multicast address" << Speedwire::multicastAddress().toString(); return; } - qCDebug(dcSma()) << "SpeedwireDiscovery: Sent successfully the discovery request to multicast address" << m_multicastAddress.toString(); + qCDebug(dcSma()) << "SpeedwireDiscovery: Sent successfully the discovery request to multicast address" << Speedwire::multicastAddress().toString(); } void SpeedwireDiscovery::evaluateDiscoveryFinished() @@ -386,10 +412,16 @@ void SpeedwireDiscovery::evaluateDiscoveryFinished() void SpeedwireDiscovery::finishDiscovery() { + m_results = m_resultMeters.values() + m_resultInverters.values(); + qCDebug(dcSma()) << "SpeedwireDiscovery: Discovey finished. Found" << m_results.count() << "SMA devices in the network"; m_multicastSearchRequestTimer.stop(); if (m_multicastSocket) { + if (!m_multicastSocket->leaveMultic astGroup(Speedwire::multicastAddress())) { + qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << Speedwire::multicastAddress().toString(); + } + m_multicastSocket->close(); m_multicastSocket->deleteLater(); m_multicastSocket = nullptr; @@ -401,6 +433,7 @@ void SpeedwireDiscovery::finishDiscovery() m_unicastSocket = nullptr; } + foreach (const SpeedwireDiscoveryResult &result, m_results) { qCDebug(dcSma()) << "SpeedwireDiscovery: ============================================"; qCDebug(dcSma()) << "SpeedwireDiscovery: Device type:" << result.deviceType; diff --git a/sma/speedwire/speedwirediscovery.h b/sma/speedwire/speedwirediscovery.h index b71072e..9c461b5 100644 --- a/sma/speedwire/speedwirediscovery.h +++ b/sma/speedwire/speedwirediscovery.h @@ -38,6 +38,7 @@ #include #include "speedwire.h" +#include "speedwireinverter.h" #include "speedwireinterface.h" class SpeedwireDiscovery : public QObject @@ -52,7 +53,7 @@ public: quint32 serialNumber = 0; } SpeedwireDiscoveryResult; - explicit SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, QObject *parent = nullptr); + explicit SpeedwireDiscovery(NetworkDeviceDiscovery *networkDeviceDiscovery, quint32 localSerialNumber, QObject *parent = nullptr); ~SpeedwireDiscovery(); bool initialize(SpeedwireInterface::DeviceType deviceType = SpeedwireInterface::DeviceTypeUnknown); @@ -71,14 +72,17 @@ private: SpeedwireInterface::DeviceType m_deviceType = SpeedwireInterface::DeviceTypeUnknown; QUdpSocket *m_multicastSocket = nullptr; QUdpSocket *m_unicastSocket = nullptr; - QHostAddress m_multicastAddress = Speedwire::multicastAddress(); - quint16 m_port = Speedwire::port(); + quint32 m_localSerialNumber = 0; bool m_initialized = false; // Discovery QTimer m_multicastSearchRequestTimer; NetworkDeviceInfos m_networkDeviceInfos; - QHash m_results; + QList m_results; + QHash m_resultMeters; + QHash m_resultInverters; + + QHash m_inverters; bool m_multicastRunning = false; bool m_unicastRunning = false; @@ -88,7 +92,7 @@ private: bool setupUnicastSocket(); void startUnicastDiscovery(); - ; + void sendUnicastDiscoveryRequest(const QHostAddress &targetHostAddress); private slots: diff --git a/sma/speedwire/speedwireinterface.cpp b/sma/speedwire/speedwireinterface.cpp index e6a5deb..323c10f 100644 --- a/sma/speedwire/speedwireinterface.cpp +++ b/sma/speedwire/speedwireinterface.cpp @@ -31,9 +31,10 @@ #include "speedwireinterface.h" #include "extern-plugininfo.h" -SpeedwireInterface::SpeedwireInterface(bool multicast, QObject *parent) : +SpeedwireInterface::SpeedwireInterface(bool multicast, quint32 sourceSerialNumber, QObject *parent) : QObject(parent), - m_multicast(multicast) + m_multicast(multicast), + m_sourceSerialNumber(sourceSerialNumber) { m_socket = new QUdpSocket(this); @@ -54,18 +55,18 @@ bool SpeedwireInterface::initialize(const QHostAddress &address) return false; } - // If already initialized and multicast, nothing could habe changed...done here + // If already initialized and multicast, nothing could have changed...done here if (m_initialized && m_multicast) 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; + if (!m_socket->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) { + qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not bind to port" << Speedwire::port(); return false; } - if (m_multicast && !m_socket->joinMulticastGroup(m_multicastAddress)) { - qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not join multicast group" << m_multicastAddress.toString() << m_socket->errorString(); + if (m_multicast && !m_socket->joinMulticastGroup(Speedwire::multicastAddress())) { + qCWarning(dcSma()) << "SpeedwireInterface: Initialization failed. Could not join multicast group" << Speedwire::multicastAddress().toString() << m_socket->errorString(); return false; } @@ -79,8 +80,8 @@ void SpeedwireInterface::deinitialize() { if (m_initialized) { if (m_multicast) { - if (!m_socket->leaveMulticastGroup(m_multicastAddress)) { - qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << m_multicastAddress.toString(); + if (!m_socket->leaveMulticastGroup(Speedwire::multicastAddress())) { + qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << Speedwire::multicastAddress().toString(); } } @@ -100,11 +101,6 @@ bool SpeedwireInterface::initialized() const return m_initialized; } -quint16 SpeedwireInterface::sourceModelId() const -{ - return m_sourceModelId; -} - quint32 SpeedwireInterface::sourceSerialNumber() const { return m_sourceSerialNumber; @@ -112,8 +108,8 @@ quint32 SpeedwireInterface::sourceSerialNumber() const void SpeedwireInterface::sendData(const QByteArray &data) { - qCDebug(dcSma()) << "SpeedwireInterface: -->" << m_address.toString() << m_port << data.toHex(); - if (m_socket->writeDatagram(data, m_address, m_port) < 0) { + qCDebug(dcSma()) << "SpeedwireInterface: -->" << m_address.toString() << Speedwire::port() << data.toHex(); + if (m_socket->writeDatagram(data, m_address, Speedwire::port()) < 0) { qCWarning(dcSma()) << "SpeedwireInterface: failed to send data" << m_socket->errorString(); } } diff --git a/sma/speedwire/speedwireinterface.h b/sma/speedwire/speedwireinterface.h index 4a1efe1..2730334 100644 --- a/sma/speedwire/speedwireinterface.h +++ b/sma/speedwire/speedwireinterface.h @@ -48,7 +48,7 @@ public: }; Q_ENUM(DeviceType) - explicit SpeedwireInterface(bool multicast, QObject *parent = nullptr); + explicit SpeedwireInterface(bool multicast, quint32 sourceSerialNumber, QObject *parent = nullptr); ~SpeedwireInterface(); bool initialize(const QHostAddress &address = QHostAddress()); @@ -70,14 +70,11 @@ signals: private: QUdpSocket *m_socket = nullptr; QHostAddress m_address; - quint16 m_port = Speedwire::port(); - QHostAddress m_multicastAddress = Speedwire::multicastAddress(); bool m_multicast = false; bool m_initialized = false; // Requester - quint16 m_sourceModelId = 0x007d; - quint32 m_sourceSerialNumber = 0x3a28be52; + quint32 m_sourceSerialNumber = 0; private slots: void readPendingDatagrams(); diff --git a/sma/speedwire/speedwireinverter.cpp b/sma/speedwire/speedwireinverter.cpp index 50ac67b..6557c92 100644 --- a/sma/speedwire/speedwireinverter.cpp +++ b/sma/speedwire/speedwireinverter.cpp @@ -40,7 +40,7 @@ SpeedwireInverter::SpeedwireInverter(const QHostAddress &address, quint16 modelI m_serialNumber(serialNumber) { qCDebug(dcSma()) << "Inverter: setup interface on" << m_address.toString(); - m_interface = new SpeedwireInterface(false, this); + m_interface = new SpeedwireInterface(false, serialNumber, this); connect(m_interface, &SpeedwireInterface::dataReceived, this, &SpeedwireInverter::processData); } @@ -153,7 +153,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendIdentifyRequest() SpeedwireInverterRequest request; request.setPacketId(0x8001); request.setCommand(Speedwire::CommandIdentify); - request.setRequestData(Speedwire::discoveryDatagramUnicast()); + request.setRequestData(Speedwire::pingRequest(Speedwire::sourceModelId(), m_serialNumber)); return createReply(request); } @@ -171,9 +171,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendLoginRequest(const QString &passw QDataStream stream(&datagram, QIODevice::WriteOnly); buildDefaultHeader(stream, 58, 0xa0); - // Reset the packet id counter, otherwise there will be no response - //m_packetId = 0; - quint16 packetId = m_packetId++ | 0x8000; + quint16 packetId = static_cast(m_packetId++) | 0x8000; Speedwire::Command command = Speedwire::CommandLogin; // The payload is little endian encoded @@ -227,7 +225,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendLogoutRequest() buildDefaultHeader(stream, 34); // Reset the packet id counter, otherwise there will be no response - quint16 packetId = m_packetId++ | 0x8000; + quint16 packetId = static_cast(m_packetId++) | 0x8000; Speedwire::Command command = Speedwire::CommandLogout; // The payload is little endian encoded @@ -241,7 +239,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendLogoutRequest() stream << static_cast(0x0300); // Source - stream << m_interface->sourceModelId(); + stream << Speedwire::sourceModelId(); stream << m_interface->sourceSerialNumber(); stream << static_cast(0x0300); @@ -275,7 +273,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendSoftwareVersionRequest() buildDefaultHeader(stream, 38, 0xa0); // Reset the packet id counter, otherwise there will be no response - quint16 packetId = m_packetId++ | 0x8000; + quint16 packetId = static_cast(m_packetId++) | 0x8000; Speedwire::Command command = Speedwire::CommandQueryDevice; // The payload is little endian encoded @@ -305,7 +303,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendDeviceTypeRequest() buildDefaultHeader(stream, 38, 0xa0); // Reset the packet id counter, otherwise there will be no response - quint16 packetId = m_packetId++ | 0x8000; + quint16 packetId = static_cast(m_packetId++) | 0x8000; Speedwire::Command command = Speedwire::CommandQueryDevice; // The payload is little endian encoded @@ -326,6 +324,36 @@ SpeedwireInverterReply *SpeedwireInverter::sendDeviceTypeRequest() return createReply(request); } +SpeedwireInverterReply *SpeedwireInverter::sendBatteryInfoRequest() +{ + qCDebug(dcSma()) << "Inverter: Sending battery info request to" << m_address.toString(); + // Build the header + QByteArray datagram; + QDataStream stream(&datagram, QIODevice::WriteOnly); + buildDefaultHeader(stream, 38, 0xa0); + + // Reset the packet id counter, otherwise there will be no response + quint16 packetId = static_cast(m_packetId++) | 0x8000; + Speedwire::Command command = Speedwire::CommandQueryAc; + + // The payload is little endian encoded + buildPacket(stream, command, packetId); + + // 2 words + stream << static_cast(0x00491E00); + stream << static_cast(0x00495DFF); + + // End of data + stream << static_cast(0); + + // Final datagram + SpeedwireInverterRequest request; + request.setPacketId(packetId); + request.setCommand(command); + request.setRequestData(datagram); + return createReply(request); +} + void SpeedwireInverter::startConnecting(const QString &password) { m_password = password; @@ -431,7 +459,7 @@ void SpeedwireInverter::buildPacket(QDataStream &stream, quint32 command, quint1 // Destination Ctrl stream << static_cast(0x0100); // Source - stream << m_interface->sourceModelId() << m_interface->sourceSerialNumber(); + stream << Speedwire::sourceModelId() << m_interface->sourceSerialNumber(); // Destination Ctrl stream << static_cast(0x0100); @@ -489,7 +517,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendQueryRequest(Speedwire::Command c buildDefaultHeader(stream); // Reset the packet id counter, otherwise there will be no response - quint16 packetId = m_packetId++ | 0x8000; + quint16 packetId = static_cast(m_packetId++) | 0x8000; // The payload is little endian encoded buildPacket(stream, command, packetId); @@ -514,20 +542,20 @@ void SpeedwireInverter::processSoftwareVersionResponse(const QByteArray &respons // 07000000 07000000 01348200 2ff5b261 00000000 00000000 feffffff feffffff 00055302 00055302 00000000 00000000 00000000 qCDebug(dcSma()) << "Inverter: Process software version request response" << response.toHex(); // TODO: -// QDataStream stream(response); -// stream.setByteOrder(QDataStream::LittleEndian); + // QDataStream stream(response); + // stream.setByteOrder(QDataStream::LittleEndian); -// // First -// quint32 firstWord; -// quint32 secondWord; -// stream >> firstWord >> secondWord; -// quint8 byte1, byte2, byte3, byte4; -// stream >> byte1 >> byte2 >> byte3 >> byte4; + // // First + // quint32 firstWord; + // quint32 secondWord; + // stream >> firstWord >> secondWord; + // quint8 byte1, byte2, byte3, byte4; + // stream >> byte1 >> byte2 >> byte3 >> byte4; // BCD // 00 82 34 01 ?? -// QString softwareVersion = QString("%1.%2.%3.%4").arg(byte1).arg(byte2).arg(byte3).arg(byte4); -// qCDebug(dcSma()) << "Inverter: Software version" << softwareVersion; + // QString softwareVersion = QString("%1.%2.%3.%4").arg(byte1).arg(byte2).arg(byte3).arg(byte4); + // qCDebug(dcSma()) << "Inverter: Software version" << softwareVersion; } @@ -938,6 +966,46 @@ void SpeedwireInverter::processGridFrequencyResponse(const QByteArray &response) } } +void SpeedwireInverter::processBatteryInfoResponse(const QByteArray &response) +{ + // 10000000 10000000 + // 01574600 c20cbb61 89130000 89130000 89130000 89130000 010000000 + // 0000000 + qCDebug(dcSma()) << "Inverter: ################ Process battery info response" << response.toHex(); + // QDataStream stream(response); + // stream.setByteOrder(QDataStream::LittleEndian); + // quint32 firstWord, secondWord; + // stream >> firstWord >> secondWord; + + //// // Each line has 7 words + //// quint32 measurementId; + //// quint32 measurementType; // ? + + ////// while (!stream.atEnd()) { + ////// // First row + ////// stream >> measurementId; + + ////// // End of data, we are done + ////// if (measurementId == 0) + ////// return; + + ////// // Unknown + ////// stream >> measurementType; + + ////// quint8 measurmentNumber = static_cast(measurementId & 0xff); + ////// measurementId = measurementId & 0x00ffff00; + + ////// // Read measurent lines + ////// if (measurementId == 0x465700 && measurmentNumber == 0x01) { + ////// quint32 frequency; + ////// stream >> frequency; + ////// m_gridFrequency = readValue(frequency, 100.0); + ////// qCDebug(dcSma()) << "Inverter: Grid frequency" << m_gridFrequency << "Hz"; + ////// readUntilEndOfMeasurement(stream); + ////// } + ////// } +} + void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response) { // 00000000 00000000 @@ -1083,43 +1151,88 @@ void SpeedwireInverter::setState(State state) setReachable(false); break; case StateInitializing: { - // Try to fetch ac power - qCDebug(dcSma()) << "Inverter: Request AC power..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { + + if (m_modelId == 372) { + // Home manager 2.0, no login, just fetch...testing + + // ############# TESTING ########## + + // Query battery info + qCDebug(dcSma()) << "Inverter: Request battery info..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491e00, 0x00495dff); // Battery infos + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - - // TODO: try to send identify request and retry 3 times before giving up, - // still need to figure out why the inverter stops responding sometimes and how we can - // make it communicative again, a reconfugre always fixes this issue...somehow... - - setState(StateDisconnected); - return; + // setState(StateDisconnected); + // return; } - // Reachable, but received an inverter error, probably not logged - if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { - qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; - setState(StateLogin); - return; + qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); + processBatteryInfoResponse(reply->responsePayload()); + + + qCDebug(dcSma()) << "Inverter: Request battery SoC..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00295A00, 0x00295AFF); // SoC + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); + // setState(StateDisconnected); + // return; + } else { + processBatteryInfoResponse(reply->responsePayload()); + } + qCDebug(dcSma()) << "Inverter: Request battery termperature..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491E00, 0x00495DFF); // SoC + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); + // setState(StateDisconnected); + // return; + } else { + processBatteryInfoResponse(reply->responsePayload()); + } + }); + }); + }); + + } else { + // Try to fetch ac power + qCDebug(dcSma()) << "Inverter: Request AC power..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { + qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); + + // TODO: try to send identify request and retry 3 times before giving up, + // still need to figure out why the inverter stops responding sometimes and how we can + // make it communicative again, a reconfugre always fixes this issue...somehow... + + setState(StateDisconnected); + return; + } + + // Reachable, but received an inverter error, probably not logged + if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { + qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; + setState(StateLogin); + return; + } } - } - // We where able to read data...emit the signal for the setup just incase - emit loginFinished(true); + // We where able to read data...emit the signal for the setup just incase + emit loginFinished(true); - qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); - processAcPowerResponse(reply->responseData()); + qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); + processAcPowerResponse(reply->responseData()); - - if (m_deviceInformationFetched) { - setState(StateQueryData); - } else { - setState(StateGetInformation); - } - }); + if (m_deviceInformationFetched) { + setState(StateQueryData); + } else { + setState(StateGetInformation); + } + }); + } break; } case StateLogin: { @@ -1248,6 +1361,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processAcTotalPowerResponse(reply->responsePayload()); + // Query grid frequency qCDebug(dcSma()) << "Inverter: Request grid frequency..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00465700, 0x004657ff); diff --git a/sma/speedwire/speedwireinverter.h b/sma/speedwire/speedwireinverter.h index 57888e7..d749eb5 100644 --- a/sma/speedwire/speedwireinverter.h +++ b/sma/speedwire/speedwireinverter.h @@ -100,6 +100,7 @@ public: SpeedwireInverterReply *sendLogoutRequest(); SpeedwireInverterReply *sendSoftwareVersionRequest(); SpeedwireInverterReply *sendDeviceTypeRequest(); + SpeedwireInverterReply *sendBatteryInfoRequest(); // Start connecting void startConnecting(const QString &password = "0000"); @@ -124,7 +125,7 @@ private: bool m_reachable = false; State m_state = StateDisconnected; - quint16 m_packetId = 1; + quint8 m_packetId = 1; bool m_deviceInformationFetched = false; @@ -185,6 +186,7 @@ private: void processDcVoltageCurrentResponse(const QByteArray &response); void processEnergyProductionResponse(const QByteArray &response); void processGridFrequencyResponse(const QByteArray &response); + void processBatteryInfoResponse(const QByteArray &response); void processInverterStatusResponse(const QByteArray &response); void readUntilEndOfMeasurement(QDataStream &stream); From 3c42c4837db6aabef3fd410a95be2db7095d28a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 25 Apr 2023 21:13:42 +0200 Subject: [PATCH 2/4] SMA: add speedwire battery handling --- sma/integrationpluginsma.cpp | 83 ++++++- sma/integrationpluginsma.h | 1 + sma/integrationpluginsma.json | 89 +++++++- sma/speedwire/speedwirediscovery.cpp | 22 +- sma/speedwire/speedwireinverter.cpp | 324 ++++++++++++++++++--------- sma/speedwire/speedwireinverter.h | 19 ++ 6 files changed, 412 insertions(+), 126 deletions(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 8131d05..e0f8050 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -116,7 +116,13 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) if (result.serialNumber == 0) continue; - ThingDescriptor descriptor(speedwireMeterThingClassId, "SMA Energy Meter (" + QString::number(result.serialNumber) + ")" , result.address.toString()); + QString thingName = "SMA Energy Meter (" + QString::number(result.serialNumber) + ")"; + + // Note: the SMA Homemanager 2 identifies it self as inverter / data provider...we filter it out here. + if (result.modelId == 372) + thingName = "SMA Home Manager 2.0 (" + QString::number(result.serialNumber) + ")"; + + ThingDescriptor descriptor(speedwireMeterThingClassId, thingName, 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) { @@ -165,6 +171,11 @@ void IntegrationPluginSma::discoverThings(ThingDiscoveryInfo *info) if (result.serialNumber == 0) continue; + // Note: the SMA Homemanager 2 identifies him self as inverter / data provider... + // we filter it out here since it is a meter and also should identify as one. + if (result.modelId == 372) + continue; + ThingDescriptor descriptor(speedwireInverterThingClassId, "SMA inverter (" + QString::number(result.serialNumber) + ")", result.address.toString()); // We found an inverter, let's check if we already added this one foreach (Thing *existingThing, myThings()) { @@ -327,6 +338,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) }); connect(meter, &SpeedwireMeter::valuesUpdated, thing, [=](){ + qCDebug(dcSma()) << "Meter values updated for" << thing->name() << meter->currentPower() << "W"; thing->setStateValue(speedwireMeterConnectedStateTypeId, true); thing->setStateValue(speedwireMeterCurrentPowerStateTypeId, meter->currentPower()); thing->setStateValue(speedwireMeterCurrentPowerPhaseAStateTypeId, meter->currentPowerPhaseA()); @@ -406,9 +418,18 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) if (!reachable) { markSpeedwireInverterAsDisconnected(thing); } + + foreach (Thing *batteryThing, myThings().filterByParentId(thing->id()).filterByThingClassId(speedwireBatteryThingClassId)) { + if (reachable) { + thing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + } else { + markSpeedwireBatteryAsDisconnected(batteryThing); + } + } }); connect(inverter, &SpeedwireInverter::valuesUpdated, thing, [=](){ + qCDebug(dcSma()) << "Inverter values updated for" << thing->name() << -inverter->totalAcPower() << "W" << inverter->totalEnergyProduced() << "kWh"; thing->setStateValue(speedwireInverterConnectedStateTypeId, true); thing->setStateValue(speedwireInverterTotalEnergyProducedStateTypeId, inverter->totalEnergyProduced()); thing->setStateValue(speedwireInverterEnergyProducedTodayStateTypeId, inverter->todayEnergyProduced()); @@ -427,9 +448,46 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) thing->setStateValue(speedwireInverterCurrentPowerMpp2StateTypeId, inverter->powerDcMpp2()); }); + connect(inverter, &SpeedwireInverter::batteryValuesUpdated, thing, [=](){ + if (!thing->setupComplete() || !inverter->batteryAvailable()) + return; + + // First check if we already set up a battery for this inverter + Things childThings = myThings().filterByParentId(thing->id()).filterByThingClassId(speedwireBatteryThingClassId); + if (childThings.isEmpty()) { + // Autocreate battery + emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(speedwireBatteryThingClassId, "SMA Battery", QString(), thing->id())); + } else { + // We can only have one battery as a child + Thing *batteryThing = childThings.first(); + batteryThing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + batteryThing->setStateValue(speedwireBatteryBatteryLevelStateTypeId, inverter->batteryCharge()); + batteryThing->setStateValue(speedwireBatteryBatteryCriticalStateTypeId, inverter->batteryCharge() < 10); + batteryThing->setStateValue(speedwireBatteryTemperatureStateTypeId, inverter->batteryTemperature()); + batteryThing->setStateValue(speedwireBatteryVoltageStateTypeId, inverter->batteryVoltage()); + batteryThing->setStateValue(speedwireBatteryCurrentStateTypeId, inverter->batteryCurrent()); + + double batteryPower = inverter->batteryVoltage() * inverter->batteryCurrent(); // P = U * I + qCDebug(dcSma()) << "Battery values updated for" << batteryThing->name() << batteryPower << "W"; + batteryThing->setStateValue(speedwireBatteryCurrentPowerStateTypeId, batteryPower); + if (batteryPower == 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "idle"); + } else if (batteryPower < 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "discharging"); + } else if (batteryPower > 0) { + batteryThing->setStateValue(speedwireBatteryChargingStateStateTypeId, "charging"); + } + } + }); + qCDebug(dcSma()) << "Inverter: Start connecting using password" << password; inverter->startConnecting(password); + } else if (thing->thingClassId() == speedwireBatteryThingClassId) { + + qCDebug(dcSma()) << "Battery: Setup SMA battery" << thing; + info->finish(Thing::ThingErrorNoError); + } else if (thing->thingClassId() == modbusInverterThingClassId) { // Handle reconfigure @@ -514,6 +572,20 @@ void IntegrationPluginSma::postSetupThing(Thing *thing) setupRefreshTimer(); + } else if (thing->thingClassId() == speedwireBatteryThingClassId) { + SpeedwireInverter *inverter = m_speedwireInverters.value(myThings().findById(thing->parentId())); + if (inverter) { + if (inverter->reachable()) { + thing->setStateValue(speedwireBatteryConnectedStateTypeId, true); + } else { + markSpeedwireBatteryAsDisconnected(thing); + } + } else { + markSpeedwireBatteryAsDisconnected(thing); + } + + setupRefreshTimer(); + } else if (thing->thingClassId() == modbusInverterThingClassId) { SmaInverterModbusTcpConnection *connection = m_modbusInverters.value(thing); if (connection) { @@ -787,6 +859,15 @@ void IntegrationPluginSma::markSpeedwireInverterAsDisconnected(Thing *thing) thing->setStateValue(speedwireInverterCurrentPowerStateTypeId, 0); } +void IntegrationPluginSma::markSpeedwireBatteryAsDisconnected(Thing *thing) +{ + thing->setStateValue(speedwireBatteryConnectedStateTypeId, false); + thing->setStateValue(speedwireBatteryVoltageStateTypeId, 0); + thing->setStateValue(speedwireBatteryCurrentStateTypeId, 0); + thing->setStateValue(speedwireBatteryCurrentPowerStateTypeId, 0); + thing->setStateValue(speedwireBatteryChargingStateStateTypeId, "idle"); +} + void IntegrationPluginSma::markModbusInverterAsDisconnected(Thing *thing) { thing->setStateValue(modbusInverterVoltagePhaseAStateTypeId, 0); diff --git a/sma/integrationpluginsma.h b/sma/integrationpluginsma.h index 627e33f..d0cb9fd 100644 --- a/sma/integrationpluginsma.h +++ b/sma/integrationpluginsma.h @@ -87,6 +87,7 @@ private: void markSpeedwireMeterAsDisconnected(Thing *thing); void markSpeedwireInverterAsDisconnected(Thing *thing); + void markSpeedwireBatteryAsDisconnected(Thing *thing); void markModbusInverterAsDisconnected(Thing *thing); quint64 getLocalSerialNumber(); diff --git a/sma/integrationpluginsma.json b/sma/integrationpluginsma.json index 2527ce9..02f9f4d 100644 --- a/sma/integrationpluginsma.json +++ b/sma/integrationpluginsma.json @@ -88,7 +88,7 @@ "name": "speedwireMeter", "displayName": "SMA Energy Meter", "createMethods": ["discovery", "user"], - "interfaces": [ "energymeter" ], + "interfaces": [ "energymeter", "connectable"], "paramTypes": [ { "id": "7c81a0c5-9bc6-43bb-a01a-4de5fe656bba", @@ -286,7 +286,7 @@ "displayName": "SMA Inverter", "createMethods": ["discovery", "user"], "setupMethod": "EnterPin", - "interfaces": [ "solarinverter" ], + "interfaces": [ "solarinverter", "connectable"], "paramTypes": [ { "id": "c8098d53-69eb-4d0b-9f07-e43c4a0ea9a9", @@ -447,6 +447,91 @@ } ] }, + { + "id": "b459dad2-f78b-4a87-a7f3-22f3147b83d8", + "name": "speedwireBattery", + "displayName": "SMA Battery", + "createMethods": ["auto"], + "setupMethod": "JustAdd", + "interfaces": [ "energystorage", "connectable"], + "paramTypes": [ ], + "stateTypes": [ + { + "id": "7f242169-c01a-4c9a-ac71-4f9fa5409875", + "name": "connected", + "displayName": "Connected", + "type": "bool", + "defaultValue": false + }, + { + "id": "d2144cad-e507-433b-a9d3-2ab9cf0c1014", + "name": "voltage", + "displayName": "Voltage", + "type": "double", + "unit": "Volt", + "defaultValue": 0, + "cached": false + }, + { + "id": "541c110d-2f56-44bb-8f7e-de55759b942d", + "name": "current", + "displayName": "Current", + "type": "double", + "unit": "Ampere", + "defaultValue": 0, + "cached": false + }, + { + "id": "6a146a40-84da-4392-8466-4176b21280d2", + "name": "temperature", + "displayName": "Temperature", + "type": "double", + "unit": "DegreeCelsius", + "defaultValue": 0.00 + }, + { + "id": "f0f69109-83a4-4b2a-9e16-66aa33c2e169", + "name": "currentPower", + "displayName": "Current power", + "type": "double", + "unit": "Watt", + "defaultValue": 0.00 + }, + { + "id": "38a413cd-3d09-482d-8d25-b602db3b6540", + "name": "capacity", + "displayName": "Available energy", + "type": "double", + "unit": "KiloWattHour", + "defaultValue": 0.00 + }, + { + "id": "d815aedf-e836-4274-9b51-2f0128420c46", + "name": "batteryCritical", + "displayName": "Battery critical", + "type": "bool", + "defaultValue": false + }, + { + "id": "ec534954-8ee4-46f4-94b6-b48b375b1d7d", + "name": "batteryLevel", + "displayName": "Battery level", + "type": "int", + "unit": "Percentage", + "minValue": 0, + "maxValue": 100, + "defaultValue": 0 + }, + { + "id": "93310fa3-8237-423b-9062-62e0626e8c70", + "name": "chargingState", + "displayName": "Charging state", + "type": "QString", + "possibleValues": ["idle", "charging", "discharging"], + "defaultValue": "idle" + } + ] + }, { "id": "12e0429e-e8ce-48bd-a11c-faaf0bd71856", "name": "modbusInverter", diff --git a/sma/speedwire/speedwirediscovery.cpp b/sma/speedwire/speedwirediscovery.cpp index 24132e6..1392009 100644 --- a/sma/speedwire/speedwirediscovery.cpp +++ b/sma/speedwire/speedwirediscovery.cpp @@ -101,18 +101,8 @@ bool SpeedwireDiscovery::startDiscovery() m_multicastRunning = false; m_unicastRunning = false; - switch(m_deviceType) { - case SpeedwireInterface::DeviceTypeMeter: - startMulticastDiscovery(); - break; - case SpeedwireInterface::DeviceTypeInverter: - startUnicastDiscovery(); - break; - default: - startUnicastDiscovery(); - startMulticastDiscovery(); - break; - } + startUnicastDiscovery(); + startMulticastDiscovery(); return true; } @@ -376,17 +366,15 @@ void SpeedwireDiscovery::processDatagram(const QHostAddress &senderAddress, quin SpeedwireInverterReply *reply = inverter->sendIdentifyRequest(); qCDebug(dcSma()) << "SpeedwireDiscovery: send identify request to" << senderAddress.toString(); connect(reply, &SpeedwireInverterReply::finished, this, [this, inverter, senderAddress, reply](){ - qCDebug(dcSma()) << "SpeedwireDiscovery: ############### identify request finished from" << senderAddress.toString() << reply->error(); + qCDebug(dcSma()) << "SpeedwireDiscovery: identify request finished from" << senderAddress.toString() << reply->error(); SpeedwireInverterReply *loginReply = inverter->sendLoginRequest(); qCDebug(dcSma()) << "SpeedwireDiscovery: make login attempt using the default password."; connect(loginReply, &SpeedwireInverterReply::finished, this, [loginReply, senderAddress](){ - qCDebug(dcSma()) << "SpeedwireDiscovery: ########################## login attempt finished" << senderAddress.toString() << loginReply->error(); + qCDebug(dcSma()) << "SpeedwireDiscovery: login attempt finished" << senderAddress.toString() << loginReply->error(); }); }); - - } else { qCWarning(dcSma()) << "SpeedwireDiscovery: Unhandled data received" << datagram.toHex(); return; @@ -418,7 +406,7 @@ void SpeedwireDiscovery::finishDiscovery() m_multicastSearchRequestTimer.stop(); if (m_multicastSocket) { - if (!m_multicastSocket->leaveMultic astGroup(Speedwire::multicastAddress())) { + if (!m_multicastSocket->leaveMulticastGroup(Speedwire::multicastAddress())) { qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << Speedwire::multicastAddress().toString(); } diff --git a/sma/speedwire/speedwireinverter.cpp b/sma/speedwire/speedwireinverter.cpp index 6557c92..0c0e566 100644 --- a/sma/speedwire/speedwireinverter.cpp +++ b/sma/speedwire/speedwireinverter.cpp @@ -144,6 +144,36 @@ double SpeedwireInverter::powerDcMpp2() const return m_powerDcMpp2; } +bool SpeedwireInverter::batteryAvailable() const +{ + return m_batteryAvailable; +} + +double SpeedwireInverter::batteryCycles() const +{ + return m_batteryCycles; +} + +double SpeedwireInverter::batteryCharge() const +{ + return m_batteryCharge; +} + +double SpeedwireInverter::batteryTemperature() const +{ + return m_batteryTemperature; +} + +double SpeedwireInverter::batteryCurrent() const +{ + return m_batteryCurrent; +} + +double SpeedwireInverter::batteryVoltage() const +{ + return m_batteryVoltage; +} + SpeedwireInverterReply *SpeedwireInverter::sendIdentifyRequest() { // Request 534d4100000402a000000001002600106065 09 a0 ffff ffffffff 0000 7d00 52be283a 0000 0000 0000 0180 00020000 000000000000000000000000 @@ -968,42 +998,122 @@ void SpeedwireInverter::processGridFrequencyResponse(const QByteArray &response) void SpeedwireInverter::processBatteryInfoResponse(const QByteArray &response) { - // 10000000 10000000 - // 01574600 c20cbb61 89130000 89130000 89130000 89130000 010000000 - // 0000000 - qCDebug(dcSma()) << "Inverter: ################ Process battery info response" << response.toHex(); - // QDataStream stream(response); - // stream.setByteOrder(QDataStream::LittleEndian); - // quint32 firstWord, secondWord; - // stream >> firstWord >> secondWord; + // Charging + // 32000000 34000000 + // 095b4940 95ed5064 d2000000 d2000000 d2000000 d2000000 01000000 + // 095c4900 95ed5064 98530000 98530000 98530000 98530000 01000000 + // 095d4940 95ed5064 e1010000 e1010000 e1010000 e1010000 01000000 + // 00000000 - //// // Each line has 7 words - //// quint32 measurementId; - //// quint32 measurementType; // ? + // Disacharging + // 32000000 34000000 + // 095b4940 b74e5364 dc000000 dc000000 dc000000 dc000000 01000000 + // 095c4900 b74e5364 e87b0000 e87b0000 e87b0000 e87b0000 01000000 + // 095d4940 b74e5364 b6f8ffff b6f8ffff b6f8ffff b6f8ffff 01000000 + // 00000000 + qCDebug(dcSma()) << "Inverter: Process battery info response" << response.toHex(); - ////// while (!stream.atEnd()) { - ////// // First row - ////// stream >> measurementId; + QDataStream stream(response); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 firstWord, secondWord; + stream >> firstWord >> secondWord; - ////// // End of data, we are done - ////// if (measurementId == 0) - ////// return; + // Each line has 7 words + quint32 measurementId; + quint32 measurementType; // ? - ////// // Unknown - ////// stream >> measurementType; + while (!stream.atEnd()) { + // First row + stream >> measurementId; - ////// quint8 measurmentNumber = static_cast(measurementId & 0xff); - ////// measurementId = measurementId & 0x00ffff00; + // End of data, we are done + if (measurementId == 0) + return; - ////// // Read measurent lines - ////// if (measurementId == 0x465700 && measurmentNumber == 0x01) { - ////// quint32 frequency; - ////// stream >> frequency; - ////// m_gridFrequency = readValue(frequency, 100.0); - ////// qCDebug(dcSma()) << "Inverter: Grid frequency" << m_gridFrequency << "Hz"; - ////// readUntilEndOfMeasurement(stream); - ////// } - ////// } + // Unknown + stream >> measurementType; + + quint8 measurmentNumber = static_cast(measurementId & 0xff); + measurementId = measurementId & 0x00ffff00; + + // Read measurent lines + if (measurementId == 0x495a00) { + quint32 batteryCycles; + stream >> batteryCycles; + m_batteryCycles = readValue(batteryCycles); + qCDebug(dcSma()) << "Battery: Cycle count" << m_batteryCycles; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495b00) { + qint32 batteryTemperature; + stream >> batteryTemperature; + m_batteryTemperature = readValue(batteryTemperature, 10.0); + qCDebug(dcSma()) << "Battery: Temperature" << m_batteryTemperature << "°C"; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495c00) { + quint32 batteryVoltage; + stream >> batteryVoltage; + m_batteryVoltage = readValue(batteryVoltage, 100.0); + qCDebug(dcSma()) << "Battery: Voltage" << m_batteryVoltage << "V"; + readUntilEndOfMeasurement(stream); + } else if (measurementId == 0x495d00) { + qint32 batteryCurrent; + stream >> batteryCurrent; + m_batteryCurrent = readValue(batteryCurrent, 1000.0); + qCDebug(dcSma()) << "Battery: Current" << m_batteryCurrent << "A"; + readUntilEndOfMeasurement(stream); + } else { + quint32 unknwonValue; + stream >> unknwonValue; + qCDebug(dcSma()) << "Battery: Measurement ID:" << QString("0x%1").arg(measurementId , 0, 16) << "Measurement number:" << QString("0x%1").arg(measurmentNumber, 0, 16); + qCDebug(dcSma()) << "Battery: Unknown value:" << QString("0x%1").arg(unknwonValue , 0, 16) << unknwonValue; + readUntilEndOfMeasurement(stream); + } + } +} + +void SpeedwireInverter::processBatteryChargeResponse(const QByteArray &response) +{ + qCDebug(dcSma()) << "Inverter: Process battery charge response" << response.toHex(); + + QDataStream stream(response); + stream.setByteOrder(QDataStream::LittleEndian); + quint32 firstWord, secondWord; + stream >> firstWord >> secondWord; + + // Each line has 7 words + quint32 measurementId; + quint32 measurementType; // ? + + while (!stream.atEnd()) { + // First row + stream >> measurementId; + + // End of data, we are done + if (measurementId == 0) + return; + + // Unknown + stream >> measurementType; + + quint8 measurmentNumber = static_cast(measurementId & 0xff); + measurementId = measurementId & 0x00ffff00; + + // Read measurent lines + + if (measurementId == 0x295a00) { + quint32 batteryCharge; + stream >> batteryCharge; + m_batteryCharge = readValue(batteryCharge); + qCDebug(dcSma()) << "Battery: Level" << m_batteryCharge << "%"; + readUntilEndOfMeasurement(stream); + } else { + quint32 unknwonValue; + stream >> unknwonValue; + qCDebug(dcSma()) << "Battery: Measurement ID: " << QString("0x%1").arg(measurementId , 0, 16) << "Measurement number:" << QString("0x%1").arg(measurmentNumber, 0, 16); + qCDebug(dcSma()) << "Battery: Unknown value:" << QString("0x%1").arg(unknwonValue , 0, 16) << unknwonValue; + readUntilEndOfMeasurement(stream); + } + } } void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response) @@ -1015,6 +1125,7 @@ void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response // TODO: } + void SpeedwireInverter::readUntilEndOfMeasurement(QDataStream &stream) { // Read until end of line (0x01000000) @@ -1044,6 +1155,15 @@ void SpeedwireInverter::setReachable(bool reachable) emit reachableChanged(m_reachable); } +void SpeedwireInverter::setBatteryAvailable(bool available) +{ + if (m_batteryAvailable == available) + return; + + m_batteryAvailable = available; + emit batteryAvailableChanged(m_batteryAvailable); +} + void SpeedwireInverter::processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data) { // Note: the interface is already filtering out data from other hosts m_address @@ -1151,88 +1271,43 @@ void SpeedwireInverter::setState(State state) setReachable(false); break; case StateInitializing: { - - if (m_modelId == 372) { - // Home manager 2.0, no login, just fetch...testing - - // ############# TESTING ########## - - // Query battery info - qCDebug(dcSma()) << "Inverter: Request battery info..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491e00, 0x00495dff); // Battery infos - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + // Try to fetch ac power + qCDebug(dcSma()) << "Inverter: Request AC power..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; + + // TODO: try to send identify request and retry 3 times before giving up, + // still need to figure out why the inverter stops responding sometimes and how we can + // make it communicative again, a reconfugre always fixes this issue...somehow... + + setState(StateDisconnected); + return; } - qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); - processBatteryInfoResponse(reply->responsePayload()); - - - qCDebug(dcSma()) << "Inverter: Request battery SoC..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00295A00, 0x00295AFF); // SoC - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; - } else { - processBatteryInfoResponse(reply->responsePayload()); - } - qCDebug(dcSma()) << "Inverter: Request battery termperature..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491E00, 0x00495DFF); // SoC - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - // setState(StateDisconnected); - // return; - } else { - processBatteryInfoResponse(reply->responsePayload()); - } - }); - }); - }); - - } else { - // Try to fetch ac power - qCDebug(dcSma()) << "Inverter: Request AC power..."; - SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464000, 0x004642ff); - connect(reply, &SpeedwireInverterReply::finished, this, [=](){ - if (reply->error() != SpeedwireInverterReply::ErrorNoError) { - if (reply->error() == SpeedwireInverterReply::ErrorTimeout) { - qCWarning(dcSma()) << "Inverter: Failed to query data from inverter:" << reply->request().command() << reply->error(); - - // TODO: try to send identify request and retry 3 times before giving up, - // still need to figure out why the inverter stops responding sometimes and how we can - // make it communicative again, a reconfugre always fixes this issue...somehow... - - setState(StateDisconnected); - return; - } - - // Reachable, but received an inverter error, probably not logged - if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { - qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; - setState(StateLogin); - return; - } + // Reachable, but received an inverter error, probably not logged + if (reply->error() == SpeedwireInverterReply::ErrorInverterError) { + qCDebug(dcSma()) << "Inverter: Query data request finished with inverter error. Try to login..."; + setState(StateLogin); + return; } + } - // We where able to read data...emit the signal for the setup just incase - emit loginFinished(true); + // We where able to read data...emit the signal for the setup just incase + emit loginFinished(true); - qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); - processAcPowerResponse(reply->responseData()); + qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); + processAcPowerResponse(reply->responseData()); - if (m_deviceInformationFetched) { - setState(StateQueryData); - } else { - setState(StateGetInformation); - } - }); - } + + if (m_deviceInformationFetched) { + setState(StateQueryData); + } else { + setState(StateGetInformation); + } + }); break; } case StateLogin: { @@ -1296,6 +1371,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Get inverter status request finished successfully" << reply->request().command(); processInverterStatusResponse(reply->responsePayload()); + // Query AC voltage / current qCDebug(dcSma()) << "Inverter: Request AC voltage and current..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00464800, 0x004655ff); @@ -1309,6 +1385,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processAcVoltageCurrentResponse(reply->responsePayload()); + // Query DC power qCDebug(dcSma()) << "Inverter: Request DC power..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryDc, 0x00251e00, 0x00251eff); @@ -1322,6 +1399,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processDcPowerResponse(reply->responsePayload()); + // Query DC voltage/current qCDebug(dcSma()) << "Inverter: Request DC voltage and current..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryDc, 0x00451f00, 0x004521ff); @@ -1335,6 +1413,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processDcVoltageCurrentResponse(reply->responsePayload()); + // Query energy production qCDebug(dcSma()) << "Inverter: Request energy production..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryEnergy, 0x00260100, 0x002622ff); @@ -1348,6 +1427,7 @@ void SpeedwireInverter::setState(State state) qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command(); processEnergyProductionResponse(reply->responsePayload()); + // Query total AC power qCDebug(dcSma()) << "Inverter: Request total AC power..."; SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00263f00, 0x00263fff); @@ -1377,7 +1457,39 @@ void SpeedwireInverter::setState(State state) setReachable(true); emit valuesUpdated(); - setState(StateIdle); + + // ############# Optional ########## + + // Query battery info + qCDebug(dcSma()) << "Inverter: Request battery info..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00491e00, 0x00495dff); // Battery infos + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCDebug(dcSma()) << "Inverter: Failed to query battery info from inverter:" << reply->request().command() << reply->error(); + setBatteryAvailable(false); + setState(StateIdle); + } else { + qCDebug(dcSma()) << "Inverter: Process battery info response" << reply->responsePayload().toHex(); + processBatteryInfoResponse(reply->responsePayload()); + } + + qCDebug(dcSma()) << "Inverter: Request battery charge status..."; + SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryAc, 0x00295A00, 0x00295AFF); // Battery SoC + connect(reply, &SpeedwireInverterReply::finished, this, [=](){ + if (reply->error() != SpeedwireInverterReply::ErrorNoError) { + qCWarning(dcSma()) << "Inverter: Failed to query battery charge status from inverter:" << reply->request().command() << reply->error(); + setBatteryAvailable(false); + setState(StateIdle); + } else { + qCDebug(dcSma()) << "Inverter: Process battery charge status response" << reply->responsePayload().toHex(); + processBatteryChargeResponse(reply->responsePayload()); + } + + setBatteryAvailable(true); + emit batteryValuesUpdated(); + setState(StateIdle); + }); + }); }); }); }); diff --git a/sma/speedwire/speedwireinverter.h b/sma/speedwire/speedwireinverter.h index d749eb5..a91a6dd 100644 --- a/sma/speedwire/speedwireinverter.h +++ b/sma/speedwire/speedwireinverter.h @@ -94,6 +94,13 @@ public: double currentDcMpp1() const; double currentDcMpp2() const; + bool batteryAvailable() const; + double batteryCycles() const; + double batteryCharge() const; + double batteryTemperature() const; + double batteryCurrent() const; + double batteryVoltage() const; + // Query methods SpeedwireInverterReply *sendIdentifyRequest(); SpeedwireInverterReply *sendLoginRequest(const QString &password = "0000", bool loginAsUser = true); @@ -113,6 +120,8 @@ signals: void loginFinished(bool success); void stateChanged(State state); void valuesUpdated(); + void batteryAvailableChanged(bool available); + void batteryValuesUpdated(); private: SpeedwireInterface *m_interface = nullptr; @@ -164,6 +173,14 @@ private: double m_currentDcMpp1 = 0; double m_currentDcMpp2 = 0; + bool m_batteryAvailable = false; + + double m_batteryCycles = 0; + double m_batteryVoltage = 0; + double m_batteryCurrent = 0; + double m_batteryCharge = 0; + double m_batteryTemperature = 0; + void setState(State state); void sendNextReply(); @@ -187,12 +204,14 @@ private: void processEnergyProductionResponse(const QByteArray &response); void processGridFrequencyResponse(const QByteArray &response); void processBatteryInfoResponse(const QByteArray &response); + void processBatteryChargeResponse(const QByteArray &response); void processInverterStatusResponse(const QByteArray &response); void readUntilEndOfMeasurement(QDataStream &stream); double readValue(quint32 value, double divisor = 1.0); void setReachable(bool reachable); + void setBatteryAvailable(bool available); private slots: void processData(const QHostAddress &senderAddress, quint16 senderPort, const QByteArray &data); From 23d620c52d226ce4027b69a7d0bd5bcd613d5b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 15 May 2023 14:45:10 +0200 Subject: [PATCH 3/4] SMA: disable speedwire battery until verified --- sma/integrationpluginsma.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index e0f8050..9cc9419 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -455,8 +455,9 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) // First check if we already set up a battery for this inverter Things childThings = myThings().filterByParentId(thing->id()).filterByThingClassId(speedwireBatteryThingClassId); if (childThings.isEmpty()) { + // FIXME: re-enable autosetup once verified to be working as expected // Autocreate battery - emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(speedwireBatteryThingClassId, "SMA Battery", QString(), thing->id())); + // emit autoThingsAppeared(ThingDescriptors() << ThingDescriptor(speedwireBatteryThingClassId, "SMA Battery", QString(), thing->id())); } else { // We can only have one battery as a child Thing *batteryThing = childThings.first(); From c193e69b3ffd4a3e5d9185f9a07ca98472f6361b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 22 May 2023 17:04:59 +0200 Subject: [PATCH 4/4] SMA: fix crash on login failure of inverter --- sma/integrationpluginsma.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sma/integrationpluginsma.cpp b/sma/integrationpluginsma.cpp index 9cc9419..2b439e9 100644 --- a/sma/integrationpluginsma.cpp +++ b/sma/integrationpluginsma.cpp @@ -398,7 +398,7 @@ void IntegrationPluginSma::setupThing(ThingSetupInfo *info) pluginStorage()->remove(""); pluginStorage()->endGroup(); - delete inverter; + inverter->deleteLater(); info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Failed to log in with the given password. Please try again.")); return; }