# 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/>.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

from .toolcommon import * 

##############################################################

def writePropertyGetSetDataUnitDeclarationsTcp(fileDescriptor, registerDefinitions):
    for registerDefinition in registerDefinitions:
        propertyName = registerDefinition['id']
        propertyTyp = getCppDataType(registerDefinition)
        if 'unit' in registerDefinition and registerDefinition['unit'] != '':
            writeLine(fileDescriptor, '    /* %s [%s] - Address: %s, Size: %s */' % (registerDefinition['description'], registerDefinition['unit'], registerDefinition['address'], registerDefinition['size']))
        else:
            writeLine(fileDescriptor, '    /* %s - Address: %s, Size: %s */' % (registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))

        # Check if we require a read method
        if 'R' in registerDefinition['access']:
            writeLine(fileDescriptor, '    QModbusDataUnit %sDataUnit() const;' % (propertyName))

        # Check if we require a write method
        if 'W' in registerDefinition['access']:
            writeLine(fileDescriptor, '    QModbusDataUnit set%sDataUnit(%s %s);' % (propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName))

        writeLine(fileDescriptor)


def writePropertyGetSetDataUnitImplementationsTcp(fileDescriptor, className, registerDefinitions):
    for registerDefinition in registerDefinitions:
        propertyName = registerDefinition['id']
        propertyTyp = getCppDataType(registerDefinition)

        # Check if we require a read method
        if 'R' in registerDefinition['access']:

            writeLine(fileDescriptor, 'QModbusDataUnit %s::%sDataUnit() const' % (className, propertyName))
            writeLine(fileDescriptor, '{')

            # Build request depending on the register type
            if registerDefinition['registerType'] == 'inputRegister':
                writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, %s, %s);' % (registerDefinition['address'], registerDefinition['size']))
            elif registerDefinition['registerType'] == 'discreteInputs':
                writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, %s, %s);' % (registerDefinition['address'], registerDefinition['size']))
            elif registerDefinition['registerType'] == 'coils':
                writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, %s);' % (registerDefinition['address'], registerDefinition['size']))
            else:
                #Default to holdingRegister
                writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, %s);' % (registerDefinition['address'], registerDefinition['size']))

            writeLine(fileDescriptor, '}')
            writeLine(fileDescriptor)

        # Check if we require a write method
        if 'W' in registerDefinition['access']:
            writeLine(fileDescriptor, 'QModbusDataUnit %s::set%sDataUnit(%s %s)' % (className, propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName))
            writeLine(fileDescriptor, '{')

            writeLine(fileDescriptor, '    QVector<quint16> values = %s;' % getConversionToValueMethod(registerDefinition))
            if registerDefinition['registerType'] == 'holdingRegister':
                writeLine(fileDescriptor, '    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, values.length());' % (registerDefinition['address']))
            elif registerDefinition['registerType'] == 'coils':
                writeLine(fileDescriptor, '    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, values.length());' % (registerDefinition['address']))
            else:
                logger.warning('Error: invalid register type for writing.')
                exit(1)

            writeLine(fileDescriptor, '    request.setValues(values);')

            writeLine(fileDescriptor, '    return request;')
            writeLine(fileDescriptor, '}')
            writeLine(fileDescriptor)


##############################################################

def writePropertyGetSetMethodDeclarationsTcp(fileDescriptor, registerDefinitions):
    for registerDefinition in registerDefinitions:
        propertyName = registerDefinition['id']
        propertyTyp = getCppDataType(registerDefinition)
        if 'unit' in registerDefinition and registerDefinition['unit'] != '':
            writeLine(fileDescriptor, '    /* %s [%s] - Address: %s, Size: %s */' % (registerDefinition['description'], registerDefinition['unit'], registerDefinition['address'], registerDefinition['size']))
        else:
            writeLine(fileDescriptor, '    /* %s - Address: %s, Size: %s */' % (registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))

        # Check if we require a read method
        if 'R' in registerDefinition['access']:
            writeLine(fileDescriptor, '    %s %s() const;' % (propertyTyp, propertyName))

        # Check if we require a write method
        if 'W' in registerDefinition['access']:
            writeLine(fileDescriptor, '    QModbusReply *set%s(%s %s);' % (propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName))

        writeLine(fileDescriptor)


def writePropertyGetSetMethodImplementationsTcp(fileDescriptor, className, registerDefinitions):
    for registerDefinition in registerDefinitions:
        propertyName = registerDefinition['id']
        propertyTyp = getCppDataType(registerDefinition)

        # Check if we require a read method
        if 'R' in registerDefinition['access']:
            if 'enum' in registerDefinition:
                writeLine(fileDescriptor, '%s::%s %s::%s() const' % (className, propertyTyp, className, propertyName))
            else:
                writeLine(fileDescriptor, '%s %s::%s() const' % (propertyTyp, className, propertyName))

            writeLine(fileDescriptor, '{')
            writeLine(fileDescriptor, '    return m_%s;' % propertyName)
            writeLine(fileDescriptor, '}')
            writeLine(fileDescriptor)

        # Check if we require a write method
        if 'W' in registerDefinition['access']:
            writeLine(fileDescriptor, 'QModbusReply *%s::set%s(%s %s)' % (className, propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName))
            writeLine(fileDescriptor, '{')
            writeLine(fileDescriptor, '    return m_modbusTcpMaster->sendWriteRequest(set%sDataUnit(%s), m_slaveId);' % (propertyName[0].upper() + propertyName[1:], propertyName))
            writeLine(fileDescriptor, '}')
            writeLine(fileDescriptor)


##############################################################

def writeInternalPropertyReadMethodDeclarationsTcp(fileDescriptor, registerDefinitions):
    for registerDefinition in registerDefinitions:
        if 'R' in registerDefinition['access']:
            propertyName = registerDefinition['id']
            writeLine(fileDescriptor, '    QModbusReply *read%s();' % (propertyName[0].upper() + propertyName[1:]))

##############################################################

def writeInternalPropertyReadMethodImplementationsTcp(fileDescriptor, className, registerDefinitions):
    for registerDefinition in registerDefinitions:
        if 'R' in registerDefinition['access']:
            propertyName = registerDefinition['id']
            writeLine(fileDescriptor, 'QModbusReply *%s::read%s()' % (className, propertyName[0].upper() + propertyName[1:]))
            writeLine(fileDescriptor, '{')
            writeLine(fileDescriptor, '    return m_modbusTcpMaster->sendReadRequest(%sDataUnit(), m_slaveId);' % propertyName)
            writeLine(fileDescriptor, '}')
            writeLine(fileDescriptor)


##############################################################

def writeInternalBlockReadDataUnitDeclarationsTcp(fileDescriptor, blockDefinitions):
    for blockDefinition in blockDefinitions:
        blockName = blockDefinition['id']
        blockRegisters = blockDefinition['registers']
        blockStartAddress = 0
        registerCount = 0
        blockSize = 0

        for i, blockRegister in enumerate(blockRegisters):
            if i == 0:
                blockStartAddress = blockRegister['address']

            registerCount += 1
            blockSize += blockRegister['size']

        writeLine(fileDescriptor, '    /* Data unit describing read block request from start address %s with size of %s registers containing following %s properties:' % (blockStartAddress, blockSize, registerCount))
        for i, registerDefinition in enumerate(blockRegisters):
            if 'unit' in registerDefinition and registerDefinition['unit'] != '':
                writeLine(fileDescriptor, '     - %s [%s] - Address: %s, Size: %s' % (registerDefinition['description'], registerDefinition['unit'], registerDefinition['address'], registerDefinition['size']))
            else:
                writeLine(fileDescriptor, '     - %s - Address: %s, Size: %s' % (registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
        writeLine(fileDescriptor, '    */' )
        writeLine(fileDescriptor, '    QModbusDataUnit readBlock%sDataUnit() const;' % (blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor)


##############################################################

def writeInternalBlockReadDataUnitImplementationsTcp(fileDescriptor, className, blockDefinitions):
    for blockDefinition in blockDefinitions:
        blockName = blockDefinition['id']
        blockRegisters = blockDefinition['registers']
        blockStartAddress = 0
        registerCount = 0
        blockSize = 0
        registerType = ""

        for i, blockRegister in enumerate(blockRegisters):
            if i == 0:
                blockStartAddress = blockRegister['address']
                registerType = blockRegister['registerType']

            registerCount += 1
            blockSize += blockRegister['size']


        writeLine(fileDescriptor, 'QModbusDataUnit %s::readBlock%sDataUnit() const' % (className, blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor, '{')

        # Build request depending on the register type
        if registerType == 'inputRegister':
            writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, %s, %s);' % (blockStartAddress, blockSize))
        elif registerType == 'discreteInputs':
            writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, %s, %s);' % (blockStartAddress, blockSize))
        elif registerType == 'coils':
            writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, %s);' % (blockStartAddress, blockSize))
        else:
            #Default to holdingRegister
            writeLine(fileDescriptor, '    return QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, %s);' % (blockStartAddress, blockSize))

        writeLine(fileDescriptor, '}')
        writeLine(fileDescriptor)


##############################################################

def writeInternalBlockReadMethodDeclarationsTcp(fileDescriptor, blockDefinitions):
    for blockDefinition in blockDefinitions:
        blockName = blockDefinition['id']
        blockRegisters = blockDefinition['registers']
        blockStartAddress = 0
        registerCount = 0
        blockSize = 0

        for i, blockRegister in enumerate(blockRegisters):
            if i == 0:
                blockStartAddress = blockRegister['address']
                registerType = blockRegister['registerType']

            registerCount += 1
            blockSize += blockRegister['size']

        writeLine(fileDescriptor, '    /* Read block from start address %s with size of %s registers containing following %s properties:' % (blockStartAddress, blockSize, registerCount))
        for i, registerDefinition in enumerate(blockRegisters):
            if 'unit' in registerDefinition and registerDefinition['unit'] != '':
                writeLine(fileDescriptor, '     - %s [%s] - Address: %s, Size: %s' % (registerDefinition['description'], registerDefinition['unit'], registerDefinition['address'], registerDefinition['size']))
            else:
                writeLine(fileDescriptor, '     - %s - Address: %s, Size: %s' % (registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
        writeLine(fileDescriptor, '    */' )
        writeLine(fileDescriptor, '    QModbusReply *readBlock%s();' % (blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor)

##############################################################

def writeInternalBlockReadMethodImplementationsTcp(fileDescriptor, className, blockDefinitions):
    for blockDefinition in blockDefinitions:
        blockName = blockDefinition['id']
        writeLine(fileDescriptor, 'QModbusReply *%s::readBlock%s()' % (className, blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor, '{')
        writeLine(fileDescriptor, '    return m_modbusTcpMaster->sendReadRequest(readBlock%sDataUnit(), m_slaveId);' % (blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor, '}')
        writeLine(fileDescriptor)


##############################################################

def writePropertyUpdateMethodImplementationsTcp(fileDescriptor, className, registerDefinitions, queuedRequests, queuedRequestsDelay):
    for registerDefinition in registerDefinitions:
        
        if 'access' in registerDefinition:
            if not 'R' in registerDefinition['access']:
                continue


        propertyName = registerDefinition['id']
        writeLine(fileDescriptor, 'void %s::update%s()' % (className, propertyName[0].upper() + propertyName[1:]))
        writeLine(fileDescriptor, '{')

        if queuedRequests:
            if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
                writeLine(fileDescriptor, '    if (m_currentInitReply)')
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    // Update registers from %s' % registerDefinition['description'])
                writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
                writeLine(fileDescriptor, '    QModbusReply *reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (!reply) {')
                writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" init register from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '        finishInitialization(false);')
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                writeLine(fileDescriptor, '        if (!verifyInitFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedInitRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    m_currentInitReply = reply;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        m_currentInitReply = nullptr;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                writeLine(fileDescriptor, '            QModbusResponse response = reply->rawResult();')
                writeLine(fileDescriptor, '            if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                writeLine(fileDescriptor, '                qCWarning(dc%s()) << "Modbus reply error occurred while updating init \\"%s\\" registers" << reply->error() << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '            } else {')
                writeLine(fileDescriptor, '                qCWarning(dc%s()) << "Modbus reply error occurred while updating init \\"%s\\" registers" << reply->error() << reply->errorString();' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '            }')
                writeLine(fileDescriptor, '            finishInitialization(false);')
                writeLine(fileDescriptor, '            return;')
                writeLine(fileDescriptor, '        }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                writeLine(fileDescriptor, '        process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (!verifyInitFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedInitRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    });')
                writeLine(fileDescriptor)
            else:
                writeLine(fileDescriptor, '    if (m_currentUpdateReply)')
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    // Update registers from %s' % registerDefinition['description'])
                writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
                writeLine(fileDescriptor, '    QModbusReply *reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (!reply) {')
                writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '        if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                writeLine(fileDescriptor, '        if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    m_currentUpdateReply = reply;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        m_currentUpdateReply = nullptr;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::NoError) {')
                writeLine(fileDescriptor, '            const QModbusDataUnit unit = reply->result();')
                writeLine(fileDescriptor, '            process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
                writeLine(fileDescriptor, '        }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    });')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){')
                writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '        } else {')
                writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description']))
                writeLine(fileDescriptor, '        }')
                writeLine(fileDescriptor, '    });')
        else:
            writeLine(fileDescriptor, '    // Update registers from %s' % registerDefinition['description'])
            writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
            writeLine(fileDescriptor, '    QModbusReply *reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
            writeLine(fileDescriptor, '    if (!reply) {')
            writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();' % (className, registerDefinition['description']))
            writeLine(fileDescriptor, '        return;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    if (reply->isFinished()) {')
            writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
            writeLine(fileDescriptor, '        return;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
            writeLine(fileDescriptor, '        handleModbusError(reply->error());')
            writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::NoError) {')
            writeLine(fileDescriptor, '            const QModbusDataUnit unit = reply->result();')
            writeLine(fileDescriptor, '            process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
            writeLine(fileDescriptor, '        }')
            writeLine(fileDescriptor, '    });')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){')
            writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
            writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
            writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description']))
            writeLine(fileDescriptor, '        } else {')
            writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description']))
            writeLine(fileDescriptor, '        }')
            writeLine(fileDescriptor, '    });')
 
        writeLine(fileDescriptor, '}')
        writeLine(fileDescriptor)

##############################################################

def writeBlockUpdateMethodImplementationsTcp(fileDescriptor, className, blockDefinitions, queuedRequests, queuedRequestsDelay):
    for blockDefinition in blockDefinitions:
        blockName = blockDefinition['id']
        blockRegisters = blockDefinition['registers']
        blockStartAddress = 0
        registerCount = 0
        blockSize = 0

        for i, blockRegister in enumerate(blockRegisters):
            if i == 0:
                blockStartAddress = blockRegister['address']

            registerCount += 1
            blockSize += blockRegister['size']

        writeLine(fileDescriptor, 'void %s::update%sBlock()' % (className, blockName[0].upper() + blockName[1:]))
        writeLine(fileDescriptor, '{')

        if queuedRequests:
            
            if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'init':
                writeLine(fileDescriptor, '    if (m_currentInitReply)')
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    // Update register block \"%s\"' % blockName)
                writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
                writeLine(fileDescriptor, '    QModbusReply *reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
                writeLine(fileDescriptor, '    if (!reply) {')
                writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading init block \\"%s\\" registers";' % (className, blockName))
                writeLine(fileDescriptor, '        finishInitialization(false);')
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                writeLine(fileDescriptor, '        if (!verifyInitFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedInitRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    m_currentInitReply = reply;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        m_currentInitReply = nullptr;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                writeLine(fileDescriptor, '            QModbusResponse response = reply->rawResult();')
                writeLine(fileDescriptor, '            if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                writeLine(fileDescriptor, '                qCWarning(dc%s()) << "Modbus reply error occurred while updating init block \\"%s\\" registers" << reply->error() << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName))
                writeLine(fileDescriptor, '            } else {')
                writeLine(fileDescriptor, '                qCWarning(dc%s()) << "Modbus reply error occurred while updating init block \\"%s\\" registers" << reply->error() << reply->errorString();' % (className, blockName))
                writeLine(fileDescriptor, '            }')
                writeLine(fileDescriptor, '            finishInitialization(false);')
                writeLine(fileDescriptor, '            return;')
                writeLine(fileDescriptor, '        }') 
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        const QVector<quint16> blockValues = unit.values();')
                writeLine(fileDescriptor, '        processBlock%sRegisterValues(blockValues);' % (blockName[0].upper() + blockName[1:]))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (!verifyInitFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedInitRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    });')
                writeLine(fileDescriptor)
            else:

                writeLine(fileDescriptor, '    // Update register block \"%s\"' % blockName)
                writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
                writeLine(fileDescriptor, '    QModbusReply *reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
                writeLine(fileDescriptor, '    if (!reply) {')
                writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
                writeLine(fileDescriptor, '        if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                writeLine(fileDescriptor, '        if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '            QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        return;')
                writeLine(fileDescriptor, '    }')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    m_currentUpdateReply = reply;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        m_currentUpdateReply = nullptr;')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::NoError) {')
                writeLine(fileDescriptor, '            const QModbusDataUnit unit = reply->result();')
                writeLine(fileDescriptor, '            const QVector<quint16> blockValues = unit.values();')
                writeLine(fileDescriptor, '            processBlock%sRegisterValues(blockValues);' % (blockName[0].upper() + blockName[1:]))
                writeLine(fileDescriptor, '            if (!verifyUpdateFinished())')
                writeLine(fileDescriptor, '                QTimer::singleShot(%s, this, &%s::sendNextQueuedRequest);' % (queuedRequestsDelay, className))
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '        }')
                writeLine(fileDescriptor, '    });')
                writeLine(fileDescriptor)
                writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){')
                writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName))
                writeLine(fileDescriptor, '        } else {')
                writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName))
                writeLine(fileDescriptor, '        }')
                writeLine(fileDescriptor, '    });')
        else:
            writeLine(fileDescriptor, '    // Update register block \"%s\"' % blockName)
            writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
            writeLine(fileDescriptor, '    QModbusReply *reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
            writeLine(fileDescriptor, '    if (!reply) {')
            writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
            writeLine(fileDescriptor, '        return;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    if (reply->isFinished()) {')
            writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
            writeLine(fileDescriptor, '        return;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
            writeLine(fileDescriptor, '        handleModbusError(reply->error());')
            writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::NoError) {')
            writeLine(fileDescriptor, '            const QModbusDataUnit unit = reply->result();')
            writeLine(fileDescriptor, '            const QVector<quint16> blockValues = unit.values();')
            writeLine(fileDescriptor, '            processBlock%sRegisterValues(blockValues);' % (blockName[0].upper() + blockName[1:]))
            writeLine(fileDescriptor, '        }')
            writeLine(fileDescriptor, '    });')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){')
            writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
            writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
            writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName))
            writeLine(fileDescriptor, '        } else {')
            writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName))
            writeLine(fileDescriptor, '        }')
            writeLine(fileDescriptor, '    });')

        writeLine(fileDescriptor, '}')
        writeLine(fileDescriptor)

##############################################################

def writeTestReachabilityImplementationsTcp(fileDescriptor, className, registerDefinitions, checkReachableRegister):

    propertyName = checkReachableRegister['id']

    writeLine(fileDescriptor, 'void %s::testReachability()' % (className))
    writeLine(fileDescriptor, '{')
    writeLine(fileDescriptor, '    if (m_checkReachableReply)')
    writeLine(fileDescriptor, '        return;')
    writeLine(fileDescriptor)
    writeLine(fileDescriptor, '    // Try to read the check reachability register %s in order to verify if the communication is working or not.' % checkReachableRegister['id'])
    writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Test reachability by reading \\"%s\\" register:" << %s << "size:" << %s;' % (className, checkReachableRegister['description'], checkReachableRegister['address'], checkReachableRegister['size']))
    writeLine(fileDescriptor, '    m_checkReachableReply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
    writeLine(fileDescriptor, '    if (!m_checkReachableReply) {')
    writeLine(fileDescriptor, '        qCDebug(dc%s()) << "Error occurred verifying reachability by reading \\"%s\\" register";' % (className, checkReachableRegister['description']))
    writeLine(fileDescriptor, '        onReachabilityCheckFailed();')
    writeLine(fileDescriptor, '        return;')
    writeLine(fileDescriptor, '    }')
    writeLine(fileDescriptor)
    writeLine(fileDescriptor, '    if (m_checkReachableReply->isFinished()) {')
    writeLine(fileDescriptor, '        m_checkReachableReply->deleteLater(); // Broadcast reply returns immediately')
    writeLine(fileDescriptor, '        m_checkReachableReply = nullptr;')
    writeLine(fileDescriptor, '        onReachabilityCheckFailed();')
    writeLine(fileDescriptor, '        return;')
    writeLine(fileDescriptor, '    }')
    writeLine(fileDescriptor)
    writeLine(fileDescriptor, '    connect(m_checkReachableReply, &QModbusReply::finished, this, [this](){')
    writeLine(fileDescriptor, '        handleModbusError(m_checkReachableReply->error());')
    writeLine(fileDescriptor, '        if (m_checkReachableReply->error() != QModbusDevice::NoError)')
    writeLine(fileDescriptor, '            onReachabilityCheckFailed();')
    writeLine(fileDescriptor)
    writeLine(fileDescriptor, '        m_checkReachableReply->deleteLater();')
    writeLine(fileDescriptor, '        m_checkReachableReply = nullptr;')
    writeLine(fileDescriptor, '    });')
    writeLine(fileDescriptor)
    writeLine(fileDescriptor, '    connect(m_checkReachableReply, &QModbusReply::errorOccurred, this, [this] (QModbusDevice::Error error){')
    writeLine(fileDescriptor, '        QModbusResponse response = m_checkReachableReply->rawResult();')
    writeLine(fileDescriptor, '        if (m_checkReachableReply->error() == QModbusDevice::ProtocolError && response.isException()) {')
    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkReachableReply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, checkReachableRegister['description']))
    writeLine(fileDescriptor, '            // Note: if we get an exception on the reachability register, the modbus server is probably not ready')
    writeLine(fileDescriptor, '            // For some reasons on some devices the reply will never be finished on exception response. A reconnect might fix it.')
    writeLine(fileDescriptor, '            QTimer::singleShot(2000, m_modbusTcpMaster, &ModbusTcpMaster::reconnectDevice);')
    writeLine(fileDescriptor, '        } else {')
    writeLine(fileDescriptor, '            qCDebug(dc%s()) << "Modbus reply error occurred while verifying reachability by reading \\"%s\\" register" << error << m_checkReachableReply->errorString();' % (className, checkReachableRegister['description']))
    writeLine(fileDescriptor, '        }')
    writeLine(fileDescriptor, '    });')
    writeLine(fileDescriptor, '}')
    writeLine(fileDescriptor)

##############################################################

def writeInitMethodImplementationTcp(fileDescriptor, className, registerDefinitions, blockDefinitions, queuedRequests):
    writeLine(fileDescriptor, 'bool %s::initialize()' % (className))
    writeLine(fileDescriptor, '{')
    writeLine(fileDescriptor, '    if (!m_reachable) {')
    writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Tried to initialize but the device is not to be reachable.";' % className)
    writeLine(fileDescriptor, '        return false;')
    writeLine(fileDescriptor, '    }')

    # First check if there are any init registers
    initRequired = False
    for registerDefinition in registerDefinitions:
        if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
            initRequired = True
            break

    for blockDefinition in blockDefinitions:
        if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'init':
            initRequired = True
            break

    if initRequired:

        if queuedRequests:
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    if (!m_modbusTcpMaster->connected()) {')
            writeLine(fileDescriptor, '        m_initRequestQueue.clear();')
            writeLine(fileDescriptor, '        return false;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    m_initializing = true;')
            writeLine(fileDescriptor)

            # Read individual registers
            for registerDefinition in registerDefinitions:
                propertyName = registerDefinition['id']
                
                if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
                    writeLine(fileDescriptor, '    enqueueInitRequest(&%s::update%s);' % (className, propertyName[0].upper() + propertyName[1:]))

            # Read init blocks
            writeLine(fileDescriptor)
            for blockDefinition in blockDefinitions:
                blockName = blockDefinition['id']
                if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'init':
                    writeLine(fileDescriptor, '    enqueueInitRequest(&%s::update%sBlock);' % (className, blockName[0].upper() + blockName[1:]))

            writeLine(fileDescriptor, '    sendNextQueuedInitRequest();');

        else:
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    if (m_initObject) {')
            writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Tried to initialize but the init process is already running.";' % className)
            writeLine(fileDescriptor, '        return false;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    m_initializing = true;')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    // Parent object for the init process')
            writeLine(fileDescriptor, '    m_initObject = new QObject(this);')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    QModbusReply *reply = nullptr;')

            # Read individual registers
            for registerDefinition in registerDefinitions:
                propertyName = registerDefinition['id']

                if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    // Read %s' % registerDefinition['description'])
                    writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read init \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
                    writeLine(fileDescriptor, '    reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
                    writeLine(fileDescriptor, '    if (!reply) {')
                    writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        finishInitialization(false);')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                    writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    m_pendingInitReplies.append(reply);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){')
                    writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                    writeLine(fileDescriptor, '        m_pendingInitReplies.removeAll(reply);')
                    writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                    writeLine(fileDescriptor, '            finishInitialization(false);')
                    writeLine(fileDescriptor, '            return;')
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                    writeLine(fileDescriptor, '        process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
                    writeLine(fileDescriptor, '        verifyInitFinished();')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, m_initObject, [this, reply] (QModbusDevice::Error error){')
                    writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                    writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        } else {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor, '    });')

            # Read init blocks
            for blockDefinition in blockDefinitions:
                blockName = blockDefinition['id']
                blockRegisters = blockDefinition['registers']

                if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'init':
                    blockStartAddress = 0
                    registerCount = 0
                    blockSize = 0
                    registerType = ""

                    for i, blockRegister in enumerate(blockRegisters):
                        if i == 0:
                            blockStartAddress = blockRegister['address']
                            registerType = blockRegister['registerType']

                        registerCount += 1
                        blockSize += blockRegister['size']

                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    // Read %s' % blockName)
                    writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read init block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
                    writeLine(fileDescriptor, '    reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
                    writeLine(fileDescriptor, '    if (!reply) {')
                    writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
                    writeLine(fileDescriptor, '        finishInitialization(false);')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                    writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    m_pendingInitReplies.append(reply);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, m_initObject, [this, reply](){')
                    writeLine(fileDescriptor, '        m_pendingInitReplies.removeAll(reply);')
                    writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                    writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                    writeLine(fileDescriptor, '            finishInitialization(false);')
                    writeLine(fileDescriptor, '            return;')
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                    writeLine(fileDescriptor, '        const QVector<quint16> blockValues = unit.values();')
                    writeLine(fileDescriptor, '        processBlock%sRegisterValues(blockValues);' % (blockName[0].upper() + blockName[1:]))
                    writeLine(fileDescriptor, '        verifyInitFinished();')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, m_initObject, [reply] (QModbusDevice::Error error){')
                    writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                    writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName))
                    writeLine(fileDescriptor, '        } else {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName))
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)

    else:
        writeLine(fileDescriptor, '    // No init registers defined. Nothing to be done and we are finished.')
        writeLine(fileDescriptor, '    m_initializing = false;')
        writeLine(fileDescriptor, '    emit initializationFinished(true);')

    writeLine(fileDescriptor, '    return true;')
    writeLine(fileDescriptor, '}')
    writeLine(fileDescriptor)

##############################################################

def writeUpdateMethodTcp(fileDescriptor, className, registerDefinitions, blockDefinitions, queuedRequests):
    writeLine(fileDescriptor, 'bool %s::update()' % (className))
    writeLine(fileDescriptor, '{')

    # First check if there are any update registers
    updateRequired = False
    for registerDefinition in registerDefinitions:
        if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update':
            updateRequired = True
            break

    for blockDefinition in blockDefinitions:  
        if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'update':
            updateRequired = True
            break

    if updateRequired:
        if queuedRequests:
            writeLine(fileDescriptor, '    if (!m_modbusTcpMaster->connected()) {')
            writeLine(fileDescriptor, '        m_updateRequestQueue.clear();')
            writeLine(fileDescriptor, '        return false;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)

            writeLine(fileDescriptor, '    if (!m_updateRequestQueue.isEmpty()) {')
            writeLine(fileDescriptor, '        qCDebug(dc%s()) << "Tried to update but there are still some update requests pending. Waiting for them to be finished..." << m_updateRequestQueue.length();' % className)
            writeLine(fileDescriptor, '        sendNextQueuedRequest();')
            writeLine(fileDescriptor, '        return true;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)

            # Read individual registers
            for registerDefinition in registerDefinitions:
                propertyName = registerDefinition['id']
                
                if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update':
                    writeLine(fileDescriptor, '    enqueueRequest(&%s::update%s);' % (className, propertyName[0].upper() + propertyName[1:]))

            # Read init blocks
            writeLine(fileDescriptor)
            for blockDefinition in blockDefinitions:
                blockName = blockDefinition['id']
                if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'update':
                    writeLine(fileDescriptor, '    enqueueRequest(&%s::update%sBlock);' % (className, blockName[0].upper() + blockName[1:]))

            writeLine(fileDescriptor, '    sendNextQueuedRequest();');
        else:
            writeLine(fileDescriptor, '    if (!m_modbusTcpMaster->connected())')
            writeLine(fileDescriptor, '        return false;')
            writeLine(fileDescriptor)

            writeLine(fileDescriptor, '    if (!m_pendingUpdateReplies.isEmpty()) {')
            writeLine(fileDescriptor, '        qCDebug(dc%s()) << "Tried to update but there are still some update replies pending. Waiting for them to be finished...";' % className)
            writeLine(fileDescriptor, '        return true;')
            writeLine(fileDescriptor, '    }')
            writeLine(fileDescriptor)
            writeLine(fileDescriptor, '    QModbusReply *reply = nullptr;')

            # Read individual registers
            for registerDefinition in registerDefinitions:
                propertyName = registerDefinition['id']
                propertyTyp = getCppDataType(registerDefinition)

                if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update':
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    // Read %s' % registerDefinition['description'])
                    writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read \\"%s\\" register:" << %s << "size:" << %s;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size']))
                    writeLine(fileDescriptor, '    reply = read%s();' % (propertyName[0].upper() + propertyName[1:]))
                    writeLine(fileDescriptor, '    if (!reply) {')
                    writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << m_modbusTcpMaster->errorString();' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                    writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    m_pendingUpdateReplies.append(reply);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                    writeLine(fileDescriptor, '        m_pendingUpdateReplies.removeAll(reply);')
                    writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                    writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                    writeLine(fileDescriptor, '            verifyUpdateFinished();')
                    writeLine(fileDescriptor, '            return;')
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                    writeLine(fileDescriptor, '        process%sRegisterValues(unit.values());' % (propertyName[0].upper() + propertyName[1:]))
                    writeLine(fileDescriptor, '        verifyUpdateFinished();')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){')
                    writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                    writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        } else {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << m_modbusTcpMaster->hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description']))
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor, '    });')

            # Read init blocks
            for blockDefinition in blockDefinitions:
                blockName = blockDefinition['id']
                blockRegisters = blockDefinition['registers']

                if 'readSchedule' in blockDefinition and blockDefinition['readSchedule'] == 'update':
                    blockStartAddress = 0
                    registerCount = 0
                    blockSize = 0
                    registerType = ""

                    for i, blockRegister in enumerate(blockRegisters):
                        if i == 0:
                            blockStartAddress = blockRegister['address']
                            registerType = blockRegister['registerType']

                        registerCount += 1
                        blockSize += blockRegister['size']

                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    // Read %s' % blockName)
                    writeLine(fileDescriptor, '    reply = readBlock%s();' % (blockName[0].upper() + blockName[1:]))
                    writeLine(fileDescriptor, '    qCDebug(dc%s()) << "--> Read block \\"%s\\" registers from:" << %s << "size:" << %s;' % (className, blockName, blockStartAddress, blockSize))
                    writeLine(fileDescriptor, '    if (!reply) {')
                    writeLine(fileDescriptor, '        qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName))
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    if (reply->isFinished()) {')
                    writeLine(fileDescriptor, '        reply->deleteLater(); // Broadcast reply returns immediately')
                    writeLine(fileDescriptor, '        return false;')
                    writeLine(fileDescriptor, '    }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    m_pendingUpdateReplies.append(reply);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);')
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::finished, this, [this, reply](){')
                    writeLine(fileDescriptor, '        m_pendingUpdateReplies.removeAll(reply);')
                    writeLine(fileDescriptor, '        handleModbusError(reply->error());')
                    writeLine(fileDescriptor, '        if (reply->error() != QModbusDevice::NoError) {')
                    writeLine(fileDescriptor, '            verifyUpdateFinished();')
                    writeLine(fileDescriptor, '            return;')
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '        const QModbusDataUnit unit = reply->result();')
                    writeLine(fileDescriptor, '        const QVector<quint16> blockValues = unit.values();')
                    writeLine(fileDescriptor, '        processBlock%sRegisterValues(blockValues);' % (blockName[0].upper() + blockName[1:]))
                    writeLine(fileDescriptor, '        verifyUpdateFinished();')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)
                    writeLine(fileDescriptor, '    connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){')
                    writeLine(fileDescriptor, '        QModbusResponse response = reply->rawResult();')
                    writeLine(fileDescriptor, '        if (reply->error() == QModbusDevice::ProtocolError && response.isException()) {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString() << ModbusDataUtils::exceptionCodeToString(response.exceptionCode());' % (className, blockName))
                    writeLine(fileDescriptor, '        } else {')
                    writeLine(fileDescriptor, '            qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName))
                    writeLine(fileDescriptor, '        }')
                    writeLine(fileDescriptor, '    });')
                    writeLine(fileDescriptor)

    else:
        writeLine(fileDescriptor, '    // No update registers defined. Nothing to be done and we are finished.')
        writeLine(fileDescriptor, '    emit updateFinished();')

    writeLine(fileDescriptor, '    return true;')
    writeLine(fileDescriptor, '}')
    writeLine(fileDescriptor)
