/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2021, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef SPEEDWIRE_H #define SPEEDWIRE_H #include #include #include #include #include class Speedwire { Q_GADGET public: enum Command { CommandIdentify = 0x00000201, CommandQueryStatus = 0x51800200, CommandQueryAc = 0x51000200, CommandQueryDc = 0x53800200, CommandQueryEnergy = 0x54000200, CommandQueryDevice = 0x58000200, CommandQueryDeviceResponse = 58000201, CommandLogin = 0xfffd040c, CommandLogout = 0xfffd010e, CommandLoginResponse = 0x0ffdf40d }; Q_ENUM(Command) enum ProtocolId { ProtocolIdUnknown = 0x0000, ProtocolIdMeter = 0x6069, ProtocolIdInverter = 0x6065, ProtocolIdDiscoveryResponse = 0x0001, ProtocolIdDiscovery = 0xffff }; Q_ENUM(ProtocolId) enum DeviceClass { DeviceClassUnknown = 0x0000, DeviceClassAllDevices = 0x1f40, DeviceClassSolarInverter = 0x1f41, DeviceClassWindTurbine = 0x1f42, DeviceClassBatteryInverter = 0x1f47, DeviceClassConsumer = 0x1f61, DeviceClassSensorSystem = 0x1f80, DeviceClassElectricityMeter = 0x1f81, DeviceClassCommunicationProduct = 0x1fc0 }; Q_ENUM(DeviceClass) class Header { public: Header() = default; quint32 smaSignature = 0; quint16 headerLength = 0; quint16 tagType = 0; quint16 tagVersion = 0; quint16 group = 0; quint16 payloadLength = 0; quint16 smaNet2Version = 0; ProtocolId protocolId = ProtocolIdUnknown; inline bool isValid() const { return smaSignature == Speedwire::smaSignature() && protocolId != ProtocolIdUnknown; } }; typedef struct InverterPacket { quint8 wordCount = 0; quint8 control = 0; quint16 destinationModelId = 0; quint32 destinationSerialNumber = 0; quint16 destinationControl = 0; quint16 sourceModelId = 0; quint32 sourceSerialNumber = 0; quint16 sourceControl = 0; quint16 errorCode = 0; quint16 fragmentId = 0; quint16 packetId = 0; quint32 command = 0; } InverterPacket; Speedwire() = default; //static QHash deviceTypes = { {0x0000, "Unknwon"} }; static quint16 port() { return 9522; } static QHostAddress multicastAddress() { return QHostAddress("239.12.255.254"); } static quint32 smaSignature() { return 0x534d4100; } static quint16 tag0() { return 0x02a0; } static quint16 tagVersion() { return 0; } static quint16 smaNet2Version() { return 0x0010; } static QString getModelName(quint16 modelIdentifier) { switch (modelIdentifier) { case 9015: return "SB 700"; case 9016: return "SB 700U"; case 9017: return "SB 1100"; case 9018: return "SB 1100U"; case 9019: return "SB 1100LV"; case 9020: return "SB 1700"; case 9021: return "SB 1900TLJ"; case 9022: return "SB 2100TL"; case 9023: return "SB 2500"; case 9024: return "SB 2800"; case 9025: return "SB 2800i"; case 9026: return "SB 3000"; case 9027: return "SB 3000US"; case 9028: return "SB 3300"; case 9029: return "SB 3300U"; case 9030: return "SB 3300TL"; case 9031: return "SB 3300TL HC"; case 9032: return "SB 3800"; case 9033: return "SB 3800U"; case 9034: return "SB 4000US"; case 9035: return "SB 4200TL"; case 9036: return "SB 4200TL HC"; case 9037: return "SB 5000TL"; case 9038: return "SB 5000TLW"; case 9039: return "SB 5000TL HC"; case 9066: return "SB 1200"; case 9067: return "STP 10000TL-10"; case 9068: return "STP 12000TL-10"; case 9069: return "STP 15000TL-10"; case 9070: return "STP 17000TL-10"; case 9084: return "WB 3600TL-20"; case 9085: return "WB 5000TL-20"; case 9086: return "SB 3800US-10"; case 9098: return "STP 5000TL-20"; case 9099: return "STP 6000TL-20"; case 9100: return "STP 7000TL-20"; case 9101: return "STP 8000TL-10"; case 9102: return "STPcase 9000TL-20"; case 9103: return "STP 8000TL-20"; case 9104: return "SB 3000TL-JP-21"; case 9105: return "SB 3500TL-JP-21"; case 9106: return "SB 4000TL-JP-21"; case 9107: return "SB 4500TL-JP-21"; case 9108: return "SCSMC"; case 9109: return "SB 1600TL-10"; case 9131: return "STP 20000TL-10"; case 9139: return "STP 20000TLHE-10"; case 9140: return "STP 15000TLHE-10"; case 9157: return "Sunny Island 2012"; case 9158: return "Sunny Island 2224"; case 9159: return "Sunny Island 5048"; case 9160: return "SB 3600TL-20"; case 9168: return "SC630HE-11"; case 9169: return "SC500HE-11"; case 9170: return "SC400HE-11"; case 9171: return "WB 3000TL-21"; case 9172: return "WB 3600TL-21"; case 9173: return "WB 4000TL-21"; case 9174: return "WB 5000TL-21"; case 9175: return "SC 250"; case 9176: return "SMA Meteo Station"; case 9177: return "SB 240-10"; case 9179: return "Multigate-10"; case 9180: return "Multigate-US-10"; case 9181: return "STP 20000TLEE-10"; case 9182: return "STP 15000TLEE-10"; case 9183: return "SB 2000TLST-21"; case 9184: return "SB 2500TLST-21"; case 9185: return "SB 3000TLST-21"; case 9186: return "WB 2000TLST-21"; case 9187: return "WB 2500TLST-21"; case 9188: return "WB 3000TLST-21"; case 9189: return "WTP 5000TL-20"; case 9190: return "WTP 6000TL-20"; case 9191: return "WTP 7000TL-20"; case 9192: return "WTP 8000TL-20"; case 9193: return "WTPcase 9000TL-20"; case 9254: return "Sunny Island 3324"; case 9255: return "Sunny Island 4.0M"; case 9256: return "Sunny Island 4248"; case 9257: return "Sunny Island 4248U"; case 9258: return "Sunny Island 4500"; case 9259: return "Sunny Island 4548U"; case 9260: return "Sunny Island 5.4M"; case 9261: return "Sunny Island 5048U"; case 9262: return "Sunny Island 6048U"; case 9278: return "Sunny Island 3.0M"; case 9279: return "Sunny Island 4.4M"; case 9281: return "STP 10000TL-20"; case 9282: return "STP 11000TL-20"; case 9283: return "STP 12000TL-20"; case 9284: return "STP 20000TL-30"; case 9285: return "STP 25000TL-30"; case 9301: return "SB1.5-1VL-40"; case 9302: return "SB2.5-1VL-40"; case 9303: return "SB2.0-1VL-40"; case 9304: return "SB5.0-1SP-US-40"; case 9305: return "SB6.0-1SP-US-40"; case 9306: return "SB8.0-1SP-US-40"; case 9307: return "Energy Meter"; default: return "Unknown"; } }; // Multicast device discovery request packet, according to SMA documentation. // However, this does not seem to be supported anymore with version 3.x devices // 0x53, 0x4d, 0x41, 0x00, 0x00, 0x04, 0x02, 0xa0, // sma signature, tag0 // 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x20, // 0xffffffff group, 0x0000 length, 0x0020 "SMA Net ?", Version ? // 0x00, 0x00, 0x00, 0x00 // 0x0000 protocol, 0x00 #long words, 0x00 ctrl // Unicast device discovery request packet, according to SMA documentation // 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 Speedwire::Header parseHeader(QDataStream &stream) { stream.setByteOrder(QDataStream::BigEndian); Header header; quint16 protocolId; stream >> header.smaSignature >> header.headerLength; stream >> header.tagType >> header.tagVersion >> header.group; stream >> header.payloadLength >> header.smaNet2Version; stream >> protocolId; header.protocolId = static_cast(protocolId); return header; }; static Speedwire::InverterPacket parseInverterPacket(QDataStream &stream) { // Make sure the data stream is little endian stream.setByteOrder(QDataStream::LittleEndian); InverterPacket packet; stream >> packet.wordCount; stream >> packet.control; stream >> packet.destinationModelId; stream >> packet.destinationSerialNumber; stream >> packet.destinationControl; stream >> packet.sourceModelId; stream >> packet.sourceSerialNumber; stream >> packet.sourceControl; stream >> packet.errorCode; stream >> packet.fragmentId; stream >> packet.packetId; stream >> packet.command; return packet; }; }; inline QDebug operator<<(QDebug debug, const Speedwire::Header &header) { debug.nospace() << "SpeedwireHeader(" << header.protocolId << ", payload size: " << header.payloadLength << ", group: " << header.payloadLength << ")"; return debug.maybeSpace(); } inline QDebug operator<<(QDebug debug, const Speedwire::InverterPacket &packet) { debug.nospace() << "InverterPacket(" << packet.sourceSerialNumber; debug.nospace() << ", Model ID: " << packet.sourceModelId; debug.nospace() << ", command: " << packet.command; debug.nospace() << ", error: " << packet.errorCode; debug.nospace() << ", fragment: " << packet.fragmentId; debug.nospace() << ", packet ID: " << packet.fragmentId; debug.nospace() << ")"; return debug.maybeSpace(); } #endif // SPEEDWIRE_H