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);