nymea-plugins-modbus/sma/speedwire/speedwireinverter.cpp

1561 lines
66 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins-modbus.
*
* nymea-plugins-modbus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins-modbus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins-modbus. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "speedwireinverter.h"
#include "extern-plugininfo.h"
#include <QDateTime>
SpeedwireInverter::SpeedwireInverter(SpeedwireInterface *speedwireInterface, NetworkDeviceMonitor *monitor, quint16 modelId, quint32 serialNumber, QObject *parent)
: QObject{parent},
m_speedwireInterface{speedwireInterface},
m_monitor{monitor},
m_modelId{modelId},
m_serialNumber{serialNumber}
{
if (!m_monitor->networkDeviceInfo().address().isNull())
m_address = m_monitor->networkDeviceInfo().address();
qCDebug(dcSma()) << "Inverter: setup interface on" << m_monitor;
connect(m_monitor, &NetworkDeviceMonitor::reachableChanged, this, [this](bool reachable){
qCDebug(dcSma()) << "Inverter: reachable changed" << m_monitor;
if (reachable) {
m_address = m_monitor->networkDeviceInfo().address();
}
});
connect(m_speedwireInterface, &SpeedwireInterface::dataReceived, this, &SpeedwireInverter::processData);
}
SpeedwireInverter::SpeedwireInverter(SpeedwireInterface *speedwireInterface, const QHostAddress &address, quint16 modelId, quint32 serialNumber, QObject *parent)
: QObject{parent},
m_speedwireInterface{speedwireInterface},
m_address{address},
m_modelId{modelId},
m_serialNumber{serialNumber}
{
qCDebug(dcSma()) << "Inverter: setup interface on" << m_address.toString();
connect(m_speedwireInterface, &SpeedwireInterface::dataReceived, this, &SpeedwireInverter::processData);
}
NetworkDeviceMonitor *SpeedwireInverter::monitor() const
{
return m_monitor;
}
SpeedwireInverter::State SpeedwireInverter::state() const
{
return m_state;
}
bool SpeedwireInverter::reachable() const
{
return m_reachable;
}
QString SpeedwireInverter::modelName() const
{
return m_modelName;
}
double SpeedwireInverter::totalAcPower() const
{
return m_totalAcPower;
}
double SpeedwireInverter::gridFrequency() const
{
return m_gridFrequency;
}
double SpeedwireInverter::totalEnergyProduced() const
{
return m_totalEnergyProduced;
}
double SpeedwireInverter::todayEnergyProduced() const
{
return m_todayEnergyProduced;
}
double SpeedwireInverter::voltageAcPhase1() const
{
return m_voltageAcPhase1;
}
double SpeedwireInverter::voltageAcPhase2() const
{
return m_voltageAcPhase2;
}
double SpeedwireInverter::voltageAcPhase3() const
{
return m_voltageAcPhase3;
}
double SpeedwireInverter::currentAcPhase1() const
{
return m_currentAcPhase1;
}
double SpeedwireInverter::currentAcPhase2() const
{
return m_currentAcPhase2;
}
double SpeedwireInverter::currentAcPhase3() const
{
return m_currentAcPhase3;
}
double SpeedwireInverter::powerAcPhase1() const
{
return m_powerAcPhase1;
}
double SpeedwireInverter::powerAcPhase2() const
{
return m_powerAcPhase2;
}
double SpeedwireInverter::powerAcPhase3() const
{
return m_powerAcPhase3;
}
double SpeedwireInverter::powerDcMpp1() const
{
return m_powerDcMpp1;
}
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
// Response 534d4100000402a000000001004e00106065 13 90 7d00 52be283a 0000 b500 c2c12e12 0000 0000 0000 0180 01020000 00000000000000000003000000ff0000ecd5ff1f0100b500c2c12e1200000a000c00000000000000030000000101000000000000
qCDebug(dcSma()) << "Inverter: Sending identify request to" << m_address.toString();
SpeedwireInverterRequest request;
request.setPacketId(0x8001);
request.setCommand(Speedwire::CommandIdentify);
request.setRequestData(Speedwire::pingRequest(Speedwire::sourceModelId(), m_serialNumber));
return createReply(request);
}
SpeedwireInverterReply *SpeedwireInverter::sendLoginRequest(const QString &password, bool loginAsUser)
{
qCDebug(dcSma()) << "Inverter: Sending login request as" << (loginAsUser ? "user" : "installer") << "using password" << password;
// Request: 534d4100000402a000000001003a001060650ea0 b500 c2c12e12 0001 7d00 52be283a 0001 0000 0000 0180 0c04fdff0 7000000 84030000 3408b261 00000000 b8b8b8b8888888888888888800000000 // Login request
// Response: 534d4100000402a000000001003a001060650ed0 7d00 52be283a 0001 b500 c2c12e12 0001 0000000001800 d04fdff0 7000000 84030000 3408b261 00000000 b8b8b8b8888888888888888800000000 // Login OK
// Response: 534d4100000402a000000001003a001060650ed0 7d00 52be283a 0001 b500 c2c12e12 0001 0001000001800 d04fdff0 7000000 84030000 b709b261 00000000 b8b8b8b9888888888888888800000000 // Login FAILED, error 1
// Build the header
QByteArray datagram;
QDataStream stream(&datagram, QIODevice::WriteOnly);
buildDefaultHeader(stream, 58, 0xa0);
quint16 packetId = static_cast<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandLogin;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
// User type: 7 = user, a = installer
stream << (loginAsUser ? static_cast<quint32>(0x00000007) : static_cast<quint32>(0x0000000a));
// Timeout
stream << static_cast<quint32>(900); // 900 ms
// Current time
stream << static_cast<quint32>(QDateTime::currentMSecsSinceEpoch() / 1000.0);
// Zeros
stream << static_cast<quint32>(0);
// Encode password
QByteArray passwordData = password.toUtf8();
QByteArray encodedPassword(12, loginAsUser ? 0x88 : 0xBB);
for (int i = 0; i < password.length(); i++) {
encodedPassword[i] = (passwordData.at(i) + (loginAsUser ? 0x88 : 0xBB) % 0xff);
}
// Add encoded password
for (int i = 0; i < encodedPassword.length(); i++) {
stream << static_cast<quint8>(encodedPassword.at(i));
}
// End of data
stream << static_cast<quint32>(0);
// Final datagram
SpeedwireInverterRequest request;
request.setPacketId(packetId);
request.setCommand(command);
request.setRequestData(datagram);
return createReply(request);
}
SpeedwireInverterReply *SpeedwireInverter::sendLogoutRequest()
{
// Request 534d4100000402a000000001 0022 0010 6065 08a0 ffffffffffff0003 7d0052be283a0003 00000000 0280 0e01fdff ffffffff 00000000 => logoff command = 0xfffd01e0
// 534d4100000402a000000001 0022 0010 6065 08a0 ffffffffffff0003 7d0052be283a0003 00000000 0480 0e01fdff ffffffff 00000000
// Request 534d4100000402a000000001 0022 0010 6065 08a0 ffffffffffff0003 7d0040be283a0003 000000000380 e001fdff ffffffff 00000000
// Request 534d4100000402a000000001 0022 0010 6065 08a0 ffffffffffff0003 7d0042be283a0003 000000000180 e001fdff ffffffff 00000000
// 534d4100000402a000000001 0022 0010 6065 08a0 ffffffffffff0003 7d0052be283a0003 000000000080 0e01fdff ffffffff 00000000
// Build the header
QByteArray datagram;
QDataStream stream(&datagram, QIODevice::WriteOnly);
buildDefaultHeader(stream, 34);
// Reset the packet id counter, otherwise there will be no response
quint16 packetId = static_cast<quint16>(m_packetId++) | 0x8000;
Speedwire::Command command = Speedwire::CommandLogout;
// The payload is little endian encoded
stream.setByteOrder(QDataStream::LittleEndian);
// Target
// stream << static_cast<quint16>(m_modelId);
// stream << static_cast<quint32>(m_serialNumber);
stream << static_cast<quint16>(0xffff);
stream << static_cast<quint32>(0xffffffff);
stream << static_cast<quint16>(0x0300);
// Source
stream << Speedwire::sourceModelId();
stream << m_speedwireInterface->sourceSerialNumber();
stream << static_cast<quint16>(0x0300);
stream << static_cast<quint16>(0);
stream << static_cast<quint16>(0);
stream << packetId;
stream << command;
// Only first word
stream << static_cast<quint32>(0xffffffff);
stream << static_cast<quint32>(0);
SpeedwireInverterRequest request;
request.setPacketId(packetId);
request.setCommand(command);
request.setRequestData(datagram);
request.setRetries(0);
return createReply(request);
}
SpeedwireInverterReply *SpeedwireInverter::sendSoftwareVersionRequest()
{
qCDebug(dcSma()) << "Inverter: Sending software version request to" << m_address.toString();
// Request 534d4100000402a00000000100260010 6065 09a0 7a01 842a71b3 0001 7d00 42be283a 0001 000000000380 00020058 00348200 ff348200 00000000 => query software version
// Response 534d4100000402a000000001004e0010 6065 13a0 7d00 42be283a 00a1 7a01 842a71b3 0001 000000000380 01020058 0a000000 0a000000 01348200 2ae5e65f 00000000 00000000 feffffff feffffff 040a1003 040a1003 00000000 00000000 00000000 code = 0x00823401 3 (BCD).10 (BCD).10 (BIN) Typ R (Enum)
// 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::CommandQueryDevice;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
// First and last word
stream << static_cast<quint32>(0x00823400);
stream << static_cast<quint32>(0x008234ff);
// End of data
stream << static_cast<quint32>(0);
// Final datagram
SpeedwireInverterRequest request;
request.setPacketId(packetId);
request.setCommand(command);
request.setRequestData(datagram);
return createReply(request);
}
SpeedwireInverterReply *SpeedwireInverter::sendDeviceTypeRequest()
{
qCDebug(dcSma()) << "Inverter: Sending software version 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::CommandQueryDevice;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
// 2 words
stream << static_cast<quint32>(0x00821e00);
stream << static_cast<quint32>(0x008220ff);
// End of data
stream << static_cast<quint32>(0);
// Final datagram
SpeedwireInverterRequest request;
request.setPacketId(packetId);
request.setCommand(command);
request.setRequestData(datagram);
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;
refresh();
}
void SpeedwireInverter::refresh()
{
// Only refresh if not already busy...
if (m_state != StateIdle && m_state != StateDisconnected)
return;
// Run the state machine
setState(StateInitializing);
}
void SpeedwireInverter::sendNextReply()
{
// Pending reply
if (m_currentReply)
return;
// No reply left
if (m_replyQueue.isEmpty())
return;
// Pick the next reply and send request
m_currentReply = m_replyQueue.dequeue();
qCDebug(dcSma()) << "Inverter: --> Sending" << m_currentReply->request().command() << "packet ID:" << m_currentReply->request().packetId();
m_speedwireInterface->sendDataUnicast(m_address, m_currentReply->request().requestData());
m_currentReply->startWaiting();
}
SpeedwireInverterReply *SpeedwireInverter::createReply(const SpeedwireInverterRequest &request)
{
SpeedwireInverterReply *reply = new SpeedwireInverterReply(request, this);
connect(reply, &SpeedwireInverterReply::timeout, this, &SpeedwireInverter::onReplyTimeout);
connect(reply, &SpeedwireInverterReply::finished, this, &SpeedwireInverter::onReplyFinished);
// Make sure the reply gets deleted once finished
connect(reply, &SpeedwireInverterReply::finished, reply, &SpeedwireInverterReply::deleteLater);
// Schedule request
m_replyQueue.enqueue(reply);
sendNextReply();
return reply;
}
void SpeedwireInverter::buildDefaultHeader(QDataStream &stream, quint16 payloadSize, quint8 control)
{
// Header (big endian)
// 534d4100000402a00000000100260010606509a0
// 534d4100 : SMA\0 signature
// 0004 : header length
// 02a0 : Tag0 type
// 0000 : Tag version
// 0001 : Group
// 0026 : payload length
// 0010 : SMA Net 2 version
// 6065 : inverter protocol id
// 09 : length of long words = (payload length - 2) / 4
// a0 : control ?
stream.setByteOrder(QDataStream::BigEndian);
stream << Speedwire::smaSignature();
stream << static_cast<quint16>(4); // Header length
stream << Speedwire::tag0();
stream << Speedwire::tagVersion();
stream << static_cast<quint16>(1); // Group 1 = default
stream << payloadSize;
stream << Speedwire::smaNet2Version();
stream << static_cast<quint16>(Speedwire::ProtocolIdInverter);
stream << static_cast<quint8>((payloadSize - 2) / 4); // wordCount
stream << control;
}
void SpeedwireInverter::buildPacket(QDataStream &stream, quint32 command, quint16 packetId)
{
// ========= packet header (little endian)
// == 7a01842a71b30001
// 7a01 : destination model id
// 842a71b3 : destination serial number
// 0001 : destination control field
// == 7d0042be283a0001
// 7d00 : source model id
// 42be283a: source serial number
// 0001 : source control field
// == 0000 0000 0380 00020058
// 0000 : error code
// 0000 : fragment id
// 0380 : packet id
// 00020058 : command id = CommandQueryDevice
stream.setByteOrder(QDataStream::LittleEndian);
// Destination
stream << m_modelId << m_serialNumber;
// Destination Ctrl
stream << static_cast<quint16>(0x0100);
// Source
stream << Speedwire::sourceModelId() << m_speedwireInterface->sourceSerialNumber();
// Destination Ctrl
stream << static_cast<quint16>(0x0100);
// Packet information
quint16 errorCode = 0;
quint16 fragmentId = 0;
stream << errorCode << fragmentId << packetId;
// Command
stream << static_cast<quint32>(command);
}
SpeedwireInverterReply *SpeedwireInverter::sendQueryRequest(Speedwire::Command command, quint32 firstWord, quint32 secondWord)
{
qCDebug(dcSma()) << "Inverter: Sending query request to" << m_address.toString();
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000380 00020058 00348200 ff348200 00000000 => query software version
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000380 01020058 0a000000 0a000000 01348200 2ae5e65f 00000000 00000000 feffffff feffffff 040a1003 040a1003 00000000 00000000 00000000 code = 0x00823401 3 (BCD).10 (BCD).10 (BIN) Typ R (Enum)
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00020058 001e8200 ff208200 00000000 => query device type
// Response 534d4100000402a000000001009e0010 606527a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01020058 01000000 03000000 011e8210 6f89e95f 534e3a20 33303130 35333831 31360000 00000000 00000000 00000000 00000000
// 011f8208 6f89e95f 411f0001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 => 1f41 solar inverter
// 01208208 6f89e95f 96240000 80240000 81240001 82240000 feffff00 00000000 00000000 00000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000480 00028053 001e2500 ff1e2500 00000000 => query spot dc power
// Response 534d4100000402a000000001005e0010 606517a0 7d0042be283a00a1 7a01842a71b30001 000000000480 01028053 00000000 01000000 011e2540 61a7e95f 57000000 57000000 57000000 57000000 01000000
// 021e2540 61a7e95f 5e000000 5e000000 5e000000 5e000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000580 00028053 001f4500 ff214500 00000000 => query spot dc voltage/current
// Response 534d4100000402a00000000100960010 606525a0 7d0042be283a00a1 7a01842a71b30001 000000000580 01028053 02000000 05000000 011f4540 61a7e95f 05610000 05610000 05610000 05610000 01000000
// 021f4540 61a7e95f 505b0000 505b0000 505b0000 505b0000 01000000
// 01214540 61a7e95f 60010000 60010000 60010000 60010000 01000000
// 02214540 61a7e95f 95010000 95010000 95010000 95010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000680 00020051 00404600 ff424600 00000000 => query spot ac power
// Response 534d4100000402a000000001007a0010 60651ea0 7d0042be283a00a1 7a01842a71b30001 000000000680 01020051 09000000 0b000000 01404640 61a7e95f 38000000 38000000 38000000 38000000 01000000
// 01414640 61a7e95f 37000000 37000000 37000000 37000000 01000000
// 01424640 61a7e95f 39000000 39000000 39000000 39000000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000780 00020051 00484600 ff554600 00000000 => query spot ac voltage/current
// Response 534d4100000402a000000001013e0010 60654fa0 7d0042be283a00a1 7a01842a71b30001 000000000780 01020051 0c000000 15000000 01484600 61a7e95f 5a590000 5a590000 5a590000 5a590000 01000000
// 01494600 61a7e95f cf590000 cf590000 cf590000 cf590000 01000000
// 014a4600 61a7e95f 7a590000 7a590000 7a590000 7a590000 01000000
// 014b4600 61a7e95f f19a0000 f19a0000 f19a0000 f19a0000 01000000
// 014c4600 61a7e95f 3c9b0000 3c9b0000 3c9b0000 3c9b0000 01000000
// 014d4600 61a7e95f 189b0000 189b0000 189b0000 189b0000 01000000
// 014e4600 51a7e95f 1d000000 1d000000 1d000000 1d000000 01000000
// 01534640 61a7e95f 24010000 24010000 24010000 24010000 01000000
// 01544640 61a7e95f 1e010000 1e010000 1e010000 1e010000 01000000
// 01554640 61a7e95f 23010000 23010000 23010000 23010000 01000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000980 00028051 00482100 ff482100 00000000 => query device status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000980 01028051 00000000 00000000 01482108 59c5e95f 33010001 feffff00 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// Request 534d4100000402a00000000100260010 606509a0 7a01842a71b30001 7d0042be283a0001 000000000a80 00028051 00644100 ff644100 00000000 => query grid relay status
// Response 534d4100000402a000000001004e0010 606513a0 7d0042be283a00a1 7a01842a71b30001 000000000a80 01028051 07000000 07000000 01644108 59c5e95f 33000001 37010000 fdffff00 feffff00 00000000 00000000 00000000 00000000 00000000
// Build the header
QByteArray datagram;
QDataStream stream(&datagram, QIODevice::WriteOnly);
buildDefaultHeader(stream);
// Reset the packet id counter, otherwise there will be no response
quint16 packetId = static_cast<quint16>(m_packetId++) | 0x8000;
// The payload is little endian encoded
buildPacket(stream, command, packetId);
// First and second word
stream << firstWord;
stream << secondWord;
// 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::processSoftwareVersionResponse(const QByteArray &response)
{
// 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);
// // 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;
}
void SpeedwireInverter::processDeviceTypeResponse(const QByteArray &response)
{
// Request 534d4100000402a00000000100260010 606509a0 b500 c2c12e12 0001 7d00 43be283a 0001 0000 0000 0280 0002 0058 001e8200 ff208200 00000000
// Response 534d4100000402a00000000100c60010 60653190 7d00 43be283a 00a1 b500 c2c12e12 0001 0000 0000 0280 0102 0058 00000000 03000000
// 011e8210 85f2b661 534e3a20333035303534313436000000 feffff00 00000000 00000000 00000000 // SN: 305054146
// 011f8208 85f2b661 411f 0001 421f 0000 feff ff00 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 // 411f -> 1f41 -> device type soar inverter
// 01208208 85f2b661 8a23 0000 8b23 0000 8c23 0001 8e23 0000 8f23 0000 e523 0000 e623 0000 e723 0000 // 238c -> 0001 -> STP 7000TL-20
// 01208208 85f2b661 e923 0000 e823 0000 4124 0000 4224 0000 4324 0000 feff ff00 0000 0000 0000 0000
// 00000000
// Response data: 00000000 03000000 -> first, last, then data
// 011e8210 3799b961 534e3a20 33303530 35343134 36000000 feffff00 00000000 00000000 00000000 // serialnumber as text
// 011f8208 3799b961 411f 0001 421f 0000 feff ff00 0000000000000000000000000000000000000000 // device class
// 01208208 3799b961 8a23 0000 8b23 0000 8c23 0001 8e23 0000 8f23 0000 e523 0000 e623 0000 e723 0000 // device model
// 01208208 3799b961 e923 0000 e823 0000 4124 0000 4224 0000 4324 0000 feff ff00 0000000000000000 // device model
// 00000000 // End of data
qCDebug(dcSma()) << "Inverter: Process device type response" << response.toHex();
// TODO:
}
void SpeedwireInverter::processAcPowerResponse(const QByteArray &response)
{
// Request 534d4100000402a00000000100260010606509a0 b500 c2c12e12 0001 7d00 52be283a 0001 0000 0000 0180 00020051 00404600 ff424600 00000000
// Response 534d4100000402a0000000010026001060650990 7d00 52be283a 00e1 b500 c2c12e12 0001 ffff 0000 0180 01020051 00404600 ff424600 00000000 // Error: login required
// Response 534d4100000402a000000001007a001060651e90 7d00 52be283a 00a1 b500 c2c12e12 0001 0000 0000 0580 01020051
// No sun
// 07000000 09000000
// 01 4046 40 7503ba61 00000080 00000080 00000080 00000080 01000000
// 01 4146 40 7503ba61 00000080 00000080 00000080 00000080 01000000
// 01 4246 40 7503ba61 00000080 00000080 00000080 00000080 01000000 00000000
// Sun
// 07000000 09000000
// 01 4046 40 77fbba61 23000000 23000000 23000000 23000000 01000000
// 01 4146 40 77fbba61 23000000 23000000 23000000 23000000 01000000
// 01 4246 40 77fbba61 23000000 23000000 23000000 23000000 01000000
// 00000000
// 0a000000 0c000000
// 09 4046 40 ec767864 be050000 be050000 be050000 be050000 01000000
// 09 4146 40 ec767864 c6050000 c6050000 c6050000 c6050000 01000000
// 09 4246 40 ec767864 c8050000 c8050000 c8050000 c8050000 01000000
// 00000000
// Note: sometimes the inverter sends negative power values, we filter those away since it does not make sense.
// D | Sma: Inverter: Process AC power query response "0a0000000c000000094046406740b165a7feffffa7feffffa7feffffa7feffff01000000094146406740b165a7feffffa7feffffa7feffffa7feffff01000000094246406740b165a6feffffa6feffffa6feffffa6feffff0100000000000000"
// D | Sma: Inverter: Power AC phase 1 4.29497e+06 W
// D | Sma: Inverter: Power AC phase 2 4.29497e+06 W
// D | Sma: Inverter: Power AC phase 3 4.29497e+06 W
// 0a000000 0c000000
// 09 4046 40 6740b165 a7feffff a7feffff a7feffff a7feffff 01000000
// 09 4146 40 6740b165 a7feffff a7feffff a7feffff a7feffff 01000000
// 09 4246 40 6740b165 a6feffff a6feffff a6feffff a6feffff 01000000
// 00000000
// 40464001
qCDebug(dcSma()) << "Inverter: Process AC power query 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;
measurementId = measurementId & 0x00ffff00;
// Read measurent lines
if (measurementId == 0x464000) {
qint32 powerAcPhase1;
stream >> powerAcPhase1;
if (powerAcPhase1 >= 0) {
m_powerAcPhase1 = readValue(powerAcPhase1, 1000.0);
qCDebug(dcSma()) << "Inverter: Power AC phase 1" << m_powerAcPhase1 << "W";
} else {
qCDebug(dcSma()) << "Inverter: Power AC phase 1 is negative, ignoring value" << powerAcPhase1 << "W";
}
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x464100) {
qint32 powerAcPhase2;
stream >> powerAcPhase2;
if (powerAcPhase2 >= 0) {
m_powerAcPhase2 = readValue(powerAcPhase2, 1000.0);
qCDebug(dcSma()) << "Inverter: Power AC phase 2" << m_powerAcPhase2 << "W";
} else {
qCDebug(dcSma()) << "Inverter: Power AC phase 2 is negative, ignoring value" << powerAcPhase2 << "W";
}
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x464200) {
qint32 powerAcPhase3;
stream >> powerAcPhase3;
if (powerAcPhase3 >= 0) {
m_powerAcPhase3 = readValue(powerAcPhase3, 1000.0);
qCDebug(dcSma()) << "Inverter: Power AC phase 3" << m_powerAcPhase3 << "W";
} else {
qCDebug(dcSma()) << "Inverter: Power AC phase 3 is negative, ignoring value" << powerAcPhase3 << "W";
}
readUntilEndOfMeasurement(stream);
}
}
}
void SpeedwireInverter::processAcVoltageCurrentResponse(const QByteArray &response)
{
// No sun
// 0a000000 0f000000
// 01 4846 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 01 4946 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 01 4a46 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 01 5046 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 01 5146 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 01 5246 00 7503ba61 ffffffff ffffffff ffffffff ffffffff 01000000
// 00000000
// Sun
// 0a000000 0f000000
// 01484600 c1f0ba61 f9580000 f9580000 f9580000 f9580000 01000000
// 01494600 c1f0ba61 ff580000 ff580000 ff580000 ff580000 01000000
// 014a4600 c1f0ba61 02590000 02590000 02590000 02590000 01000000
// 01504600 c1f0ba61 00000000 00000000 00000000 00000000 01000000
// 01514600 c1f0ba61 00000000 00000000 00000000 00000000 01000000
// 01524600 c1f0ba61 00000000 00000000 00000000 00000000 01000000
// 00000000
qCDebug(dcSma()) << "Inverter: Process AC voltage/current query 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;
measurementId = measurementId & 0x00ffff00;
// Read measurent lines
if (measurementId == 0x464800) {
quint32 voltageAcPhase1;
stream >> voltageAcPhase1;
m_voltageAcPhase1 = readValue(voltageAcPhase1, 100.0);
qCDebug(dcSma()) << "Inverter: Voltage AC phase 1" << m_voltageAcPhase1 << "V";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x464900) {
quint32 voltageAcPhase2;
stream >> voltageAcPhase2;
m_voltageAcPhase2 = readValue(voltageAcPhase2, 100.0);
qCDebug(dcSma()) << "Inverter: Voltage AC phase 2" << m_voltageAcPhase2 << "V";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x464a00) {
quint32 voltageAcPhase3;
stream >> voltageAcPhase3;
m_voltageAcPhase3 = readValue(voltageAcPhase3, 100.0);
qCDebug(dcSma()) << "Inverter: Voltage AC phase 3" << m_voltageAcPhase3 << "V";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x465000) {
quint32 currentAcPhase1;
stream >> currentAcPhase1;
m_currentAcPhase1 = readValue(currentAcPhase1, 1000.0);
qCDebug(dcSma()) << "Inverter: Current AC phase 1" << m_currentAcPhase1 << "A";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x465100) {
quint32 currentAcPhase2;
stream >> currentAcPhase2;
m_currentAcPhase2 = readValue(currentAcPhase2, 1000.0);
qCDebug(dcSma()) << "Inverter: Current AC phase 2" << m_currentAcPhase2 << "A";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x465200) {
quint32 currentAcPhase3;
stream >> currentAcPhase3;
m_currentAcPhase3 = readValue(currentAcPhase3, 1000.0);
qCDebug(dcSma()) << "Inverter: Current AC phase 3" << m_currentAcPhase3 << "A";
readUntilEndOfMeasurement(stream);
}
}
}
void SpeedwireInverter::processAcTotalPowerResponse(const QByteArray &response)
{
// 00000000 00000000
// 013f2640 8606bb61 16010000 16010000 16010000 16010000 01000000
// 00000000
qCDebug(dcSma()) << "Inverter: Process AC total power query 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;
measurementId = measurementId & 0x00ffff00;
// Read measurent lines
if (measurementId == 0x263f00) {
qint32 totalAcPower;
stream >> totalAcPower;
if (totalAcPower >= 0) {
m_totalAcPower = readValue(totalAcPower);
qCDebug(dcSma()) << "Inverter: Total AC power" << m_totalAcPower << "W";
} else {
qCDebug(dcSma()) << "Inverter: Total AC power is negative, ignoring value" << totalAcPower << "W";
}
readUntilEndOfMeasurement(stream);
}
}
}
void SpeedwireInverter::processDcPowerResponse(const QByteArray &response)
{
// No sun
// 00000000 01000000
// 011e2540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 021e2540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 00000000
// Sun
// 00000000 01000000
// 011e2540 7b0dbb61 8a000000 8a000000 8a000000 8a000000 01000000
// 021e2540 7b0dbb61 8f000000 8f000000 8f000000 8f000000 01000000
// 00000000
qCDebug(dcSma()) << "Inverter: Process DC power query 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 == 0x251e00 && measurmentNumber == 0x01) {
quint32 powerMpp1;
stream >> powerMpp1;
m_powerDcMpp1 = readValue(powerMpp1);
qCDebug(dcSma()) << "Inverter: DC power MPP1" << m_powerDcMpp1 << "W";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x251e00 && measurmentNumber == 0x02) {
quint32 powerMpp2;
stream >> powerMpp2;
m_powerDcMpp2 = readValue(powerMpp2);
qCDebug(dcSma()) << "Inverter: DC power MPP2" << m_powerDcMpp2 << "W";
readUntilEndOfMeasurement(stream);
}
}
}
void SpeedwireInverter::processDcVoltageCurrentResponse(const QByteArray &response)
{
// 02000000 05000000
// 011f4540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 021f4540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 01214540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 02214540 7503ba61 00000080 00000080 00000080 00000080 01000000
// 00000000
// 02000000 05000000
// 011f4540 160ebb61 009a0000 009a0000 009a0000 009a0000 01000000
// 021f4540 160ebb61 02a00000 02a00000 02a00000 02a00000 01000000
// 01214540 160ebb61 76010000 76010000 76010000 76010000 01000000
// 02214540 160ebb61 77010000 77010000 77010000 77010000 01000000
// 00000000
qCDebug(dcSma()) << "Inverter: Process DC voltage/current 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 == 0x451f00 && measurmentNumber == 0x01) {
quint32 voltageMpp1;
stream >> voltageMpp1;
m_voltageDcMpp1 = readValue(voltageMpp1, 100.0);
qCDebug(dcSma()) << "Inverter: DC voltage MPP1" << m_voltageDcMpp1 << "V";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x451e00 && measurmentNumber == 0x02) {
quint32 voltageMpp2;
stream >> voltageMpp2;
m_voltageDcMpp2 = readValue(voltageMpp2, 100.0);
qCDebug(dcSma()) << "Inverter: DC voltage MPP2" << m_voltageDcMpp2 << "V";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x452100 && measurmentNumber == 0x01) {
quint32 currentMpp1;
stream >> currentMpp1;
m_currentDcMpp1 = readValue(currentMpp1, 1000.0);
qCDebug(dcSma()) << "Inverter: DC current MPP1" << m_currentDcMpp1 << "A";
readUntilEndOfMeasurement(stream);
} else if (measurementId == 0x452100 && measurmentNumber == 0x02) {
quint32 currentMpp2;
stream >> currentMpp2;
m_currentDcMpp2 = readValue(currentMpp2, 1000.0);
qCDebug(dcSma()) << "Inverter: DC current MPP2" << m_currentDcMpp2 << "A";
readUntilEndOfMeasurement(stream);
}
}
}
void SpeedwireInverter::processEnergyProductionResponse(const QByteArray &response)
{
// 00000000 01000000
// 01012600 6f03ba61 4d147402 0000000 00122260 01523ba6 17c05000 00000000
// 000000000
qCDebug(dcSma()) << "Inverter: Process energy production response";// << response.toHex();
QDataStream stream(response);
stream.setByteOrder(QDataStream::LittleEndian);
quint32 firstWord, secondWord;
stream >> firstWord >> secondWord;
quint32 dataId, dataType;
stream >> dataId >> dataType;
quint32 unknwonWord;
quint32 totalEnergy, todayEnergy;
stream >> totalEnergy >> unknwonWord >> unknwonWord >> unknwonWord >> todayEnergy;
m_todayEnergyProduced = readValue(todayEnergy, 1000.0);
m_totalEnergyProduced = readValue(totalEnergy, 1000.0);
qCDebug(dcSma()) << "Inverter: Energy total:" << m_totalEnergyProduced << "kWh";
qCDebug(dcSma()) << "Inverter: Energy today:" << m_todayEnergyProduced << "kWh";
}
void SpeedwireInverter::processGridFrequencyResponse(const QByteArray &response)
{
// 10000000 10000000
// 01574600 c20cbb61 89130000 89130000 89130000 89130000 010000000
// 0000000
qCDebug(dcSma()) << "Inverter: Process grid frequency 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;
measurementId = measurementId & 0x00ffff00;
// Read measurent lines
if (measurementId == 0x465700) {
quint32 frequency;
stream >> frequency;
m_gridFrequency = readValue(frequency, 100.0);
qCDebug(dcSma()) << "Inverter: Grid frequency" << m_gridFrequency << "Hz";
readUntilEndOfMeasurement(stream);
}
}
}
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" << batteryCurrent << 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
// 01482108 b527bb61 23000000 2f010000 33010001 c7010000 feffff00 00000000 00000000 00000000
// 00000000
qCDebug(dcSma()) << "Inverter: Process inverter status response" << response.toHex();
// TODO:
}
void SpeedwireInverter::readUntilEndOfMeasurement(QDataStream &stream)
{
// Read until end of line (0x01000000)
quint32 word;
while (!stream.atEnd()) {
stream >> word;
if (word == 1) {
return;
}
}
}
double SpeedwireInverter::readValue(quint32 value, double divisor)
{
// Note: the first 2 are verifies as invalid values, the last one has been seen on some system in the field,
// where the inverter has sent garbage data, the only consistancy was the ffff xxxx.
if (value == 0x80000000 || value == 0xffffffff || ((value & 0xffff0000) == 0xffff0000))
return 0;
return value / divisor;
}
double SpeedwireInverter::readValue(qint32 value, double divisor)
{
if (static_cast<quint32>(value) == 0x80000000 || static_cast<quint32>(value) == 0xffffffff)
return 0;
return value / divisor;
}
void SpeedwireInverter::setReachable(bool reachable)
{
if (m_reachable == reachable)
return;
m_reachable = 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)
{
Q_UNUSED(senderPort)
// Process only data coming from our target address if there is any
if (!m_address.isNull() && senderAddress != m_address)
return;
if (data.size() < 18) {
qCDebug(dcSma()) << "Inverter: The received datagram is to short to be a SMA speedwire message. Ignoring data...";
return;
}
QDataStream stream(data);
Speedwire::Header header = Speedwire::parseHeader(stream);
if (!header.isValid()) {
qCWarning(dcSma()) << "Inverter: Datagram header is not valid. Ignoring data...";
return;
}
if (header.protocolId != Speedwire::ProtocolIdInverter) {
//qCWarning(dcSma()) << "Inverter: Received datagram from different protocol" << header.protocolId << "Ignoring data...";
return;
}
Speedwire::InverterPacket packet = Speedwire::parseInverterPacket(stream);
if (packet.sourceModelId != m_modelId || packet.sourceSerialNumber != m_serialNumber) {
qCWarning(dcSma()) << "Inverter: Received datagram from different inverter" << packet.sourceSerialNumber << "Ignoring data...";
return;
}
qCDebug(dcSma()) << "Inverter: <-- Received" << static_cast<Speedwire::Command>(packet.command) << "Packet ID:" << packet.packetId;
//qCDebug(dcSma()) << "Inverter:" << data.toHex();
if (m_currentReply && m_currentReply->request().packetId() == packet.packetId) {
qCDebug(dcSma()) << "Inverter: Received response for current reply" << static_cast<Speedwire::Command>(m_currentReply->request().command()) << "Packet ID:" << m_currentReply->request().packetId();
m_currentReply->m_responseData = data;
m_currentReply->m_responseHeader = header;
m_currentReply->m_responsePacket = packet;
// Set the payload
while (!stream.atEnd()) {
quint8 byte;
stream >> byte;
m_currentReply->m_responsePayload.append(byte);
}
if (packet.errorCode != 0) {
m_currentReply->finishReply(SpeedwireInverterReply::ErrorInverterError);
} else {
m_currentReply->finishReply(SpeedwireInverterReply::ErrorNoError);
}
} else {
if (m_currentReply) {
qCWarning(dcSma()) << "Inverter: Received unexpected data: waiting for" << static_cast<Speedwire::Command>(m_currentReply->request().command()) << "Packet ID:" << m_currentReply->request().packetId();
} else {
qCWarning(dcSma()) << "Inverter: Received unexpected data: not waiting for any response.";
}
qCWarning(dcSma()) << "Inverter:" << header;
qCWarning(dcSma()) << "Inverter:" << packet;
qCWarning(dcSma()) << "Inverter:" << data.toHex();
}
}
void SpeedwireInverter::onReplyTimeout()
{
SpeedwireInverterReply *reply = qobject_cast<SpeedwireInverterReply *>(sender());
qCDebug(dcSma()) << "Inverter: Reply timeout" << reply->request().packetId() << reply->request().command();
reply->m_retries += 1;
if (reply->m_retries <= reply->m_maxRetries) {
qCDebug(dcSma()) << "Inverter: Resend request" << reply->m_retries << "/" << reply->m_maxRetries;
m_replyQueue.prepend(reply);
m_currentReply = nullptr;
sendNextReply();
} else {
if (reply->m_maxRetries == 0) {
qCWarning(dcSma()) << "Inverter: No response received for request. Finish reply with" << SpeedwireInverterReply::ErrorTimeout;
} else {
qCWarning(dcSma()) << "Inverter: No response received for request after" << reply->m_maxRetries << "attempts. Finish reply with" << SpeedwireInverterReply::ErrorTimeout;
}
// Finish with timeout error
reply->finishReply(SpeedwireInverterReply::ErrorTimeout);
}
}
void SpeedwireInverter::onReplyFinished()
{
SpeedwireInverterReply *reply = qobject_cast<SpeedwireInverterReply *>(sender());
if (m_currentReply == reply) {
// Note: the reply is self deleting on finished
m_currentReply = nullptr;
sendNextReply();
}
}
void SpeedwireInverter::setState(State state)
{
if (m_state == state)
return;
qCDebug(dcSma()) << "Inverter: State changed" << state;
m_state = state;
emit stateChanged(m_state);
switch (m_state) {
case StateIdle:
break;
case StateDisconnected:
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) {
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);
qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command();
processAcPowerResponse(reply->responsePayload());
if (m_deviceInformationFetched) {
setState(StateQueryData);
} else {
setState(StateGetInformation);
}
});
break;
}
case StateLogin: {
SpeedwireInverterReply *loginReply = sendLoginRequest(m_password);
connect(loginReply, &SpeedwireInverterReply::finished, this, [=](){
if (loginReply->error() != SpeedwireInverterReply::ErrorNoError) {
qCWarning(dcSma()) << "Inverter: Failed to login to inverter:" << loginReply->error();
emit loginFinished(false);
setState(StateDisconnected);
return;
}
qCDebug(dcSma()) << "Inverter: Login request finished successfully.";
emit loginFinished(true);
setReachable(true);
// Logged in successfully, reinit the data fetch process
setState(StateInitializing);
});
break;
}
case StateGetInformation: {
SpeedwireInverterReply *softwareVersionReply = sendSoftwareVersionRequest();
connect(softwareVersionReply, &SpeedwireInverterReply::finished, this, [=](){
if (softwareVersionReply->error() != SpeedwireInverterReply::ErrorNoError) {
qCWarning(dcSma()) << "Inverter: Failed to get software version from inverter:" << softwareVersionReply->error();
setState(StateDisconnected);
return;
}
qCDebug(dcSma()) << "Inverter: Software version request finished successfully.";
processSoftwareVersionResponse(softwareVersionReply->responsePayload());
SpeedwireInverterReply *deviceTypeReply = sendDeviceTypeRequest();
connect(deviceTypeReply, &SpeedwireInverterReply::finished, this, [=](){
if (deviceTypeReply->error() != SpeedwireInverterReply::ErrorNoError) {
qCWarning(dcSma()) << "Inverter: Failed to get device information from inverter:" << deviceTypeReply->error();
setState(StateDisconnected);
return;
}
qCDebug(dcSma()) << "Inverter: Get device information finished successfully.";
processDeviceTypeResponse(deviceTypeReply->responsePayload());
m_deviceInformationFetched = true;
setState(StateQueryData);
});
});
break;
}
case StateQueryData: {
// Query inverter status
qCDebug(dcSma()) << "Inverter: Request inverter status...";
SpeedwireInverterReply *reply = sendQueryRequest(Speedwire::CommandQueryStatus, 0x00214800, 0x002148FF);
connect(reply, &SpeedwireInverterReply::finished, this, [=](){
if (reply->error() != SpeedwireInverterReply::ErrorNoError) {
qCWarning(dcSma()) << "Inverter: Failed to get status from inverter:" << reply->request().command() << reply->error();
setState(StateDisconnected);
return;
}
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);
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;
}
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);
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;
}
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);
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;
}
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);
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;
}
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);
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;
}
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);
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;
}
qCDebug(dcSma()) << "Inverter: Query request finished successfully" << reply->request().command();
processGridFrequencyResponse(reply->responsePayload());
setReachable(true);
emit valuesUpdated();
// ############# 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);
return;
} 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);
return;
} else {
qCDebug(dcSma()) << "Inverter: Process battery charge status response" << reply->responsePayload().toHex();
processBatteryChargeResponse(reply->responsePayload());
}
setBatteryAvailable(true);
emit batteryValuesUpdated();
setState(StateIdle);
});
});
});
});
});
});
});
});
});
break;
}
}
}