1561 lines
66 KiB
C++
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;
|
|
}
|
|
}
|
|
}
|