This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
Michael Schwarz 092d80508c Fix reading data of large SunSpec models
Up until now, the data of a SunSpec model was read as one block. This
does not work for large SunSpec models since the max. number of
registers that can be read as a block via Modbus is 125. The data of
SunSpec models is now read in chunks of max. 125 registers.
2025-11-26 07:57:27 +01:00

254 lines
9.0 KiB
C++

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2025, nymea GmbH
* Contact: contact@nymea.io
*
* This fileDescriptor is part of nymea.
* This project including source code and documentation is protected by
* copyright law, and remains the property of nymea GmbH. All rights, including
* reproduction, publication, editing and translation, are reserved. The use of
* this project is subject to the terms of a license agreement to be concluded
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
* under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; version 3. This project is distributed in the hope that
* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this project. If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under
* contact@nymea.io or see our FAQ/Licensing Information on
* https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "sunspecmodel.h"
#include "sunspecconnection.h"
#include "sunspecmodelrepeatingblock.h"
Q_LOGGING_CATEGORY(dcSunSpecModelData, "SunSpecModelData")
SunSpecModel::SunSpecModel(SunSpecConnection *connection, quint16 modbusStartRegister, quint16 modelId, quint16 modelLength, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent) :
QObject(parent),
m_connection(connection),
m_modbusStartRegister(modbusStartRegister),
m_modelId(modelId),
m_modelLength(modelLength),
m_byteOrder(byteOrder)
{
m_initTimer.setSingleShot(true);
m_initTimer.setInterval(10000);
connect(&m_initTimer, &QTimer::timeout, this, [this](){
if (!m_initialized) {
emit initFinished(false);
}
});
}
SunSpecConnection *SunSpecModel::connection() const
{
return m_connection;
}
bool SunSpecModel::initialized() const
{
return m_initialized;
}
quint16 SunSpecModel::modelId() const
{
return m_modelId;
}
quint16 SunSpecModel::modelLength() const
{
return m_modelLength;
}
quint16 SunSpecModel::modbusStartRegister() const
{
return m_modbusStartRegister;
}
SunSpecDataPoint::ByteOrder SunSpecModel::byteOrder() const
{
return m_byteOrder;
}
QHash<QString, SunSpecDataPoint> SunSpecModel::dataPoints() const
{
return m_dataPoints;
}
QVector<quint16> SunSpecModel::blockData() const
{
return m_blockData;
}
SunSpecModel::CommonModelInfo SunSpecModel::commonModelInfo() const
{
return m_commonModelInfo;
}
QList<SunSpecModelRepeatingBlock *> SunSpecModel::repeatingBlocks() const
{
return m_repeatingBlocks;
}
void SunSpecModel::init()
{
m_initialized = false;
readBlockData();
m_initTimer.start();
}
void SunSpecModel::readBlockData()
{
qCDebug(dcSunSpecModelData())
<< "Start reading SunSpec model data (start address:"
<< m_modbusStartRegister
<< ", length:"
<< m_modelLength + 2
<< ")";
m_partialBlockData.clear();
m_lastStartAddress = 0;
m_lastReadSize = 0;
readNextBlockDataPart();
}
bool SunSpecModel::operator ==(const SunSpecModel &other) const
{
return m_connection == other.connection() &&
m_modbusStartRegister == other.modbusStartRegister() &&
m_modelId == other.modelId() &&
m_modelLength == other.modelLength();
}
void SunSpecModel::setInitializedFinished()
{
if (!m_initialized) {
m_initTimer.stop();
m_initialized = true;
emit initFinished(true);
}
}
void SunSpecModel::readNextBlockDataPart()
{
const auto startAddress = m_modbusStartRegister + m_partialBlockData.size();
// Model header has length 2.
const auto readSize = static_cast<quint16>(std::min(125, m_modelLength + 2 - m_partialBlockData.size()));
if (startAddress == m_lastStartAddress && readSize == m_lastReadSize) {
qCWarning(dcSunSpecModelData()) << "Last read yielded no data. Not trying to read the same data again.";
return;
}
qCDebug(dcSunSpecModelData())
<< "Trying to read data block (start address:"
<< startAddress
<< ", length:"
<< readSize
<< ")";
QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters,
startAddress,
readSize);
QModbusReply *reply = m_connection->sendReadRequest(request, m_connection->slaveId());
if (!reply) {
qCDebug(dcSunSpecModelData()) << "Read data block error: " << m_connection->modbusTcpClient()->errorString();
return;
}
if (reply->isFinished()) {
qCWarning(dcSunSpecModelData()) << "Read data block error: " << m_connection->modbusTcpClient()->errorString();
reply->deleteLater(); // broadcast replies return immediately
return;
}
connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
connect(reply, &QModbusReply::finished, this, [=]() {
if (reply->error() != QModbusDevice::NoError) {
qCWarning(dcSunSpec()) << name() << description() << "Read data block response error:" << reply->error();
return;
}
const QModbusDataUnit unit = reply->result();
qCDebug(dcSunSpecModelData()) << "-->" << "Received data block" << this << unit.values().count() << SunSpecDataPoint::registersToString(unit.values());
m_partialBlockData.append(unit.values());
if (m_partialBlockData.size() < m_modelLength + 2) {
readNextBlockDataPart();
} else { // finished reading
handleNewBlockData();
}
});
connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error) {
qCWarning(dcSunSpecModelData()) << name() << description() << "Modbus reply while reading data block. Error:" << error << reply->errorString();
});
}
void SunSpecModel::handleNewBlockData()
{
m_blockData = m_partialBlockData;
emit blockDataChanged(m_blockData);
// Fill the data points
foreach (const QString &dataPointName, m_dataPoints.keys()) {
QVector<quint16> rawData = m_blockData.mid(m_dataPoints[dataPointName].addressOffset(), m_dataPoints[dataPointName].size());
m_dataPoints[dataPointName].setRawData(rawData);
qCDebug(dcSunSpecModelData()) << "Set raw data:" << m_dataPoints[dataPointName] << SunSpecDataPoint::registersToString(rawData) << (m_dataPoints[dataPointName].isValid() ? "Valid" : "Invalid");
}
// Fill the private member data using the data points
processBlockData();
// Handle repeating blocks
if (!m_repeatingBlocks.isEmpty()) {
auto repeatingBlocksTotalSize = 0;
foreach (SunSpecModelRepeatingBlock *block, m_repeatingBlocks) {
repeatingBlocksTotalSize += block->blockSize();
}
const auto repeatingBlocksDataOffset = m_blockData.size() - repeatingBlocksTotalSize;
foreach (SunSpecModelRepeatingBlock *block, m_repeatingBlocks) {
qCDebug(dcSunSpecModelData()) << "Block" << block->blockIndex();
// Fill the data points
const auto blockOffset = repeatingBlocksDataOffset + block->blockIndex() * block->blockSize();
foreach (const QString &dataPointName, block->m_dataPoints.keys()) {
const auto dataPointOffset = blockOffset + block->m_dataPoints[dataPointName].addressOffset();
QVector<quint16> rawData = m_blockData.mid(dataPointOffset, block->m_dataPoints[dataPointName].size());
block->m_dataPoints[dataPointName].setRawData(rawData);
qCDebug(dcSunSpecModelData())
<< "Set raw data (offset:"
<< dataPointOffset
<< ", size:"
<< block->m_dataPoints[dataPointName].size()
<< "):"
<< block->m_dataPoints[dataPointName]
<< SunSpecDataPoint::registersToString(rawData)
<< (block->m_dataPoints[dataPointName].isValid() ? "Valid" : "Invalid");
}
// Fill the private member data using the data points
block->processBlockData();
}
}
// Make sure initialized gets called
setInitializedFinished();
// Inform about the new block data
emit blockUpdated();
}
QDebug operator<<(QDebug debug, SunSpecModel *model)
{
debug.nospace().noquote() << "SunSpecModel(Model: " << model->modelId() << ", " << model->name() << ", " << model->label() << ", Register: " << model->modbusStartRegister() << ", Length: " << model->modelLength() << ")";
return debug.space().quote();
}