#!/usr/bin/env python3 # Copyright (C) 2021 nymea GmbH # # This program 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 2 # of the License, or (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # To lazy to type all those register plugins, let's make live much easier and generate code from a json register definition import os import re import sys import json import shutil import argparse import datetime def convertToAlphaNumeric(text): finalText = '' for character in text: if character.isalnum(): finalText += character else: finalText += ' ' return finalText def splitCamelCase(text): return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', text)).split() def convertToCamelCase(text, capitalize = False): s = convertToAlphaNumeric(text) s = s.replace("-", " ").replace("_", " ") words = s.split() #print('--> words', words) finalWords = [] for i in range(len(words)): camelCaseSplit = splitCamelCase(words[i]) if len(camelCaseSplit) == 0: finalWords.append(words[i]) else: #print('--> camel split words', camelCaseSplit) for j in range(len(camelCaseSplit)): finalWords.append(camelCaseSplit[j]) if len(finalWords) == 0: return text finalText = '' if capitalize: finalText = finalWords[0].capitalize() + ''.join(i.capitalize() for i in finalWords[1:]) else: finalText = finalWords[0].lower() + ''.join(i.capitalize() for i in finalWords[1:]) #print('Convert camel case:', text, '-->', finalText) return finalText def loadJsonFile(filePath): print('--> Loading JSON file', filePath) jsonFile = open(filePath, 'r') return json.load(jsonFile) def writeLine(fileDescriptor, line = ''): fileDescriptor.write(line + '\n') def writeLicenseHeader(fileDescriptor): writeLine(fileDescriptor, '/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* Copyright 2013 - %s, nymea GmbH' % datetime.datetime.now().year) writeLine(fileDescriptor, '* Contact: contact@nymea.io') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* This fileDescriptor is part of nymea.') writeLine(fileDescriptor, '* This project including source code and documentation is protected by') writeLine(fileDescriptor, '* copyright law, and remains the property of nymea GmbH. All rights, including') writeLine(fileDescriptor, '* reproduction, publication, editing and translation, are reserved. The use of') writeLine(fileDescriptor, '* this project is subject to the terms of a license agreement to be concluded') writeLine(fileDescriptor, '* with nymea GmbH in accordance with the terms of use of nymea GmbH, available') writeLine(fileDescriptor, '* under https://nymea.io/license') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* GNU Lesser General Public License Usage') writeLine(fileDescriptor, '* Alternatively, this project may be redistributed and/or modified under the') writeLine(fileDescriptor, '* terms of the GNU Lesser General Public License as published by the Free') writeLine(fileDescriptor, '* Software Foundation; version 3. This project is distributed in the hope that') writeLine(fileDescriptor, '* it will be useful, but WITHOUT ANY WARRANTY; without even the implied') writeLine(fileDescriptor, '* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU') writeLine(fileDescriptor, '* Lesser General Public License for more details.') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* You should have received a copy of the GNU Lesser General Public License') writeLine(fileDescriptor, '* along with this project. If not, see .') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* For any further details and any questions please contact us under') writeLine(fileDescriptor, '* contact@nymea.io or see our FAQ/Licensing Information on') writeLine(fileDescriptor, '* https://nymea.io/license/faq') writeLine(fileDescriptor, '*') writeLine(fileDescriptor, '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */') writeLine(fileDescriptor) def writeEnumDefinition(fileDescriptor, enumDefinition): print('Writing enum', enumDefinition) enumName = enumDefinition['name'] enumValues = enumDefinition['values'] writeLine(fileDescriptor, ' enum %s {' % enumName) for i in range(len(enumValues)): enumData = enumValues[i] line = (' %s%s = %s' % (enumName, enumData['key'], enumData['value'])) if i < (len(enumValues) - 1): line += ',' writeLine(fileDescriptor, line) writeLine(fileDescriptor, ' };') writeLine(fileDescriptor, ' Q_ENUM(%s)' % enumName) writeLine(fileDescriptor) def getCppDataType(registerDefinition, rawType = False): if not rawType: if 'enum' in registerDefinition: return registerDefinition['enum'] if 'scaleFactor' in registerDefinition or 'staticScaleFactor' in registerDefinition: return 'float' if registerDefinition['type'] == 'uint16': return 'quint16' if registerDefinition['type'] == 'int16': return 'qint16' if registerDefinition['type'] == 'uint32': return 'quint32' if registerDefinition['type'] == 'int32': return 'qint32' if registerDefinition['type'] == 'uint64': return 'quint64' if registerDefinition['type'] == 'int64': return 'qint64' if registerDefinition['type'] == 'float': return 'float' if registerDefinition['type'] == 'float64': return 'double' if registerDefinition['type'] == 'string': return 'QString' def getConversionToValueMethod(registerDefinition): # Handle enums propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition, True) if 'enum' in registerDefinition: enumName = registerDefinition['enum'] if registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertFromUInt16(static_cast<%s>(%s))' % (propertyTyp, propertyName)) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertFromInt16(static_cast<%s>(%s))' % (propertyTyp, propertyName)) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertFromUInt32(static_cast<%s>(%s), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, endianness)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertFromInt32(static_cast<%s>(%s), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, endianness)) # Handle scale factors if 'scaleFactor' in registerDefinition: scaleFactorProperty = 'm_%s' % registerDefinition['scaleFactor'] if registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertFromUInt16(static_cast<%s>(%s * 1.0 / pow(10, %s)))' % (propertyTyp, propertyName, scaleFactorProperty)) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertFromInt16(static_cast<%s>(%s * 1.0 / pow(10, %s)))' % (propertyTyp, propertyName, scaleFactorProperty)) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertFromUInt32(static_cast<%s>(%s * 1.0 / pow(10, %s)), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, scaleFactorProperty, endianness)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertFromInt32(static_cast<%s>(%s * 1.0 / pow(10, %s)), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, scaleFactorProperty, endianness)) elif 'staticScaleFactor' in registerDefinition: scaleFactor = registerDefinition['staticScaleFactor'] if registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertFromUInt16(static_cast<%s>(%s * 1.0 / pow(10, %s)))' % (propertyTyp, propertyName, scaleFactor)) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertFromInt16(static_cast<%s>(%s * 1.0 / pow(10, %s)))' % (propertyTyp, propertyName, scaleFactor)) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertFromUInt32(static_cast<%s>(%s * 1.0 / pow(10, %s)), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, scaleFactor, endianness)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertFromInt32(static_cast<%s>(%s * 1.0 / pow(10, %s)), ModbusDataUtils::ByteOrder%s)' % (propertyTyp, propertyName, scaleFactor, endianness)) # Handle default types elif registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertFromUInt16(%s)' % propertyName) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertFromInt16(%s)' % propertyName) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertFromUInt32(%s, ModbusDataUtils::ByteOrder%s)' % (propertyName, endianness)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertFromInt32(%s, ModbusDataUtils::ByteOrder%s)' % (propertyName, endianness)) elif registerDefinition['type'] == 'uint64': return ('ModbusDataUtils::convertFromUInt64(%s, ModbusDataUtils::ByteOrder%s)' % (propertyName, endianness)) elif registerDefinition['type'] == 'int64': return ('ModbusDataUtils::convertFromInt64(%s, ModbusDataUtils::ByteOrder%s)' % (propertyName, endianness)) elif registerDefinition['type'] == 'float': return ('ModbusDataUtils::convertFromFloat32(%s, ModbusDataUtils::ByteOrder%s)' % propertyName, endianness) elif registerDefinition['type'] == 'float64': return ('ModbusDataUtils::convertFromFloat64(%s, ModbusDataUtils::ByteOrder%s)' % propertyName, endianness) elif registerDefinition['type'] == 'string': return ('ModbusDataUtils::convertFromString(%s)' % propertyName) def getValueConversionMethod(registerDefinition): # Handle enums if 'enum' in registerDefinition: enumName = registerDefinition['enum'] if registerDefinition['type'] == 'uint16': return ('static_cast<%s>(ModbusDataUtils::convertToUInt16(values))' % (enumName)) elif registerDefinition['type'] == 'int16': return ('static_cast<%s>(ModbusDataUtils::convertToInt16(values))' % (enumName)) elif registerDefinition['type'] == 'uint32': return ('static_cast<%s>(ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrder%s))' % (enumName, endianness)) elif registerDefinition['type'] == 'int32': return ('static_cast<%s>(ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrder%s))' % (enumName, endianness)) # Handle scale factors if 'scaleFactor' in registerDefinition: scaleFactorProperty = 'm_%s' % registerDefinition['scaleFactor'] if registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, %s)' % (scaleFactorProperty)) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertToInt16(values) * 1.0 * pow(10, %s)' % (scaleFactorProperty)) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrder%s) * 1.0 * pow(10, %s)' % (endianness, scaleFactorProperty)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrder%s) * 1.0 * pow(10, %s)' % (endianness, scaleFactorProperty)) elif 'staticScaleFactor' in registerDefinition: scaleFactor = registerDefinition['staticScaleFactor'] if registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertToUInt16(values) * 1.0 * pow(10, %s)' % (scaleFactor)) elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertToInt16(values) * 1.0 * pow(10, %s)' % (scaleFactor)) elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrder%s) * 1.0 * pow(10, %s)' % (endianness, scaleFactor)) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrder%s) * 1.0 * pow(10, %s)' % (endianness, scaleFactor)) # Handle default types elif registerDefinition['type'] == 'uint16': return ('ModbusDataUtils::convertToUInt16(values)') elif registerDefinition['type'] == 'int16': return ('ModbusDataUtils::convertToInt16(values)') elif registerDefinition['type'] == 'uint32': return ('ModbusDataUtils::convertToUInt32(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'int32': return ('ModbusDataUtils::convertToInt32(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'uint64': return ('ModbusDataUtils::convertToUInt64(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'int64': return ('ModbusDataUtils::convertToInt64(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'float': return ('ModbusDataUtils::convertToFloat32(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'float64': return ('ModbusDataUtils::convertToFloat64(values, ModbusDataUtils::ByteOrder%s)' % endianness) elif registerDefinition['type'] == 'string': return ('ModbusDataUtils::convertToString(values)') def writePropertyGetSetMethodDeclarations(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'])) writeLine(fileDescriptor, ' %s %s() const;' % (propertyTyp, propertyName)) # Check if we require a set method if registerDefinition['access'] == 'RW' or registerDefinition['access'] == 'WO': 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) # Get 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 set method if registerDefinition['access'] == 'RW' or registerDefinition['access'] == 'WO': writeLine(fileDescriptor, 'QModbusReply *%s::set%s(%s %s)' % (className, propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName)) writeLine(fileDescriptor, '{') writeLine(fileDescriptor, ' QVector values = %s;' % getConversionToValueMethod(registerDefinition)) writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Write \\"%s\\" register:" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size'])) if registerDefinition['registerType'] == 'holdingRegister': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, values.count());' % (registerDefinition['address'])) elif registerDefinition['registerType'] == 'coils': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, values.count());' % (registerDefinition['address'])) else: print('Error: invalid register type for writing.') exit(1) writeLine(fileDescriptor, ' request.setValues(values);') writeLine(fileDescriptor, ' return sendWriteRequest(request, m_slaveId);') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writePropertyGetSetMethodImplementationsRtu(fileDescriptor, className, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) # Get 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 set method if registerDefinition['access'] == 'RW' or registerDefinition['access'] == 'WO': writeLine(fileDescriptor, 'ModbusRtuReply *%s::set%s(%s %s)' % (className, propertyName[0].upper() + propertyName[1:], propertyTyp, propertyName)) writeLine(fileDescriptor, '{') writeLine(fileDescriptor, ' QVector values = %s;' % getConversionToValueMethod(registerDefinition)) writeLine(fileDescriptor, ' qCDebug(dc%s()) << "--> Write \\"%s\\" register:" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size'])) if registerDefinition['registerType'] == 'holdingRegister': writeLine(fileDescriptor, ' return m_modbusRtuMaster->writeHoldingRegisters(m_slaveId, %s, values);' % (registerDefinition['address'])) elif registerDefinition['registerType'] == 'coils': writeLine(fileDescriptor, ' return m_modbusRtuMaster->writeCoils(m_slaveId, %s, values);' % (registerDefinition['address'])) else: print('Error: invalid register type for writing.') exit(1) writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writePropertyUpdateMethodDeclarations(fileDescriptor, registerDefinitions): for registerDefinition in registerDefinitions: if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init': continue propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) writeLine(fileDescriptor, ' void update%s();' % (propertyName[0].upper() + propertyName[1:])) def validateBlocks(blockDefinitions): for blockDefinition in blockDefinitions: blockName = blockDefinition['id'] blockRegisters = blockDefinition['registers'] blockStartAddress = 0 registerCount = 0 blockSize = 0 registerAccess = "" registerType = "" for i, blockRegister in enumerate(blockRegisters): if i == 0: blockStartAddress = blockRegister['address'] registerAccess = blockRegister['access'] registerType = blockRegister['registerType'] else: previouseRegisterAddress = blockRegisters[i - 1]['address'] previouseRegisterSize = blockRegisters[i - 1]['size'] previouseRegisterType = blockRegisters[i - 1]['registerType'] if previouseRegisterAddress + previouseRegisterSize != blockRegister['address']: print('Error: block %s has invalid register order in register %s. There seems to be a gap between the registers.' % (blockName, blockRegister['id'])) exit(1) if blockRegister['access'] != registerAccess: print('Error: block %s has inconsistent register access in register %s. The block registers dont seem to have the same access rights.' % (blockName, blockRegister['id'])) exit(1) if blockRegister['registerType'] != registerType: print('Error: block %s has inconsistent register type in register %s. The block registers dont seem to be from the same type.' % (blockName, blockRegister['id'])) exit(1) registerCount += 1 blockSize += blockRegister['size'] print('Define valid block \"%s\" starting at %s with length %s containing %s properties to read.' % (blockName, blockStartAddress, blockSize, registerCount)) def writeBlocksUpdateMethodDeclarations(fileDescriptor, blockDefinitions): for blockDefinition in blockDefinitions: blockName = blockDefinition['id'] blockRegisters = blockDefinition['registers'] # Write the property get / set methods for the block registers writePropertyGetSetMethodDeclarations(fileDescriptor, blockRegisters) blockStartAddress = 0 blockSize = 0 registerCount = 0 for i, blockRegister in enumerate(blockRegisters): if i == 0: blockStartAddress = blockRegister['address'] registerCount += 1 blockSize += blockRegister['size'] # Write the block update method writeLine(fileDescriptor, ' /* Read block from start addess %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, ' void update%sBlock();' % (blockName[0].upper() + blockName[1:])) writeLine(fileDescriptor) def writePropertyUpdateMethodImplementationsTcp(fileDescriptor, className, registerDefinitions): for registerDefinition in registerDefinitions: if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init': continue propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) writeLine(fileDescriptor, 'void %s::update%s()' % (className, propertyName[0].upper() + propertyName[1:])) 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, ' if (reply) {') writeLine(fileDescriptor, ' if (!reply->isFinished()) {') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::NoError) {') writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' const QVector values = unit.values();') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" register" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size'])) writeLine(fileDescriptor, ' %s received%s = %s;' % (propertyTyp, propertyName[0].upper() + propertyName[1:], getValueConversionMethod(registerDefinition))) writeLine(fileDescriptor, ' if (m_%s != received%s) {' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' m_%s = received%s;' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' emit %sChanged(m_%s);' % (propertyName, propertyName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating \\"%s\\" registers from" << hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' emit reply->finished(); // To make sure it will be deleted') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' delete reply; // Broadcast reply returns immediatly') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << hostAddress().toString() << errorString();' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writePropertyUpdateMethodImplementationsRtu(fileDescriptor, className, registerDefinitions): for registerDefinition in registerDefinitions: if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init': continue propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) writeLine(fileDescriptor, 'void %s::update%s()' % (className, propertyName[0].upper() + propertyName[1:])) 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, ' ModbusRtuReply *reply = read%s();' % (propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' if (reply) {') writeLine(fileDescriptor, ' if (!reply->isFinished()) {') writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' if (reply->error() == ModbusRtuReply::NoError) {') writeLine(fileDescriptor, ' QVector values = reply->result();') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from \\"%s\\" register" << %s << "size:" << %s << values;' % (className, registerDefinition['description'], registerDefinition['address'], registerDefinition['size'])) # FIXME: introduce bool and check register type for parsing writeLine(fileDescriptor, ' %s received%s = %s;' % (propertyTyp, propertyName[0].upper() + propertyName[1:], getValueConversionMethod(registerDefinition))) writeLine(fileDescriptor, ' if (m_%s != received%s) {' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' m_%s = received%s;' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' emit %sChanged(m_%s);' % (propertyName, propertyName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "ModbusRtu reply error occurred while updating \\"%s\\" registers" << error << reply->errorString();' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' emit reply->finished();') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers";' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeBlockUpdateMethodImplementationsRtu(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, 'void %s::update%sBlock()' % (className, blockName[0].upper() + blockName[1:])) 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)) # Build request depending on the register type if registerType == 'inputRegister': writeLine(fileDescriptor, ' ModbusRtuReply *reply = m_modbusRtuMaster->readInputRegister(m_slaveId, %s, %s);' % (blockStartAddress, blockSize)) elif registerType == 'discreteInputs': writeLine(fileDescriptor, ' ModbusRtuReply *reply = m_modbusRtuMaster->readDiscreteInput(m_slaveId, %s, %s);' % (blockStartAddress, blockSize)) elif registerType == 'coils': writeLine(fileDescriptor, ' ModbusRtuReply *reply = m_modbusRtuMaster->readCoil(m_slaveId, %s, %s);' % (blockStartAddress, blockSize)) else: #Default to holdingRegister writeLine(fileDescriptor, ' ModbusRtuReply *reply = m_modbusRtuMaster->readHoldingRegister(m_slaveId, %s, %s);' % (blockStartAddress, blockSize)) writeLine(fileDescriptor, ' if (reply) {') writeLine(fileDescriptor, ' if (!reply->isFinished()) {') writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' if (reply->error() == ModbusRtuReply::NoError) {') writeLine(fileDescriptor, ' QVector blockValues = reply->result();') writeLine(fileDescriptor, ' QVector values;') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize)) # Start parsing the registers using offsets offset = 0 for i, blockRegister in enumerate(blockRegisters): propertyName = blockRegister['id'] propertyTyp = getCppDataType(blockRegister) writeLine(fileDescriptor, ' values = blockValues.mid(%s, %s);' % (offset, blockRegister['size'])) writeLine(fileDescriptor, ' %s received%s = %s;' % (propertyTyp, propertyName[0].upper() + propertyName[1:], getValueConversionMethod(blockRegister))) writeLine(fileDescriptor, ' if (m_%s != received%s) {' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' m_%s = received%s;' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' emit %sChanged(m_%s);' % (propertyName, propertyName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) offset += blockRegister['size'] writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &ModbusRtuReply::errorOccurred, this, [reply] (ModbusRtuReply::Error error){') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "ModbusRtu reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) writeLine(fileDescriptor, ' emit reply->finished();') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeBlockUpdateMethodImplementationsTcp(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, 'void %s::update%sBlock()' % (className, blockName[0].upper() + blockName[1:])) 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)) # Build request depending on the register type # Build request depending on the register type if registerType == 'inputRegister': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, %s, %s);' % (blockStartAddress, blockSize)) elif registerType == 'discreteInputs': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, %s, %s);' % (blockStartAddress, blockSize)) elif registerType == 'coils': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, %s);' % (blockStartAddress, blockSize)) else: #Default to holdingRegister writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, %s);' % (blockStartAddress, blockSize)) writeLine(fileDescriptor, ' QModbusReply *reply = sendReadRequest(request, m_slaveId);') writeLine(fileDescriptor, ' if (reply) {') writeLine(fileDescriptor, ' if (!reply->isFinished()) {') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::NoError) {') writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' const QVector blockValues = unit.values();') writeLine(fileDescriptor, ' QVector values;') writeLine(fileDescriptor, ' qCDebug(dc%s()) << "<-- Response from reading block \\"%s\\" register" << %s << "size:" << %s << blockValues;' % (className, blockName, blockStartAddress, blockSize)) # Start parsing the registers using offsets offset = 0 for i, blockRegister in enumerate(blockRegisters): propertyName = blockRegister['id'] propertyTyp = getCppDataType(blockRegister) writeLine(fileDescriptor, ' values = blockValues.mid(%s, %s);' % (offset, blockRegister['size'])) writeLine(fileDescriptor, ' %s received%s = %s;' % (propertyTyp, propertyName[0].upper() + propertyName[1:], getValueConversionMethod(blockRegister))) writeLine(fileDescriptor, ' if (m_%s != received%s) {' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' m_%s = received%s;' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' emit %sChanged(m_%s);' % (propertyName, propertyName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) offset += blockRegister['size'] writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [reply] (QModbusDevice::Error error){') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while updating block \\"%s\\" registers" << error << reply->errorString();' % (className, blockName)) writeLine(fileDescriptor, ' emit reply->finished();') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading block \\"%s\\" registers";' % (className, blockName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeInternalPropertyReadMethodDeclarationsTcp(fileDescriptor, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] writeLine(fileDescriptor, ' QModbusReply *read%s();' % (propertyName[0].upper() + propertyName[1:])) def writeInternalPropertyReadMethodDeclarationsRtu(fileDescriptor, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] writeLine(fileDescriptor, ' ModbusRtuReply *read%s();' % (propertyName[0].upper() + propertyName[1:])) def writeInternalPropertyReadMethodImplementationsTcp(fileDescriptor, className, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] writeLine(fileDescriptor, 'QModbusReply *%s::read%s()' % (className, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, '{') # Build request depending on the register type if registerDefinition['registerType'] == 'inputRegister': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::InputRegisters, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) elif registerDefinition['registerType'] == 'discreteInputs': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::DiscreteInputs, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) elif registerDefinition['registerType'] == 'coils': writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::Coils, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) else: #Default to holdingRegister writeLine(fileDescriptor, ' QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) writeLine(fileDescriptor, ' return sendReadRequest(request, m_slaveId);') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeInternalPropertyReadMethodImplementationsRtu(fileDescriptor, className, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] writeLine(fileDescriptor, 'ModbusRtuReply *%s::read%s()' % (className, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, '{') # Build request depending on the register type if registerDefinition['registerType'] == 'inputRegister': writeLine(fileDescriptor, ' return m_modbusRtuMaster->readInputRegister(m_slaveId, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) elif registerDefinition['registerType'] == 'discreteInputs': writeLine(fileDescriptor, ' return m_modbusRtuMaster->readDiscreteInput(m_slaveId, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) elif registerDefinition['registerType'] == 'coils': writeLine(fileDescriptor, ' return m_modbusRtuMaster->readCoil(m_slaveId, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) else: #Default to holdingRegister writeLine(fileDescriptor, ' return m_modbusRtuMaster->readHoldingRegister(m_slaveId, %s, %s);' % (registerDefinition['address'], registerDefinition['size'])) writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writePropertyChangedSignals(fileDescriptor, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) if propertyTyp == 'QString': writeLine(fileDescriptor, ' void %sChanged(const %s &%s);' % (propertyName, propertyTyp, propertyName)) else: writeLine(fileDescriptor, ' void %sChanged(%s %s);' % (propertyName, propertyTyp, propertyName)) def writePrivatePropertyMembers(fileDescriptor, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) if 'defaultValue' in registerDefinition: writeLine(fileDescriptor, ' %s m_%s = %s;' % (propertyTyp, propertyName, registerDefinition['defaultValue'])) else: writeLine(fileDescriptor, ' %s m_%s;' % (propertyTyp, propertyName)) def writeInitializeMethod(fileDescriptor, className, registerDefinitions): writeLine(fileDescriptor, 'void %s::initialize()' % (className)) writeLine(fileDescriptor, '{') # First check if there are any init registers initRequired = False for registerDefinition in registerDefinitions: if registerDefinition['readSchedule'] == 'init': initRequired = True break if initRequired: # FIXME: distinguish between RTU and TCP writeLine(fileDescriptor, ' QModbusReply *reply = nullptr;') writeLine(fileDescriptor) writeLine(fileDescriptor, ' if (!m_pendingInitReplies.isEmpty()) {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Tried to initialize but there are still some init replies pending.";' % className) writeLine(fileDescriptor, ' return;') writeLine(fileDescriptor, ' }') for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init': writeLine(fileDescriptor) writeLine(fileDescriptor, ' // Read %s' % registerDefinition['description']) writeLine(fileDescriptor, ' reply = read%s();' % (propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' if (reply) {') writeLine(fileDescriptor, ' if (!reply->isFinished()) {') writeLine(fileDescriptor, ' m_pendingInitReplies.append(reply);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, reply, &QModbusReply::deleteLater);') writeLine(fileDescriptor, ' connect(reply, &QModbusReply::finished, this, [this, reply](){') writeLine(fileDescriptor, ' if (reply->error() == QModbusDevice::NoError) {') writeLine(fileDescriptor, ' const QModbusDataUnit unit = reply->result();') writeLine(fileDescriptor, ' %s received%s = %s;' % (propertyTyp, propertyName[0].upper() + propertyName[1:], getValueConversionMethod(registerDefinition))) writeLine(fileDescriptor, ' if (m_%s != received%s) {' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' m_%s = received%s;' % (propertyName, propertyName[0].upper() + propertyName[1:])) writeLine(fileDescriptor, ' emit %sChanged(m_%s);' % (propertyName, propertyName)) writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor) writeLine(fileDescriptor, ' m_pendingInitReplies.removeAll(reply);') writeLine(fileDescriptor, ' verifyInitFinished();') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor) writeLine(fileDescriptor, ' connect(reply, &QModbusReply::errorOccurred, this, [this, reply] (QModbusDevice::Error error){') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Modbus reply error occurred while reading \\"%s\\" registers from" << hostAddress().toString() << error << reply->errorString();' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' emit reply->finished(); // To make sure it will be deleted') writeLine(fileDescriptor, ' });') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' delete reply; // Broadcast reply returns immediatly') writeLine(fileDescriptor, ' }') writeLine(fileDescriptor, ' } else {') writeLine(fileDescriptor, ' qCWarning(dc%s()) << "Error occurred while reading \\"%s\\" registers from" << hostAddress().toString() << errorString();' % (className, registerDefinition['description'])) writeLine(fileDescriptor, ' }') else: writeLine(fileDescriptor, ' // No init registers defined. Nothing to be done and we are finished.') writeLine(fileDescriptor, ' emit initializationFinished();') writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeUpdateMethod(fileDescriptor, className, registerDefinitions): writeLine(fileDescriptor, 'void %s::update()' % (className)) writeLine(fileDescriptor, '{') for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'update': writeLine(fileDescriptor, ' update%s();' % (propertyName[0].upper() + propertyName[1:])) # Add the update block methods if 'blocks' in registerJson: for blockDefinition in registerJson['blocks']: blockName = blockDefinition['id'] writeLine(fileDescriptor, ' update%sBlock();' % (blockName[0].upper() + blockName[1:])) writeLine(fileDescriptor, '}') writeLine(fileDescriptor) def writeRegistersDebugLine(fileDescriptor, debugObjectParamName, registerDefinitions): for registerDefinition in registerDefinitions: propertyName = registerDefinition['id'] propertyTyp = getCppDataType(registerDefinition) line = ('" - %s:" << %s->%s()' % (registerDefinition['description'], debugObjectParamName, propertyName)) if 'unit' in registerDefinition and registerDefinition['unit'] != '': line += (' << " [%s]"' % registerDefinition['unit']) writeLine(fileDescriptor, ' debug.nospace().noquote() << %s << "\\n";' % (line)) def writeTcpHeaderFile(): print('Writing modbus TCP hader file %s' % headerFilePath) headerFile = open(headerFilePath, 'w') writeLicenseHeader(headerFile) writeLine(headerFile, '#ifndef %s_H' % className.upper()) writeLine(headerFile, '#define %s_H' % className.upper()) writeLine(headerFile) writeLine(headerFile, '#include ') writeLine(headerFile) writeLine(headerFile, '#include "../modbus/modbusdatautils.h"') writeLine(headerFile, '#include "../modbus/modbustcpmaster.h"') writeLine(headerFile) # Begin of class writeLine(headerFile, 'class %s : public ModbusTCPMaster' % className) writeLine(headerFile, '{') writeLine(headerFile, ' Q_OBJECT') # Public members writeLine(headerFile, 'public:') # Enum declarations if 'enums' in registerJson: for enumDefinition in registerJson['enums']: writeEnumDefinition(headerFile, enumDefinition) # Constructor writeLine(headerFile, ' explicit %s(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent = nullptr);' % className) writeLine(headerFile, ' ~%s() = default;' % className) writeLine(headerFile) # Write registers get method declarations writePropertyGetSetMethodDeclarations(headerFile, registerJson['registers']) # Write block get/set method declarations writeBlocksUpdateMethodDeclarations(headerFile, registerJson['blocks']) # Write init and update method declarations writeLine(headerFile, ' virtual void initialize();') writeLine(headerFile, ' virtual void update();') writeLine(headerFile) writePropertyUpdateMethodDeclarations(headerFile, registerJson['registers']) writeLine(headerFile) # Write registers value changed signals writeLine(headerFile, 'signals:') writeLine(headerFile, ' void initializationFinished();') writeLine(headerFile) writePropertyChangedSignals(headerFile, registerJson['registers']) for blockDefinition in registerJson['blocks']: writePropertyChangedSignals(headerFile, blockDefinition['registers']) writeLine(headerFile) # Private members writeLine(headerFile, 'private:') writeLine(headerFile, ' quint16 m_slaveId = 1;') writeLine(headerFile, ' QVector m_pendingInitReplies;') writeLine(headerFile) writePrivatePropertyMembers(headerFile, registerJson['registers']) for blockDefinition in registerJson['blocks']: writePrivatePropertyMembers(headerFile, blockDefinition['registers']) writeLine(headerFile) writeLine(headerFile, ' void verifyInitFinished();') writeLine(headerFile) writeInternalPropertyReadMethodDeclarationsTcp(headerFile, registerJson['registers']) writeLine(headerFile) # End of class writeLine(headerFile) writeLine(headerFile, '};') writeLine(headerFile) writeLine(headerFile, 'QDebug operator<<(QDebug debug, %s *%s);' % (className, className[0].lower() + className[1:])) writeLine(headerFile) writeLine(headerFile, '#endif // %s_H' % className.upper()) headerFile.close() def writeTcpSourceFile(): print('Writing modbus TCP source file %s' % sourceFilePath) sourceFile = open(sourceFilePath, 'w') writeLicenseHeader(sourceFile) writeLine(sourceFile) writeLine(sourceFile, '#include "%s"' % headerFileName) writeLine(sourceFile, '#include "loggingcategories.h"') writeLine(sourceFile) writeLine(sourceFile, 'NYMEA_LOGGING_CATEGORY(dc%s, "%s")' % (className, className)) writeLine(sourceFile) # Constructor writeLine(sourceFile, '%s::%s(const QHostAddress &hostAddress, uint port, quint16 slaveId, QObject *parent) :' % (className, className)) writeLine(sourceFile, ' ModbusTCPMaster(hostAddress, port, parent),') writeLine(sourceFile, ' m_slaveId(slaveId)') writeLine(sourceFile, '{') writeLine(sourceFile, ' ') writeLine(sourceFile, '}') writeLine(sourceFile) # Property get methods writePropertyGetSetMethodImplementationsTcp(sourceFile, className, registerJson['registers']) # Block property get methods for blockDefinition in registerJson['blocks']: writePropertyGetSetMethodImplementationsTcp(sourceFile, className, blockDefinition['registers']) # Write init and update method implementation writeInitializeMethod(sourceFile, className, registerJson['registers']) writeUpdateMethod(sourceFile, className, registerJson['registers']) # Write update methods writePropertyUpdateMethodImplementationsTcp(sourceFile, className, registerJson['registers']) # Write block update method writeBlockUpdateMethodImplementationsTcp(sourceFile, className, registerJson['blocks']) # Write property read method implementations writeInternalPropertyReadMethodImplementationsTcp(sourceFile, className, registerJson['registers']) writeLine(sourceFile, 'void %s::verifyInitFinished()' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' if (m_pendingInitReplies.isEmpty()) {') writeLine(sourceFile, ' qCDebug(dc%s()) << "Initialization finished of %s" << hostAddress().toString();' % (className, className)) writeLine(sourceFile, ' emit initializationFinished();') writeLine(sourceFile, ' }') writeLine(sourceFile, '}') writeLine(sourceFile) # Write the debug print debugObjectParamName = className[0].lower() + className[1:] writeLine(sourceFile, 'QDebug operator<<(QDebug debug, %s *%s)' % (className, debugObjectParamName)) writeLine(sourceFile, '{') writeLine(sourceFile, ' debug.nospace().noquote() << "%s(" << %s->hostAddress().toString() << ":" << %s->port() << ")" << "\\n";' % (className, debugObjectParamName, debugObjectParamName)) writeRegistersDebugLine(sourceFile, debugObjectParamName, registerJson['registers']) for blockDefinition in registerJson['blocks']: writeRegistersDebugLine(sourceFile, debugObjectParamName, blockDefinition['registers']) writeLine(sourceFile, ' return debug.quote().space();') writeLine(sourceFile, '}') writeLine(sourceFile) sourceFile.close() ########################################################################################################## def writeRtuHeaderFile(): print('Writing modbus TCP hader file %s' % headerFilePath) headerFile = open(headerFilePath, 'w') writeLicenseHeader(headerFile) writeLine(headerFile, '#ifndef %s_H' % className.upper()) writeLine(headerFile, '#define %s_H' % className.upper()) writeLine(headerFile) writeLine(headerFile, '#include ') writeLine(headerFile) writeLine(headerFile, '#include "../modbus/modbusdatautils.h"') writeLine(headerFile, '#include ') writeLine(headerFile) # Begin of class writeLine(headerFile, 'class %s : public QObject' % className) writeLine(headerFile, '{') writeLine(headerFile, ' Q_OBJECT') # Public members writeLine(headerFile, 'public:') # Enum declarations if 'enums' in registerJson: for enumDefinition in registerJson['enums']: writeEnumDefinition(headerFile, enumDefinition) # Constructor writeLine(headerFile, ' explicit %s(ModbusRtuMaster *modbusRtuMaster, quint16 slaveId, QObject *parent = nullptr);' % className) writeLine(headerFile, ' ~%s() = default;' % className) writeLine(headerFile) writeLine(headerFile, ' ModbusRtuMaster *modbusRtuMaster() const;') writeLine(headerFile, ' quint16 slaveId() const;') writeLine(headerFile) # Write registers get/set method declarations writePropertyGetSetMethodDeclarations(headerFile, registerJson['registers']) # Write block get/set method declarations writeBlocksUpdateMethodDeclarations(headerFile, registerJson['blocks']) writePropertyUpdateMethodDeclarations(headerFile, registerJson['registers']) writeLine(headerFile) # Write init and update method declarations writeLine(headerFile, ' virtual void initialize();') writeLine(headerFile, ' virtual void update();') writeLine(headerFile) # Write registers value changed signals writeLine(headerFile, 'signals:') writeLine(headerFile, ' void initializationFinished();') writeLine(headerFile) writePropertyChangedSignals(headerFile, registerJson['registers']) for blockDefinition in registerJson['blocks']: writePropertyChangedSignals(headerFile, blockDefinition['registers']) writeLine(headerFile) # Private members writeLine(headerFile, 'private:') writeLine(headerFile, ' ModbusRtuMaster *m_modbusRtuMaster = nullptr;') writeLine(headerFile, ' quint16 m_slaveId = 1;') writeLine(headerFile, ' QVector m_pendingInitReplies;') writeLine(headerFile) writePrivatePropertyMembers(headerFile, registerJson['registers']) for blockDefinition in registerJson['blocks']: writePrivatePropertyMembers(headerFile, blockDefinition['registers']) writeLine(headerFile) writeLine(headerFile, ' void verifyInitFinished();') writeLine(headerFile) writeInternalPropertyReadMethodDeclarationsRtu(headerFile, registerJson['registers']) writeLine(headerFile) # End of class writeLine(headerFile) writeLine(headerFile, '};') writeLine(headerFile) writeLine(headerFile, 'QDebug operator<<(QDebug debug, %s *%s);' % (className, className[0].lower() + className[1:])) writeLine(headerFile) writeLine(headerFile, '#endif // %s_H' % className.upper()) headerFile.close() def writeRtuSourceFile(): print('Writing modbus RTU source file %s' % sourceFilePath) sourceFile = open(sourceFilePath, 'w') writeLicenseHeader(sourceFile) writeLine(sourceFile, '#include "%s"' % headerFileName) writeLine(sourceFile, '#include "loggingcategories.h"') writeLine(sourceFile) writeLine(sourceFile, 'NYMEA_LOGGING_CATEGORY(dc%s, "%s")' % (className, className)) writeLine(sourceFile) # Constructor writeLine(sourceFile, '%s::%s(ModbusRtuMaster *modbusRtuMaster, quint16 slaveId, QObject *parent) :' % (className, className)) writeLine(sourceFile, ' QObject(parent),') writeLine(sourceFile, ' m_modbusRtuMaster(modbusRtuMaster),') writeLine(sourceFile, ' m_slaveId(slaveId)') writeLine(sourceFile, '{') writeLine(sourceFile, ' ') writeLine(sourceFile, '}') writeLine(sourceFile) writeLine(sourceFile, 'ModbusRtuMaster *%s::modbusRtuMaster() const' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' return m_modbusRtuMaster;') writeLine(sourceFile, '}') writeLine(sourceFile, 'quint16 %s::slaveId() const' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' return m_slaveId;') writeLine(sourceFile, '}') # Property get methods writePropertyGetSetMethodImplementationsRtu(sourceFile, className, registerJson['registers']) # Block property get methods for blockDefinition in registerJson['blocks']: writePropertyGetSetMethodImplementationsRtu(sourceFile, className, blockDefinition['registers']) # Write init and update method implementation writeInitializeMethod(sourceFile, className, registerJson['registers']) writeUpdateMethod(sourceFile, className, registerJson['registers']) # Write update methods writePropertyUpdateMethodImplementationsRtu(sourceFile, className, registerJson['registers']) # Write block update method writeBlockUpdateMethodImplementationsRtu(sourceFile, className, registerJson['blocks']) # Write property read method implementations writeInternalPropertyReadMethodImplementationsRtu(sourceFile, className, registerJson['registers']) writeLine(sourceFile, 'void %s::verifyInitFinished()' % (className)) writeLine(sourceFile, '{') writeLine(sourceFile, ' if (m_pendingInitReplies.isEmpty()) {') writeLine(sourceFile, ' qCDebug(dc%s()) << "Initialization finished of %s";' % (className, className)) writeLine(sourceFile, ' emit initializationFinished();') writeLine(sourceFile, ' }') writeLine(sourceFile, '}') writeLine(sourceFile) # Write the debug print debugObjectParamName = className[0].lower() + className[1:] writeLine(sourceFile, 'QDebug operator<<(QDebug debug, %s *%s)' % (className, debugObjectParamName)) writeLine(sourceFile, '{') writeLine(sourceFile, ' debug.nospace().noquote() << "%s(" << %s->modbusRtuMaster()->modbusUuid().toString() << ", " << %s->modbusRtuMaster()->serialPort() << ", slave ID:" << %s->slaveId() << ")" << "\\n";' % (className, debugObjectParamName, debugObjectParamName, debugObjectParamName)) writeRegistersDebugLine(sourceFile, debugObjectParamName, registerJson['registers']) for blockDefinition in registerJson['blocks']: writeRegistersDebugLine(sourceFile, debugObjectParamName, blockDefinition['registers']) writeLine(sourceFile, ' return debug.quote().space();') writeLine(sourceFile, '}') writeLine(sourceFile) sourceFile.close() ############################################################################################ # Main ############################################################################################ parser = argparse.ArgumentParser(description='Generate modbus tcp connection class from JSON register definitions file.') parser.add_argument('-j', '--json', metavar='', help='The JSON file containing the register definitions.') parser.add_argument('-o', '--output-directory', metavar='', help='The output directory for the resulting class.') parser.add_argument('-c', '--class-name', metavar='', help='The name of the resulting class.') args = parser.parse_args() registerJson = loadJsonFile(args.json) scriptPath = os.path.dirname(os.path.realpath(sys.argv[0])) outputDirectory = os.path.realpath(args.output_directory) className = args.class_name headerFileName = className.lower() + '.h' sourceFileName = className.lower() + '.cpp' headerFilePath = os.path.join(outputDirectory, headerFileName) sourceFilePath = os.path.join(outputDirectory, sourceFileName) print('Scrip path: %s' % scriptPath) print('Output directory: %s' % outputDirectory) print('Class name: %s' % className) print('Header file: %s' % headerFileName) print('Source file: %s' % sourceFileName) print('Header file path: %s' % headerFilePath) print('Source file path: %s' % sourceFilePath) endianness = 'BigEndian' if 'endianness' in registerJson: endianness = registerJson['endianness'] protocol = 'TCP' if 'protocol' in registerJson: protocol = registerJson['protocol'] validateBlocks(registerJson['blocks']) if protocol == 'TCP': writeTcpHeaderFile() writeTcpSourceFile() else: writeRtuHeaderFile() writeRtuSourceFile()