// 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 "amtronecu.h"
#include "extern-plugininfo.h"

Q_DECLARE_LOGGING_CATEGORY(dcAmtronECUModbusTcpConnection)

AmtronECU::AmtronECU(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent)
    : AmtronECUModbusTcpConnection{hostAddress, port, slaveId, parent}
{

}

AmtronECU::AmtronECU(ModbusTcpMaster *modbusTcpMaster, quint16 slaveId, QObject *parent)
    : AmtronECUModbusTcpConnection{modbusTcpMaster, slaveId, parent}
{

}

bool AmtronECU::initialize()
{
    if (!m_reachable) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to initialize but the device is not to be reachable.";
        return false;
    }

    if (m_initObject) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to initialize but the init process is already running.";
        return false;
    }

    m_initializing = true;

    // Parent object for the init process
    m_initObject = new QObject(this);

    QModbusReply *reply = nullptr;


    // This is a 2 Step initialization. First read the firmware version, if the firmware version is smaller than 5.22 we are done
    // otherwise read also the model since we use that for detecting the exact manufacturer.

    // Some useres have to stay on 5.12 due to calibration law which is not available on 5.22

    // Read Firmware version
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read init \"Firmware version\" register:" << 100 << "size:" << 2;
    reply = readFirmwareVersion();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        finishInitialization(false);
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingInitReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){
        handleModbusError(reply->error());
        m_pendingInitReplies.removeAll(reply);
        if (reply->error() != QModbusDevice::NoError) {
            finishInitialization(false);
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from init \"Firmware version\" register" << 100 << "size:" << 2 << unit.values();
        if (unit.values().size() == 2) {
            processFirmwareVersionRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Firmware version\" registers" << 100 << "size:" << 2 << "returned different size than requested. Ignoring incomplete data" << unit.values();
            finishInitialization(false);
            return;
        }


        // Make sure this is the correct firmware version format (0x-- 0x2e 0x-- 0x--)
        if ((m_firmwareVersion & 0x00ff0000) != 0x002e0000) {
            qCWarning(dcMennekes()) << "Invalid firmware version format. Could not determin the firmware format from uint32" << QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16));
            m_detectedVersion = VersionUnknown;
            finishInitialization(false);
            return;
        }

        // If the firmware version is smaller than 5.22 (Ascii of the uint32 firmware version property),
        // we are done since the model registers have been introduced in Version 5.22
        if (m_firmwareVersion < 0x352e3232) {
            qCDebug(dcMennekes()) << "Detected firmware version" << QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16)) << ". Some features are only available from version >= 5.22.";
            m_detectedVersion = VersionOld;
        } else {
            qCDebug(dcMennekes()) << "Detected firmware version" <<  QByteArray::fromHex(QByteArray::number(m_firmwareVersion, 16)) << "All features available.";
            m_detectedVersion = VersionNew;

            // Read Device model (>= 5.22)
            qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read init \"Device model (>= 5.22)\" register:" << 142 << "size:" << 10;
            QModbusReply *reply = readModel();
            if (!reply) {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
                finishInitialization(false);
                return;
            }

            if (reply->isFinished()) {
                reply->deleteLater(); // Broadcast reply returns immediatly
                return;
            }

            m_pendingInitReplies.append(reply);
            connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
            connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){
                handleModbusError(reply->error());
                m_pendingInitReplies.removeAll(reply);
                if (reply->error() != QModbusDevice::NoError) {
                    finishInitialization(false);
                    return;
                }

                const QModbusDataUnit unit = reply->result();
                qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from init \"Device model (>= 5.22)\" register" << 142 << "size:" << 10 << unit.values();
                if (unit.values().size() == 10) {
                    processModelRegisterValues(unit.values());
                } else {
                    qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Device model (>= 5.22)\" registers" << 142 << "size:" << 10 << "returned different size than requested. Ignoring incomplete data" << unit.values();
                }
                verifyInitFinished();
            });

            connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){
                QModbusResponse response = reply->rawResult();
                if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
                    qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
                } else {
                    qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Device model (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
                }
            });
        }

        verifyInitFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Firmware version\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });

    return true;
}

bool AmtronECU::update()
{
    if (!reachable()) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Tried to update but the device is not to be reachable.";
        return false;
    }

    if (!m_modbusTcpMaster->connected())
        return false;

    if (!m_pendingUpdateReplies.isEmpty()) {
        qCDebug(dcAmtronECUModbusTcpConnection()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished...";
        return true;
    }

    // First update common registers
    // Then update registers only available for > 5.22

    QModbusReply *reply = nullptr;

    // Read CP signal state
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"CP signal state\" register:" << 122 << "size:" << 1;
    reply = readCpSignalState();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"CP signal state\" register" << 122 << "size:" << 1 << unit.values();
        if (unit.values().size() == 1) {
            processCpSignalStateRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"CP signal state\" registers" << 122 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"CP signal state\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });


    // Read Signalled current to EV
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Signalled current to EV\" register:" << 706 << "size:" << 1;
    reply = readSignalledCurrent();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Signalled current to EV\" register" << 706 << "size:" << 1 << unit.values();
        if (unit.values().size() == 1) {
            processSignalledCurrentRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Signalled current to EV\" registers" << 706 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Signalled current to EV\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });

    // Read Minimum current limit
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Minimum current limit\" register:" << 712 << "size:" << 1;
    reply = readMinCurrentLimit();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Minimum current limit\" register" << 712 << "size:" << 1 << unit.values();
        if (unit.values().size() == 1) {
            processMinCurrentLimitRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Minimum current limit\" registers" << 712 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Minimum current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });


    // Read consumptions
    reply = readBlockConsumptions();
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read block \"consumptions\" registers from:" << 200 << "size:" << 18;
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading block \"consumptions\" registers";
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        const QVector<quint16> blockValues = unit.values();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from reading block \"consumptions\" register" << 200 << "size:" << 18 << blockValues;
        if (blockValues.size() == 18) {
            processMeterEnergyL1RegisterValues(blockValues.mid(0, 2));
            processMeterEnergyL2RegisterValues(blockValues.mid(2, 2));
            processMeterEnergyL3RegisterValues(blockValues.mid(4, 2));
            processMeterPowerL1RegisterValues(blockValues.mid(6, 2));
            processMeterPowerL2RegisterValues(blockValues.mid(8, 2));
            processMeterPowerL3RegisterValues(blockValues.mid(10, 2));
            processMeterCurrentL1RegisterValues(blockValues.mid(12, 2));
            processMeterCurrentL2RegisterValues(blockValues.mid(14, 2));
            processMeterCurrentL3RegisterValues(blockValues.mid(16, 2));
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"consumptions\" block registers" << 200 << "size:" << 18 << "returned different size than requested. Ignoring incomplete data" << blockValues;
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptions\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptions\" registers" << error << reply->errorString();
        }
    });


    // Read Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" register:" << 124 << "size:" << 1;
    reply = readCpAvailability();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" register" << 124 << "size:" << 1 << unit.values();
        if (unit.values().size() == 1) {
            processCpAvailabilityRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers" << 124 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable )\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });



    // Read HEMS current limit
    qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"HEMS current limit\" register:" << 1000 << "size:" << 1;
    reply = readHemsCurrentLimit();
    if (!reply) {
        qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
        return false;
    }

    if (reply->isFinished()) {
        reply->deleteLater(); // Broadcast reply returns immediatly
        return false;
    }

    m_pendingUpdateReplies.append(reply);
    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
    connect(reply, &QModbusReply::finished, this, [this, reply](){
        m_pendingUpdateReplies.removeAll(reply);
        handleModbusError(reply->error());
        if (reply->error() != QModbusDevice::NoError) {
            verifyUpdateFinished();
            return;
        }

        const QModbusDataUnit unit = reply->result();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"HEMS current limit\" register" << 1000 << "size:" << 1 << unit.values();
        if (unit.values().size() == 1) {
            processHemsCurrentLimitRegisterValues(unit.values());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"HEMS current limit\" registers" << 1000 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
        }
        verifyUpdateFinished();
    });

    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
        QModbusResponse response = reply->rawResult();
        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
        } else {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"HEMS current limit\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
        }
    });



    // Only available on >= 5.22
    if (m_detectedVersion == VersionNew) {

        // Read Maximum current limit (>= 5.22)
        qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Maximum current limit (>= 5.22)\" register:" << 715 << "size:" << 1;
        reply = readMaxCurrentLimit();
        if (!reply) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
            return false;
        }

        if (reply->isFinished()) {
            reply->deleteLater(); // Broadcast reply returns immediatly
            return false;
        }

        m_pendingUpdateReplies.append(reply);
        connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
        connect(reply, &QModbusReply::finished, this, [this, reply](){
            m_pendingUpdateReplies.removeAll(reply);
            handleModbusError(reply->error());
            if (reply->error() != QModbusDevice::NoError) {
                verifyUpdateFinished();
                return;
            }

            const QModbusDataUnit unit = reply->result();
            qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Maximum current limit (>= 5.22)\" register" << 715 << "size:" << 1 << unit.values();
            if (unit.values().size() == 1) {
                processMaxCurrentLimitRegisterValues(unit.values());
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Maximum current limit (>= 5.22)\" registers" << 715 << "size:" << 1 << "returned different size than requested. Ignoring incomplete data" << unit.values();
            }
            verifyUpdateFinished();
        });

        connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
            QModbusResponse response = reply->rawResult();
            if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Maximum current limit (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
            }
        });

        // Read Charged energy for current session (>= 5.22)
        qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read \"Charged energy for current session (>= 5.22)\" register:" << 716 << "size:" << 2;
        reply = readChargedEnergy();
        if (!reply) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();
            return false;
        }

        if (reply->isFinished()) {
            reply->deleteLater(); // Broadcast reply returns immediatly
            return false;
        }

        m_pendingUpdateReplies.append(reply);
        connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
        connect(reply, &QModbusReply::finished, this, [this, reply](){
            m_pendingUpdateReplies.removeAll(reply);
            handleModbusError(reply->error());
            if (reply->error() != QModbusDevice::NoError) {
                verifyUpdateFinished();
                return;
            }

            const QModbusDataUnit unit = reply->result();
            qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from \"Charged energy for current session (>= 5.22)\" register" << 716 << "size:" << 2 << unit.values();
            if (unit.values().size() == 2) {
                processChargedEnergyRegisterValues(unit.values());
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"Charged energy for current session (>= 5.22)\" registers" << 716 << "size:" << 2 << "returned different size than requested. Ignoring incomplete data" << unit.values();
            }
            verifyUpdateFinished();
        });

        connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){
            QModbusResponse response = reply->rawResult();
            if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while reading \"Charged energy for current session (>= 5.22)\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();
            }
        });


        // Read consumptionsTotals
        reply = readBlockConsumptionsTotals();
        qCDebug(dcAmtronECUModbusTcpConnection()) << "--> Read block \"consumptionsTotals\" registers from:" << 218 << "size:" << 10;
        if (!reply) {
            qCWarning(dcAmtronECUModbusTcpConnection()) << "Error occurred while reading block \"consumptionsTotals\" registers";
            return false;
        }

        if (reply->isFinished()) {
            reply->deleteLater(); // Broadcast reply returns immediatly
            return false;
        }

        m_pendingUpdateReplies.append(reply);
        connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);
        connect(reply, &QModbusReply::finished, this, [this, reply](){
            m_pendingUpdateReplies.removeAll(reply);
            handleModbusError(reply->error());
            if (reply->error() != QModbusDevice::NoError) {
                verifyUpdateFinished();
                return;
            }

            const QModbusDataUnit unit = reply->result();
            const QVector<quint16> blockValues = unit.values();
            qCDebug(dcAmtronECUModbusTcpConnection()) << "<-- Response from reading block \"consumptionsTotals\" register" << 218 << "size:" << 10 << blockValues;
            if (blockValues.size() == 10) {
                processMeterTotoalEnergyRegisterValues(blockValues.mid(0, 2));
                processMeterTotalPowerRegisterValues(blockValues.mid(2, 2));
                processMeterVoltageL1RegisterValues(blockValues.mid(4, 2));
                processMeterVoltageL2RegisterValues(blockValues.mid(6, 2));
                processMeterVoltageL3RegisterValues(blockValues.mid(8, 2));
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Reading from \"consumptionsTotals\" block registers" << 218 << "size:" << 10 << "returned different size than requested. Ignoring incomplete data" << blockValues;
            }
            verifyUpdateFinished();
        });

        connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){
            QModbusResponse response = reply->rawResult();
            if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptionsTotals\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());
            } else {
                qCWarning(dcAmtronECUModbusTcpConnection()) << "Modbus reply error occurred while updating block \"consumptionsTotals\" registers" << error << reply->errorString();
            }
        });
    }
    return true;
}

AmtronECU::Version AmtronECU::detectedVersion() const
{
    return m_detectedVersion;
}

QDebug operator<<(QDebug debug, AmtronECU *amtronECU)
{
    debug.nospace().noquote() << "AmtronECU(" << amtronECU->modbusTcpMaster()->hostAddress().toString() << ":" << amtronECU->modbusTcpMaster()->port() << ")" << "\n";
    debug.nospace().noquote() << "    - holding  100 | Firmware version: " << amtronECU->firmwareVersion() << "\n";
    debug.nospace().noquote() << "    - holding  122 | CP signal state: " << amtronECU->cpSignalState() << "\n";
    debug.nospace().noquote() << "    - holding  124 | Charge Point availability ( Version < 5.22: 0 available, 1 unavailable | Version >= 5.22 1 available, 0 unavailable ): " << amtronECU->cpAvailability() << "\n";
    debug.nospace().noquote() << "    - holding  200 | Meter energy L1: " << amtronECU->meterEnergyL1() << " [Wh]" << "\n";
    debug.nospace().noquote() << "    - holding  202 | Meter energy L2: " << amtronECU->meterEnergyL2() << " [Wh]" << "\n";
    debug.nospace().noquote() << "    - holding  204 | Meter energy L3: " << amtronECU->meterEnergyL3() << " [Wh]" << "\n";
    debug.nospace().noquote() << "    - holding  206 | Meter power L1: " << amtronECU->meterPowerL1() << " [W]" << "\n";
    debug.nospace().noquote() << "    - holding  208 | Meter power L2: " << amtronECU->meterPowerL2() << " [W]" << "\n";
    debug.nospace().noquote() << "    - holding  210 | Meter power L3: " << amtronECU->meterPowerL3() << " [W]" << "\n";
    debug.nospace().noquote() << "    - holding  212 | Meter current L1: " << amtronECU->meterCurrentL1() << " [mA]" << "\n";
    debug.nospace().noquote() << "    - holding  214 | Meter current L2: " << amtronECU->meterCurrentL2() << " [mA]" << "\n";
    debug.nospace().noquote() << "    - holding  216 | Meter current L3: " << amtronECU->meterCurrentL3() << " [mA]" << "\n";
    debug.nospace().noquote() << "    - holding  706 | Signalled current to EV: " << amtronECU->signalledCurrent() << " [A]" << "\n";
    debug.nospace().noquote() << "    - holding  712 | Minimum current limit: " << amtronECU->minCurrentLimit() << " [A]" << "\n";
    debug.nospace().noquote() << "    - holding  1000 | HEMS current limit: " << amtronECU->hemsCurrentLimit() << " [A]" << "\n";

    if (amtronECU->detectedVersion() == AmtronECU::VersionNew) {
        debug.nospace().noquote() << "    - holding  142 | Device model (>= 5.22): " << amtronECU->model() << "\n";
        debug.nospace().noquote() << "    - holding  218 | Meter total energy (>= 5.22): " << amtronECU->meterTotoalEnergy() << " [Wh]" << "\n";
        debug.nospace().noquote() << "    - holding  220 | Meter total power (>= 5.22): " << amtronECU->meterTotalPower() << " [W]" << "\n";
        debug.nospace().noquote() << "    - holding  222 | Meter voltage L1 (>= 5.22): " << amtronECU->meterVoltageL1() << " [V]" << "\n";
        debug.nospace().noquote() << "    - holding  224 | Meter voltage L2 (>= 5.22): " << amtronECU->meterVoltageL2() << " [V]" << "\n";
        debug.nospace().noquote() << "    - holding  226 | Meter voltage L3 (>= 5.22): " << amtronECU->meterVoltageL3() << " [V]" << "\n";
        debug.nospace().noquote() << "    - holding  715 | Maximum current limit (>= 5.22): " << amtronECU->maxCurrentLimit() << " [A]" << "\n";
        debug.nospace().noquote() << "    - holding  716 | Charged energy for current session (>= 5.22): " << amtronECU->chargedEnergy() << " [Wh]" << "\n";
    }
    return debug.quote().space();
}

