From 7fc32167d607cd928b335d93baf5faa446be5164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 5 Apr 2023 12:46:06 +0200 Subject: [PATCH] modbus-tool: Introduce string endianess --- libnymea-modbus/modbusdatautils.cpp | 8 +++- libnymea-modbus/modbusdatautils.h | 4 +- libnymea-modbus/tools/README.md | 14 ++++++ .../tools/connectiontool/toolcommon.py | 4 +- libnymea-modbus/tools/generate-connection.py | 47 +++++++++++++++++++ 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/libnymea-modbus/modbusdatautils.cpp b/libnymea-modbus/modbusdatautils.cpp index d3fea27..be6d61c 100644 --- a/libnymea-modbus/modbusdatautils.cpp +++ b/libnymea-modbus/modbusdatautils.cpp @@ -133,10 +133,12 @@ qint64 ModbusDataUtils::convertToInt64(const QVector ®isters, ByteOr return result; } -QString ModbusDataUtils::convertToString(const QVector ®isters) +QString ModbusDataUtils::convertToString(const QVector ®isters, ByteOrder characterByteOrder) { QByteArray bytes; QDataStream stream(&bytes, QIODevice::WriteOnly); + // Note: some devices use little endian within the register uint16 representation of the 2 characters. + stream.setByteOrder(characterByteOrder == ByteOrderBigEndian ? QDataStream::BigEndian : QDataStream::LittleEndian); for (int i = 0; i < registers.count(); i++) { stream << registers.at(i); } @@ -239,11 +241,13 @@ QVector ModbusDataUtils::convertFromInt64(qint64 value, ByteOrder byteO return values; } -QVector ModbusDataUtils::convertFromString(const QString &value, quint16 stringLength) +QVector ModbusDataUtils::convertFromString(const QString &value, quint16 stringLength, ByteOrder characterByteOrder) { Q_ASSERT_X(value.length() <= stringLength, "ModbusDataUtils", "cannot convert a string which is bigger than the desired register vector."); QByteArray data = value.toLatin1() + QByteArray('\0', stringLength - value.count()); QDataStream stream(&data, QIODevice::ReadOnly); + // Note: some devices use little endian within the register uint16 representation of the 2 characters. + stream.setByteOrder(characterByteOrder == ByteOrderBigEndian ? QDataStream::BigEndian : QDataStream::LittleEndian); QVector values; for (int i = 0; i < stringLength; i++) { quint16 registerValue = 0; diff --git a/libnymea-modbus/modbusdatautils.h b/libnymea-modbus/modbusdatautils.h index f7a4bc6..97ded87 100644 --- a/libnymea-modbus/modbusdatautils.h +++ b/libnymea-modbus/modbusdatautils.h @@ -88,7 +88,7 @@ public: static qint32 convertToInt32(const QVector ®isters, ByteOrder byteOrder = ByteOrderLittleEndian); static quint64 convertToUInt64(const QVector ®isters, ByteOrder byteOrder = ByteOrderLittleEndian); static qint64 convertToInt64(const QVector ®isters, ByteOrder byteOrder = ByteOrderLittleEndian); - static QString convertToString(const QVector ®isters); + static QString convertToString(const QVector ®isters, ByteOrder characterByteOrder = ByteOrderLittleEndian); static float convertToFloat32(const QVector ®isters, ByteOrder byteOrder = ByteOrderLittleEndian); static double convertToFloat64(const QVector ®isters, ByteOrder byteOrder = ByteOrderLittleEndian); @@ -99,7 +99,7 @@ public: static QVector convertFromInt32(qint32 value, ByteOrder byteOrder = ByteOrderLittleEndian); static QVector convertFromUInt64(quint64 value, ByteOrder byteOrder = ByteOrderLittleEndian); static QVector convertFromInt64(qint64 value, ByteOrder byteOrder = ByteOrderLittleEndian); - static QVector convertFromString(const QString &value, quint16 stringLength); + static QVector convertFromString(const QString &value, quint16 stringLength, ByteOrder characterByteOrder = ByteOrderLittleEndian); static QVector convertFromFloat32(float value, ByteOrder byteOrder = ByteOrderLittleEndian); static QVector convertFromFloat64(double value, ByteOrder byteOrder = ByteOrderLittleEndian); }; diff --git a/libnymea-modbus/tools/README.md b/libnymea-modbus/tools/README.md index 4ac0c0a..6752d20 100644 --- a/libnymea-modbus/tools/README.md +++ b/libnymea-modbus/tools/README.md @@ -20,6 +20,7 @@ The basic structure of the modbus register JSON looks like following example: "className": "MyConnection", "protocol": "BOTH", "endianness": "BigEndian", + "stringEndianness": "BigEndian", "errorLimitUntilNotReachable": 10, "checkReachableRegister": "registerPropertyName", "enums": [ @@ -138,6 +139,19 @@ There are 2 possibilities: * `BigEndian`: default if not specified: register bytes come in following order `[0, 1, 2, 3]`: `ABCD` * `LittleEndian`: register bytes come in following order `[0, 1, 2, 3]`: `CDAB` + +## String endianness + +When converting multiple registers to a string, some modbus devices use a different endianess within a register. +One register contains 2 bytes, miltiple registers in a row build up a string. The string endianess tells the generated class how to parse those strings. + +There are 2 possibilities: + +* `BigEndian`: default if not specified: register bytes come in following order `[0, 1], [2, 3]`: `ABCD` +* `LittleEndian`: register bytes come in following order `[1, 0] [3, 2]`: `BADC` + +Please not that the overall endianess of the device does not change the order of the register regarding strings since in modbus a normal register is definded as big endian. Only multiple registers combined to a numeric data type will be taken into account by the `endianess` property. + ## Enums Many modbus devices provide inforation using `Enums`, indicating a special state trough a defined list of values. If a register implements an enum, you can define it in the `enums` section. The `name` property defines the name of the enum, and the script will generate a c++ enum definition from this section. Each enum value will then be generated using ` = `. diff --git a/libnymea-modbus/tools/connectiontool/toolcommon.py b/libnymea-modbus/tools/connectiontool/toolcommon.py index 6db7f65..0eb8b88 100644 --- a/libnymea-modbus/tools/connectiontool/toolcommon.py +++ b/libnymea-modbus/tools/connectiontool/toolcommon.py @@ -277,7 +277,7 @@ def getConversionToValueMethod(registerDefinition): elif registerDefinition['type'] == 'float64': return ('ModbusDataUtils::convertFromFloat64(%s, m_endianness)' % propertyName) elif registerDefinition['type'] == 'string': - return ('ModbusDataUtils::convertFromString(%s)' % propertyName) + return ('ModbusDataUtils::convertFromString(%s, m_stringEndianness)' % propertyName) def getValueConversionMethod(registerDefinition): @@ -334,7 +334,7 @@ def getValueConversionMethod(registerDefinition): elif registerDefinition['type'] == 'float64': return ('ModbusDataUtils::convertToFloat64(values, m_endianness)') elif registerDefinition['type'] == 'string': - return ('ModbusDataUtils::convertToString(values)') + return ('ModbusDataUtils::convertToString(values, m_stringEndianness)') def writeBlockGetMethodDeclarations(fileDescriptor, registerDefinitions): diff --git a/libnymea-modbus/tools/generate-connection.py b/libnymea-modbus/tools/generate-connection.py index 5b1ae97..97d1045 100644 --- a/libnymea-modbus/tools/generate-connection.py +++ b/libnymea-modbus/tools/generate-connection.py @@ -71,6 +71,9 @@ def writeTcpHeaderFile(): writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;') writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);') writeLine(headerFile) + writeLine(headerFile, ' ModbusDataUtils::ByteOrder stringEndianness() const;') + writeLine(headerFile, ' void setStringEndianness(ModbusDataUtils::ByteOrder stringEndianness);') + writeLine(headerFile) writeLine(headerFile, ' uint checkReachableRetries() const;') writeLine(headerFile, ' void setCheckReachableRetries(uint checkReachableRetries);') writeLine(headerFile) @@ -117,6 +120,7 @@ def writeTcpHeaderFile(): writeLine(headerFile, ' void updateFinished();') writeLine(headerFile) writeLine(headerFile, ' void endiannessChanged(ModbusDataUtils::ByteOrder endianness);') + writeLine(headerFile, ' void stringEndiannessChanged(ModbusDataUtils::ByteOrder stringEndianness);') writeLine(headerFile) writePropertyChangedSignals(headerFile, registerJson['registers']) @@ -148,6 +152,7 @@ def writeTcpHeaderFile(): # Private members writeLine(headerFile, 'private:') writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness) + writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_stringEndianness = ModbusDataUtils::ByteOrder%s;' % stringEndianness) writeLine(headerFile, ' quint16 m_slaveId = 1;') writeLine(headerFile) writeLine(headerFile, ' bool m_reachable = false;') @@ -259,6 +264,22 @@ def writeTcpSourceFile(): writeLine(sourceFile, '}') writeLine(sourceFile) + writeLine(sourceFile, 'ModbusDataUtils::ByteOrder %s::stringEndianness() const' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' return m_stringEndianness;') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeLine(sourceFile, 'void %s::setStringEndianness(ModbusDataUtils::ByteOrder stringEndianness)' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' if (m_stringEndianness == stringEndianness)') + writeLine(sourceFile, ' return;') + writeLine(sourceFile,) + writeLine(sourceFile, ' m_stringEndianness = stringEndianness;') + writeLine(sourceFile, ' emit stringEndiannessChanged(m_stringEndianness);') + writeLine(sourceFile, '}') + writeLine(sourceFile) + # Property get methods writePropertyGetSetMethodImplementationsTcp(sourceFile, className, registerJson['registers']) if 'blocks' in registerJson: @@ -445,6 +466,9 @@ def writeRtuHeaderFile(): writeLine(headerFile, ' ModbusDataUtils::ByteOrder endianness() const;') writeLine(headerFile, ' void setEndianness(ModbusDataUtils::ByteOrder endianness);') writeLine(headerFile) + writeLine(headerFile, ' ModbusDataUtils::ByteOrder stringEndianness() const;') + writeLine(headerFile, ' void setStringEndianness(ModbusDataUtils::ByteOrder stringEndianness);') + writeLine(headerFile) # Write registers get method declarations writePropertyGetSetMethodDeclarationsRtu(headerFile, registerJson['registers']) @@ -486,6 +510,7 @@ def writeRtuHeaderFile(): writeLine(headerFile, ' void updateFinished();') writeLine(headerFile) writeLine(headerFile, ' void endiannessChanged(ModbusDataUtils::ByteOrder endianness);') + writeLine(headerFile, ' void stringEndiannessChanged(ModbusDataUtils::ByteOrder stringEndianness);') writeLine(headerFile) writePropertyChangedSignals(headerFile, registerJson['registers']) if 'blocks' in registerJson: @@ -518,6 +543,7 @@ def writeRtuHeaderFile(): writeLine(headerFile, 'private:') writeLine(headerFile, ' ModbusRtuMaster *m_modbusRtuMaster = nullptr;') writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_endianness = ModbusDataUtils::ByteOrder%s;' % endianness) + writeLine(headerFile, ' ModbusDataUtils::ByteOrder m_stringEndianness = ModbusDataUtils::ByteOrder%s;' % stringEndianness) writeLine(headerFile, ' quint16 m_slaveId = 1;') writeLine(headerFile) writeLine(headerFile, ' bool m_reachable = false;') @@ -645,6 +671,21 @@ def writeRtuSourceFile(): writeLine(sourceFile, ' m_endianness = endianness;') writeLine(sourceFile, ' emit endiannessChanged(m_endianness);') writeLine(sourceFile, '}') + + writeLine(sourceFile, 'ModbusDataUtils::ByteOrder %s::stringEndianness() const' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' return m_stringEndianness;') + writeLine(sourceFile, '}') + writeLine(sourceFile) + + writeLine(sourceFile, 'void %s::setStringEndianness(ModbusDataUtils::ByteOrder stringEndianness)' % (className)) + writeLine(sourceFile, '{') + writeLine(sourceFile, ' if (m_stringEndianness == stringEndianness)') + writeLine(sourceFile, ' return;') + writeLine(sourceFile,) + writeLine(sourceFile, ' m_stringEndianness = stringEndianness;') + writeLine(sourceFile, ' emit stringEndiannessChanged(m_stringEndianness);') + writeLine(sourceFile, '}') writeLine(sourceFile) # Property get methods @@ -828,6 +869,11 @@ endianness = 'BigEndian' if 'endianness' in registerJson: endianness = registerJson['endianness'] +stringEndianness = 'BigEndian' +if 'stringEndianness' in registerJson: + stringEndianness = registerJson['stringEndianness'] + + errorLimitUntilNotReachable = 10 if 'errorLimitUntilNotReachable' in registerJson: errorLimitUntilNotReachable = registerJson['errorLimitUntilNotReachable'] @@ -875,6 +921,7 @@ logger.debug('Script path: %s' % scriptPath) logger.debug('Output directory: %s' % outputDirectory) logger.debug('Class name prefix: %s' % classNamePrefix) logger.debug('Endianness: %s' % endianness) +logger.debug('String endianness: %s' % stringEndianness) logger.debug('Error limit until not reachable: %s' % errorLimitUntilNotReachable) logger.debug('Check reachable register: %s' % checkReachableRegister['id'])