// SPDX-License-Identifier: LGPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of libnymea-sunspec.
*
* libnymea-sunspec is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* libnymea-sunspec 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 libnymea-sunspec. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "sunspecsolarmodulemodel.h"
#include "sunspecconnection.h"

SunSpecSolarModuleModel::SunSpecSolarModuleModel(SunSpecConnection *connection, quint16 modbusStartRegister, quint16 modelLength, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent) :
    SunSpecModel(connection, modbusStartRegister, 502, modelLength, byteOrder, parent)
{
    m_modelBlockType = SunSpecModel::ModelBlockTypeFixed;

    initDataPoints();
}

SunSpecSolarModuleModel::~SunSpecSolarModuleModel()
{

}

QString SunSpecSolarModuleModel::name() const
{
    return "solar_module";
}

QString SunSpecSolarModuleModel::description() const
{
    return "A solar module model supporting DC-DC converter";
}

QString SunSpecSolarModuleModel::label() const
{
    return "Solar Module";
}

qint16 SunSpecSolarModuleModel::a_SF() const
{
    return m_a_SF;
}
qint16 SunSpecSolarModuleModel::v_SF() const
{
    return m_v_SF;
}
qint16 SunSpecSolarModuleModel::w_SF() const
{
    return m_w_SF;
}
qint16 SunSpecSolarModuleModel::wh_SF() const
{
    return m_wh_SF;
}
SunSpecSolarModuleModel::Stat SunSpecSolarModuleModel::status() const
{
    return m_status;
}
quint16 SunSpecSolarModuleModel::vendorStatus() const
{
    return m_vendorStatus;
}
SunSpecSolarModuleModel::EvtFlags SunSpecSolarModuleModel::events() const
{
    return m_events;
}
quint32 SunSpecSolarModuleModel::vendorModuleEventFlags() const
{
    return m_vendorModuleEventFlags;
}
quint16 SunSpecSolarModuleModel::control() const
{
    return m_control;
}

QModbusReply *SunSpecSolarModuleModel::setControl(quint16 control)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("Ctl");
    QVector<quint16> registers = SunSpecDataPoint::convertFromUInt16(static_cast<quint16>(control));

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
quint32 SunSpecSolarModuleModel::vendorControl() const
{
    return m_vendorControl;
}

QModbusReply *SunSpecSolarModuleModel::setVendorControl(quint32 vendorControl)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("CtlVend");
    QVector<quint16> registers = SunSpecDataPoint::convertFromUInt32(static_cast<quint32>(vendorControl));

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
qint32 SunSpecSolarModuleModel::controlValue() const
{
    return m_controlValue;
}

QModbusReply *SunSpecSolarModuleModel::setControlValue(qint32 controlValue)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("CtlVal");
    QVector<quint16> registers = SunSpecDataPoint::convertFromInt32(controlValue);

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
quint32 SunSpecSolarModuleModel::timestamp() const
{
    return m_timestamp;
}
float SunSpecSolarModuleModel::outputCurrent() const
{
    return m_outputCurrent;
}
float SunSpecSolarModuleModel::outputVoltage() const
{
    return m_outputVoltage;
}
quint32 SunSpecSolarModuleModel::outputEnergy() const
{
    return m_outputEnergy;
}
float SunSpecSolarModuleModel::outputPower() const
{
    return m_outputPower;
}
qint16 SunSpecSolarModuleModel::temp() const
{
    return m_temp;
}
float SunSpecSolarModuleModel::inputCurrent() const
{
    return m_inputCurrent;
}
float SunSpecSolarModuleModel::inputVoltage() const
{
    return m_inputVoltage;
}
quint32 SunSpecSolarModuleModel::inputEnergy() const
{
    return m_inputEnergy;
}
float SunSpecSolarModuleModel::inputPower() const
{
    return m_inputPower;
}
void SunSpecSolarModuleModel::initDataPoints()
{
    SunSpecDataPoint modelIdDataPoint;
    modelIdDataPoint.setName("ID");
    modelIdDataPoint.setLabel("Model ID");
    modelIdDataPoint.setDescription("Model identifier");
    modelIdDataPoint.setMandatory(true);
    modelIdDataPoint.setSize(1);
    modelIdDataPoint.setAddressOffset(0);
    modelIdDataPoint.setSunSpecDataType("uint16");
    modelIdDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(modelIdDataPoint.name(), modelIdDataPoint);

    SunSpecDataPoint modelLengthDataPoint;
    modelLengthDataPoint.setName("L");
    modelLengthDataPoint.setLabel("Model Length");
    modelLengthDataPoint.setDescription("Model length");
    modelLengthDataPoint.setMandatory(true);
    modelLengthDataPoint.setSize(1);
    modelLengthDataPoint.setAddressOffset(1);
    modelLengthDataPoint.setSunSpecDataType("uint16");
    modelLengthDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(modelLengthDataPoint.name(), modelLengthDataPoint);

    SunSpecDataPoint a_SFDataPoint;
    a_SFDataPoint.setName("A_SF");
    a_SFDataPoint.setDescription("Current scale factor");
    a_SFDataPoint.setSize(1);
    a_SFDataPoint.setAddressOffset(2);
    a_SFDataPoint.setBlockOffset(0);
    a_SFDataPoint.setSunSpecDataType("sunssf");
    a_SFDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(a_SFDataPoint.name(), a_SFDataPoint);

    SunSpecDataPoint v_SFDataPoint;
    v_SFDataPoint.setName("V_SF");
    v_SFDataPoint.setDescription("Voltage scale factor");
    v_SFDataPoint.setSize(1);
    v_SFDataPoint.setAddressOffset(3);
    v_SFDataPoint.setBlockOffset(1);
    v_SFDataPoint.setSunSpecDataType("sunssf");
    v_SFDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(v_SFDataPoint.name(), v_SFDataPoint);

    SunSpecDataPoint w_SFDataPoint;
    w_SFDataPoint.setName("W_SF");
    w_SFDataPoint.setDescription("Power scale factor");
    w_SFDataPoint.setSize(1);
    w_SFDataPoint.setAddressOffset(4);
    w_SFDataPoint.setBlockOffset(2);
    w_SFDataPoint.setSunSpecDataType("sunssf");
    w_SFDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(w_SFDataPoint.name(), w_SFDataPoint);

    SunSpecDataPoint wh_SFDataPoint;
    wh_SFDataPoint.setName("Wh_SF");
    wh_SFDataPoint.setDescription("Energy scale factor");
    wh_SFDataPoint.setSize(1);
    wh_SFDataPoint.setAddressOffset(5);
    wh_SFDataPoint.setBlockOffset(3);
    wh_SFDataPoint.setSunSpecDataType("sunssf");
    wh_SFDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(wh_SFDataPoint.name(), wh_SFDataPoint);

    SunSpecDataPoint statusDataPoint;
    statusDataPoint.setName("Stat");
    statusDataPoint.setLabel("Status");
    statusDataPoint.setDescription("Enumerated value.  Module Status Code");
    statusDataPoint.setMandatory(true);
    statusDataPoint.setSize(1);
    statusDataPoint.setAddressOffset(6);
    statusDataPoint.setBlockOffset(4);
    statusDataPoint.setSunSpecDataType("enum16");
    statusDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(statusDataPoint.name(), statusDataPoint);

    SunSpecDataPoint vendorStatusDataPoint;
    vendorStatusDataPoint.setName("StatVend");
    vendorStatusDataPoint.setLabel("Vendor Status");
    vendorStatusDataPoint.setDescription("Module Vendor Status Code");
    vendorStatusDataPoint.setSize(1);
    vendorStatusDataPoint.setAddressOffset(7);
    vendorStatusDataPoint.setBlockOffset(5);
    vendorStatusDataPoint.setSunSpecDataType("enum16");
    vendorStatusDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorStatusDataPoint.name(), vendorStatusDataPoint);

    SunSpecDataPoint eventsDataPoint;
    eventsDataPoint.setName("Evt");
    eventsDataPoint.setLabel("Events");
    eventsDataPoint.setDescription("Bitmask value.  Module Event Flags");
    eventsDataPoint.setMandatory(true);
    eventsDataPoint.setSize(2);
    eventsDataPoint.setAddressOffset(8);
    eventsDataPoint.setBlockOffset(6);
    eventsDataPoint.setSunSpecDataType("bitfield32");
    eventsDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(eventsDataPoint.name(), eventsDataPoint);

    SunSpecDataPoint vendorModuleEventFlagsDataPoint;
    vendorModuleEventFlagsDataPoint.setName("EvtVend");
    vendorModuleEventFlagsDataPoint.setLabel("Vendor Module Event Flags");
    vendorModuleEventFlagsDataPoint.setDescription("Vendor specific flags");
    vendorModuleEventFlagsDataPoint.setSize(2);
    vendorModuleEventFlagsDataPoint.setAddressOffset(10);
    vendorModuleEventFlagsDataPoint.setBlockOffset(8);
    vendorModuleEventFlagsDataPoint.setSunSpecDataType("bitfield32");
    vendorModuleEventFlagsDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorModuleEventFlagsDataPoint.name(), vendorModuleEventFlagsDataPoint);

    SunSpecDataPoint controlDataPoint;
    controlDataPoint.setName("Ctl");
    controlDataPoint.setLabel("Control");
    controlDataPoint.setDescription("Module Control");
    controlDataPoint.setSize(1);
    controlDataPoint.setAddressOffset(12);
    controlDataPoint.setBlockOffset(10);
    controlDataPoint.setSunSpecDataType("enum16");
    controlDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    controlDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(controlDataPoint.name(), controlDataPoint);

    SunSpecDataPoint vendorControlDataPoint;
    vendorControlDataPoint.setName("CtlVend");
    vendorControlDataPoint.setLabel("Vendor Control");
    vendorControlDataPoint.setDescription("Vendor Module Control");
    vendorControlDataPoint.setSize(2);
    vendorControlDataPoint.setAddressOffset(13);
    vendorControlDataPoint.setBlockOffset(11);
    vendorControlDataPoint.setSunSpecDataType("enum32");
    vendorControlDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    vendorControlDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorControlDataPoint.name(), vendorControlDataPoint);

    SunSpecDataPoint controlValueDataPoint;
    controlValueDataPoint.setName("CtlVal");
    controlValueDataPoint.setLabel("Control Value");
    controlValueDataPoint.setDescription("Module Control Value");
    controlValueDataPoint.setSize(2);
    controlValueDataPoint.setAddressOffset(15);
    controlValueDataPoint.setBlockOffset(13);
    controlValueDataPoint.setSunSpecDataType("int32");
    controlValueDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    controlValueDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(controlValueDataPoint.name(), controlValueDataPoint);

    SunSpecDataPoint timestampDataPoint;
    timestampDataPoint.setName("Tms");
    timestampDataPoint.setLabel("Timestamp");
    timestampDataPoint.setDescription("Time in seconds since 2000 epoch");
    timestampDataPoint.setUnits("Secs");
    timestampDataPoint.setSize(2);
    timestampDataPoint.setAddressOffset(17);
    timestampDataPoint.setBlockOffset(15);
    timestampDataPoint.setSunSpecDataType("uint32");
    timestampDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(timestampDataPoint.name(), timestampDataPoint);

    SunSpecDataPoint outputCurrentDataPoint;
    outputCurrentDataPoint.setName("OutA");
    outputCurrentDataPoint.setLabel("Output Current");
    outputCurrentDataPoint.setDescription("Output Current");
    outputCurrentDataPoint.setUnits("A");
    outputCurrentDataPoint.setSize(1);
    outputCurrentDataPoint.setAddressOffset(19);
    outputCurrentDataPoint.setBlockOffset(17);
    outputCurrentDataPoint.setScaleFactorName("A_SF");
    outputCurrentDataPoint.setSunSpecDataType("int16");
    outputCurrentDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(outputCurrentDataPoint.name(), outputCurrentDataPoint);

    SunSpecDataPoint outputVoltageDataPoint;
    outputVoltageDataPoint.setName("OutV");
    outputVoltageDataPoint.setLabel("Output Voltage");
    outputVoltageDataPoint.setDescription("Output Voltage");
    outputVoltageDataPoint.setUnits("V");
    outputVoltageDataPoint.setSize(1);
    outputVoltageDataPoint.setAddressOffset(20);
    outputVoltageDataPoint.setBlockOffset(18);
    outputVoltageDataPoint.setScaleFactorName("V_SF");
    outputVoltageDataPoint.setSunSpecDataType("int16");
    outputVoltageDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(outputVoltageDataPoint.name(), outputVoltageDataPoint);

    SunSpecDataPoint outputEnergyDataPoint;
    outputEnergyDataPoint.setName("OutWh");
    outputEnergyDataPoint.setLabel("Output Energy");
    outputEnergyDataPoint.setDescription("Output Energy");
    outputEnergyDataPoint.setUnits("Wh");
    outputEnergyDataPoint.setSize(2);
    outputEnergyDataPoint.setAddressOffset(21);
    outputEnergyDataPoint.setBlockOffset(19);
    outputEnergyDataPoint.setScaleFactorName("Wh_SF");
    outputEnergyDataPoint.setSunSpecDataType("acc32");
    outputEnergyDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(outputEnergyDataPoint.name(), outputEnergyDataPoint);

    SunSpecDataPoint outputPowerDataPoint;
    outputPowerDataPoint.setName("OutPw");
    outputPowerDataPoint.setLabel("Output Power");
    outputPowerDataPoint.setDescription("Output Power");
    outputPowerDataPoint.setUnits("W");
    outputPowerDataPoint.setSize(1);
    outputPowerDataPoint.setAddressOffset(23);
    outputPowerDataPoint.setBlockOffset(21);
    outputPowerDataPoint.setScaleFactorName("W_SF");
    outputPowerDataPoint.setSunSpecDataType("int16");
    outputPowerDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(outputPowerDataPoint.name(), outputPowerDataPoint);

    SunSpecDataPoint tempDataPoint;
    tempDataPoint.setName("Tmp");
    tempDataPoint.setLabel("Temp");
    tempDataPoint.setDescription("Module Temperature");
    tempDataPoint.setUnits("C");
    tempDataPoint.setSize(1);
    tempDataPoint.setAddressOffset(24);
    tempDataPoint.setBlockOffset(22);
    tempDataPoint.setSunSpecDataType("int16");
    tempDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(tempDataPoint.name(), tempDataPoint);

    SunSpecDataPoint inputCurrentDataPoint;
    inputCurrentDataPoint.setName("InA");
    inputCurrentDataPoint.setLabel("Input Current");
    inputCurrentDataPoint.setDescription("Input Current");
    inputCurrentDataPoint.setUnits("A");
    inputCurrentDataPoint.setSize(1);
    inputCurrentDataPoint.setAddressOffset(25);
    inputCurrentDataPoint.setBlockOffset(23);
    inputCurrentDataPoint.setScaleFactorName("A_SF");
    inputCurrentDataPoint.setSunSpecDataType("int16");
    inputCurrentDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(inputCurrentDataPoint.name(), inputCurrentDataPoint);

    SunSpecDataPoint inputVoltageDataPoint;
    inputVoltageDataPoint.setName("InV");
    inputVoltageDataPoint.setLabel("Input Voltage");
    inputVoltageDataPoint.setDescription("Input Voltage");
    inputVoltageDataPoint.setUnits("V");
    inputVoltageDataPoint.setSize(1);
    inputVoltageDataPoint.setAddressOffset(26);
    inputVoltageDataPoint.setBlockOffset(24);
    inputVoltageDataPoint.setScaleFactorName("V_SF");
    inputVoltageDataPoint.setSunSpecDataType("int16");
    inputVoltageDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(inputVoltageDataPoint.name(), inputVoltageDataPoint);

    SunSpecDataPoint inputEnergyDataPoint;
    inputEnergyDataPoint.setName("InWh");
    inputEnergyDataPoint.setLabel("Input Energy");
    inputEnergyDataPoint.setDescription("Input Energy");
    inputEnergyDataPoint.setUnits("Wh");
    inputEnergyDataPoint.setSize(2);
    inputEnergyDataPoint.setAddressOffset(27);
    inputEnergyDataPoint.setBlockOffset(25);
    inputEnergyDataPoint.setScaleFactorName("Wh_SF");
    inputEnergyDataPoint.setSunSpecDataType("acc32");
    inputEnergyDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(inputEnergyDataPoint.name(), inputEnergyDataPoint);

    SunSpecDataPoint inputPowerDataPoint;
    inputPowerDataPoint.setName("InW");
    inputPowerDataPoint.setLabel("Input Power");
    inputPowerDataPoint.setDescription("Input Power");
    inputPowerDataPoint.setUnits("W");
    inputPowerDataPoint.setSize(1);
    inputPowerDataPoint.setAddressOffset(29);
    inputPowerDataPoint.setBlockOffset(27);
    inputPowerDataPoint.setScaleFactorName("W_SF");
    inputPowerDataPoint.setSunSpecDataType("int16");
    inputPowerDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(inputPowerDataPoint.name(), inputPowerDataPoint);

}

void SunSpecSolarModuleModel::processBlockData()
{
    // Scale factors
    if (m_dataPoints.value("A_SF").isValid())
        m_a_SF = m_dataPoints.value("A_SF").toInt16();

    if (m_dataPoints.value("V_SF").isValid())
        m_v_SF = m_dataPoints.value("V_SF").toInt16();

    if (m_dataPoints.value("W_SF").isValid())
        m_w_SF = m_dataPoints.value("W_SF").toInt16();

    if (m_dataPoints.value("Wh_SF").isValid())
        m_wh_SF = m_dataPoints.value("Wh_SF").toInt16();


    // Update properties according to the data point type
    if (m_dataPoints.value("A_SF").isValid())
        m_a_SF = m_dataPoints.value("A_SF").toInt16();

    if (m_dataPoints.value("V_SF").isValid())
        m_v_SF = m_dataPoints.value("V_SF").toInt16();

    if (m_dataPoints.value("W_SF").isValid())
        m_w_SF = m_dataPoints.value("W_SF").toInt16();

    if (m_dataPoints.value("Wh_SF").isValid())
        m_wh_SF = m_dataPoints.value("Wh_SF").toInt16();

    if (m_dataPoints.value("Stat").isValid())
        m_status = static_cast<Stat>(m_dataPoints.value("Stat").toUInt16());

    if (m_dataPoints.value("StatVend").isValid())
        m_vendorStatus = m_dataPoints.value("StatVend").toUInt16();

    if (m_dataPoints.value("Evt").isValid())
        m_events = static_cast<EvtFlags>(m_dataPoints.value("Evt").toUInt32());

    if (m_dataPoints.value("EvtVend").isValid())
        m_vendorModuleEventFlags = m_dataPoints.value("EvtVend").toUInt32();

    if (m_dataPoints.value("Ctl").isValid())
        m_control = m_dataPoints.value("Ctl").toUInt16();

    if (m_dataPoints.value("CtlVend").isValid())
        m_vendorControl = m_dataPoints.value("CtlVend").toUInt32();

    if (m_dataPoints.value("CtlVal").isValid())
        m_controlValue = m_dataPoints.value("CtlVal").toInt32();

    if (m_dataPoints.value("Tms").isValid())
        m_timestamp = m_dataPoints.value("Tms").toUInt32();

    if (m_dataPoints.value("OutA").isValid())
        m_outputCurrent = m_dataPoints.value("OutA").toFloatWithSSF(m_a_SF);

    if (m_dataPoints.value("OutV").isValid())
        m_outputVoltage = m_dataPoints.value("OutV").toFloatWithSSF(m_v_SF);

    if (m_dataPoints.value("OutWh").isValid())
        m_outputEnergy = m_dataPoints.value("OutWh").toFloatWithSSF(m_wh_SF);

    if (m_dataPoints.value("OutPw").isValid())
        m_outputPower = m_dataPoints.value("OutPw").toFloatWithSSF(m_w_SF);

    if (m_dataPoints.value("Tmp").isValid())
        m_temp = m_dataPoints.value("Tmp").toInt16();

    if (m_dataPoints.value("InA").isValid())
        m_inputCurrent = m_dataPoints.value("InA").toFloatWithSSF(m_a_SF);

    if (m_dataPoints.value("InV").isValid())
        m_inputVoltage = m_dataPoints.value("InV").toFloatWithSSF(m_v_SF);

    if (m_dataPoints.value("InWh").isValid())
        m_inputEnergy = m_dataPoints.value("InWh").toFloatWithSSF(m_wh_SF);

    if (m_dataPoints.value("InW").isValid())
        m_inputPower = m_dataPoints.value("InW").toFloatWithSSF(m_w_SF);


    qCDebug(dcSunSpecModelData()) << this;
}

QDebug operator<<(QDebug debug, SunSpecSolarModuleModel *model)
{
    debug.nospace().noquote() << "SunSpecSolarModuleModel(Model: " << model->modelId() << ", Register: " << model->modbusStartRegister() << ", Length: " << model->modelLength() << ")\n";
    debug.nospace().noquote() << "    - " << model->dataPoints().value("Stat") << "-->";
    if (model->dataPoints().value("Stat").isValid()) {
        debug.nospace().noquote() << model->status() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("StatVend") << "-->";
    if (model->dataPoints().value("StatVend").isValid()) {
        debug.nospace().noquote() << model->vendorStatus() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("Evt") << "-->";
    if (model->dataPoints().value("Evt").isValid()) {
        debug.nospace().noquote() << model->events() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("EvtVend") << "-->";
    if (model->dataPoints().value("EvtVend").isValid()) {
        debug.nospace().noquote() << model->vendorModuleEventFlags() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("Ctl") << "-->";
    if (model->dataPoints().value("Ctl").isValid()) {
        debug.nospace().noquote() << model->control() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("CtlVend") << "-->";
    if (model->dataPoints().value("CtlVend").isValid()) {
        debug.nospace().noquote() << model->vendorControl() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("CtlVal") << "-->";
    if (model->dataPoints().value("CtlVal").isValid()) {
        debug.nospace().noquote() << model->controlValue() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("Tms") << "-->";
    if (model->dataPoints().value("Tms").isValid()) {
        debug.nospace().noquote() << model->timestamp() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("OutA") << "-->";
    if (model->dataPoints().value("OutA").isValid()) {
        debug.nospace().noquote() << model->outputCurrent() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("OutV") << "-->";
    if (model->dataPoints().value("OutV").isValid()) {
        debug.nospace().noquote() << model->outputVoltage() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("OutWh") << "-->";
    if (model->dataPoints().value("OutWh").isValid()) {
        debug.nospace().noquote() << model->outputEnergy() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("OutPw") << "-->";
    if (model->dataPoints().value("OutPw").isValid()) {
        debug.nospace().noquote() << model->outputPower() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("Tmp") << "-->";
    if (model->dataPoints().value("Tmp").isValid()) {
        debug.nospace().noquote() << model->temp() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("InA") << "-->";
    if (model->dataPoints().value("InA").isValid()) {
        debug.nospace().noquote() << model->inputCurrent() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("InV") << "-->";
    if (model->dataPoints().value("InV").isValid()) {
        debug.nospace().noquote() << model->inputVoltage() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("InWh") << "-->";
    if (model->dataPoints().value("InWh").isValid()) {
        debug.nospace().noquote() << model->inputEnergy() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("InW") << "-->";
    if (model->dataPoints().value("InW").isValid()) {
        debug.nospace().noquote() << model->inputPower() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }


    return debug.space().quote();
}
