Merge PR #128: SMA: Improve speedwire protocol handling and add SMA battery

This commit is contained in:
jenkins 2023-05-22 17:15:12 +02:00
commit 7a866f8214
10 changed files with 636 additions and 146 deletions

View File

@ -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;
@ -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) {
@ -145,8 +151,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;
@ -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()) {
@ -302,7 +313,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<quint32>(thing->paramValue(speedwireMeterThingSerialNumberParamTypeId).toUInt());
quint16 modelId = static_cast<quint16>(thing->paramValue(speedwireMeterThingModelIdParamTypeId).toUInt());
@ -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());
@ -386,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;
}
@ -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,47 @@ 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()) {
// FIXME: re-enable autosetup once verified to be working as expected
// 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 +573,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) {
@ -541,6 +614,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();
}
@ -782,6 +860,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);
@ -796,6 +883,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<quint8>(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;

View File

@ -80,13 +80,18 @@ private:
QHash<Thing *, SpeedwireInverter *> m_speedwireInverters;
QHash<Thing *, SmaInverterModbusTcpConnection *> m_modbusInverters;
quint32 m_localSerialNumber = 0;
// Shared interface accross meters
SpeedwireInterface *m_multicastInterface = nullptr;
void markSpeedwireMeterAsDisconnected(Thing *thing);
void markSpeedwireInverterAsDisconnected(Thing *thing);
void markSpeedwireBatteryAsDisconnected(Thing *thing);
void markModbusInverterAsDisconnected(Thing *thing);
quint64 getLocalSerialNumber();
// Sma modbus data validation
bool isModbusValueValid(quint32 value);
bool isModbusValueValid(qint32 value);

View File

@ -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",

View File

@ -101,6 +101,7 @@ public:
//static QHash<quint16, QString> 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<quint16>(0x0004); // header length
stream << tag0();
stream << static_cast<quint32>(0x00000001); // group
stream << static_cast<quint16>(0x0026); // entry length
stream << smaNet2Version();
stream << static_cast<quint16>(ProtocolIdInverter);
// From now on little endian
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(0x09); // length
stream << static_cast<quint8>(0xa0); // control
stream << static_cast<quint16>(0xffff); // destination susyID
stream << static_cast<quint32>(0xffffffff); // destination serial
stream << static_cast<quint16>(0x00); // job id
stream << static_cast<quint16>(sourceSusyId); // source susyID
stream << static_cast<quint32>(sourceSerialNumber); // source susyID
stream << static_cast<quint16>(0x00); // job id
stream << static_cast<quint16>(0x00); // status
stream << static_cast<quint16>(0x00); // packet count
stream << static_cast<quint16>(0x8001); // packet id
stream << static_cast<quint8>(0x00); // command
stream << static_cast<quint8>(0x02); // param count
stream << static_cast<quint16>(0x00); // Object
stream << static_cast<quint32>(0x0); // Param 1
stream << static_cast<quint32>(0x0); // Param 2
stream << static_cast<quint32>(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>(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();
}

View File

@ -33,9 +33,10 @@
#include <QDataStream>
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();
@ -100,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;
}
@ -123,7 +114,7 @@ bool SpeedwireDiscovery::discoveryRunning() const
QList<SpeedwireDiscovery::SpeedwireDiscoveryResult> SpeedwireDiscovery::discoveryResult() const
{
return m_results.values();
return m_results;
}
bool SpeedwireDiscovery::setupMulticastSocket()
@ -137,18 +128,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<QNetworkAddressEntry> 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 +164,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 +181,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 +207,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 +218,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 +237,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 +259,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 +288,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 +308,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 +321,60 @@ 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 +383,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 +400,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->leaveMulticastGroup(Speedwire::multicastAddress())) {
qCWarning(dcSma()) << "SpeedwireDiscovery: Failed to leave multicast group" << Speedwire::multicastAddress().toString();
}
m_multicastSocket->close();
m_multicastSocket->deleteLater();
m_multicastSocket = nullptr;
@ -401,6 +421,7 @@ void SpeedwireDiscovery::finishDiscovery()
m_unicastSocket = nullptr;
}
foreach (const SpeedwireDiscoveryResult &result, m_results) {
qCDebug(dcSma()) << "SpeedwireDiscovery: ============================================";
qCDebug(dcSma()) << "SpeedwireDiscovery: Device type:" << result.deviceType;

View File

@ -38,6 +38,7 @@
#include <network/networkdevicediscovery.h>
#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<QHostAddress, SpeedwireDiscoveryResult> m_results;
QList<SpeedwireDiscoveryResult> m_results;
QHash<QHostAddress, SpeedwireDiscoveryResult> m_resultMeters;
QHash<QHostAddress, SpeedwireDiscoveryResult> m_resultInverters;
QHash<QHostAddress, SpeedwireInverter *> 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:

View File

@ -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();
}
}

View File

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

View File

@ -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);
}
@ -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
@ -153,7 +183,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 +201,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<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandLogin;
// The payload is little endian encoded
@ -227,7 +255,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<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandLogout;
// The payload is little endian encoded
@ -241,7 +269,7 @@ SpeedwireInverterReply *SpeedwireInverter::sendLogoutRequest()
stream << static_cast<quint16>(0x0300);
// Source
stream << m_interface->sourceModelId();
stream << Speedwire::sourceModelId();
stream << m_interface->sourceSerialNumber();
stream << static_cast<quint16>(0x0300);
@ -275,7 +303,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<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandQueryDevice;
// The payload is little endian encoded
@ -305,7 +333,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<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandQueryDevice;
// The payload is little endian encoded
@ -326,6 +354,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<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandQueryAc;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
// 2 words
stream << static_cast<quint32>(0x00491E00);
stream << static_cast<quint32>(0x00495DFF);
// End of data
stream << static_cast<quint32>(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 +489,7 @@ void SpeedwireInverter::buildPacket(QDataStream &stream, quint32 command, quint1
// Destination Ctrl
stream << static_cast<quint16>(0x0100);
// Source
stream << m_interface->sourceModelId() << m_interface->sourceSerialNumber();
stream << Speedwire::sourceModelId() << m_interface->sourceSerialNumber();
// Destination Ctrl
stream << static_cast<quint16>(0x0100);
@ -489,7 +547,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<quint16>(m_packetId++) | 0x8000;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
@ -514,20 +572,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 +996,126 @@ void SpeedwireInverter::processGridFrequencyResponse(const QByteArray &response)
}
}
void SpeedwireInverter::processBatteryInfoResponse(const QByteArray &response)
{
// 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
// 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();
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<quint8>(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<quint8>(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)
{
// 00000000 00000000
@ -947,6 +1125,7 @@ void SpeedwireInverter::processInverterStatusResponse(const QByteArray &response
// TODO:
}
void SpeedwireInverter::readUntilEndOfMeasurement(QDataStream &stream)
{
// Read until end of line (0x01000000)
@ -976,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
@ -1183,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);
@ -1196,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);
@ -1209,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);
@ -1222,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);
@ -1235,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);
@ -1248,6 +1441,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);
@ -1263,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);
});
});
});
});
});

View File

@ -94,12 +94,20 @@ 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);
SpeedwireInverterReply *sendLogoutRequest();
SpeedwireInverterReply *sendSoftwareVersionRequest();
SpeedwireInverterReply *sendDeviceTypeRequest();
SpeedwireInverterReply *sendBatteryInfoRequest();
// Start connecting
void startConnecting(const QString &password = "0000");
@ -112,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;
@ -124,7 +134,7 @@ private:
bool m_reachable = false;
State m_state = StateDisconnected;
quint16 m_packetId = 1;
quint8 m_packetId = 1;
bool m_deviceInformationFetched = false;
@ -163,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();
@ -185,12 +203,15 @@ private:
void processDcVoltageCurrentResponse(const QByteArray &response);
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);