powersync-plugins/sma/speedwire.h

302 lines
12 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#include <QDebug>
#include <QDataStream>
#include <QHostAddress>
#include <QHash>
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<quint16, QString> 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>(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