diff --git a/modbus/modbusdatautils.cpp b/modbus/modbusdatautils.cpp
new file mode 100644
index 0000000..ba1caea
--- /dev/null
+++ b/modbus/modbusdatautils.cpp
@@ -0,0 +1,231 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#include "modbusdatautils.h"
+
+#include
+
+ModbusDataUtils::ModbusDataUtils()
+{
+
+}
+
+quint16 ModbusDataUtils::convertToUInt16(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 1, "ModbusDataUtils", "invalid raw data size for converting value to quint16");
+ return registers.at(0);
+}
+
+qint16 ModbusDataUtils::convertToInt16(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 1, "ModbusDataUtils", "invalid raw data size for converting value to qint16");
+ return static_cast(registers.at(0));
+}
+
+quint32 ModbusDataUtils::convertToUInt32(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 2, "ModbusDataUtils", "invalid raw data size for converting value to quint32");
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << registers.at(1);
+ inputStream << registers.at(0);
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ quint32 result = 0;
+ outputStream >> result;
+ return result;
+}
+
+qint32 ModbusDataUtils::convertToInt32(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 2, "ModbusDataUtils", "invalid raw data size for converting value to quint32");
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << registers.at(1);
+ inputStream << registers.at(0);
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ qint32 result = 0;
+ outputStream >> result;
+ return result;
+}
+
+quint64 ModbusDataUtils::convertToUInt64(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 4, "ModbusDataUtils", "invalid raw data size for converting value to quint64");
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << registers.at(3);
+ inputStream << registers.at(2);
+ inputStream << registers.at(1);
+ inputStream << registers.at(0);
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ quint64 result = 0;
+ outputStream >> result;
+ return result;
+}
+
+qint64 ModbusDataUtils::convertToInt64(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 4, "ModbusDataUtils", "invalid raw data size for converting value to qint64");
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << registers.at(3);
+ inputStream << registers.at(2);
+ inputStream << registers.at(1);
+ inputStream << registers.at(0);
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ qint64 result = 0;
+ outputStream >> result;
+ return result;
+}
+
+QString ModbusDataUtils::convertToString(const QVector ®isters)
+{
+ QByteArray bytes;
+ QDataStream stream(&bytes, QIODevice::WriteOnly);
+ for (int i = 0; i < registers.count(); i++) {
+ stream << registers.at(i);
+ }
+
+ return QString::fromUtf8(bytes).trimmed();
+}
+
+float ModbusDataUtils::convertToFloat32(const QVector ®isters)
+{
+ Q_ASSERT_X(registers.count() == 2, "ModbusDataUtils", "invalid raw data size for converting value to float32");
+ quint32 rawValue = ModbusDataUtils::convertToUInt32(registers);
+ float value = 0;
+ memcpy(&value, &rawValue, sizeof(quint32));
+ return value;
+}
+
+double ModbusDataUtils::convertToFloat64(const QVector ®isters)
+{
+
+ Q_ASSERT_X(registers.count() == 4, "ModbusDataUtils", "invalid raw data size for converting value to float64");
+ quint64 rawValue = ModbusDataUtils::convertToUInt64(registers);
+ double value = 0;
+ memcpy(&value, &rawValue, sizeof(quint64));
+ return value;
+}
+
+QVector ModbusDataUtils::convertFromUInt16(quint16 value)
+{
+ return QVector() << value;
+}
+
+QVector ModbusDataUtils::convertFromInt16(qint16 value)
+{
+ return ModbusDataUtils::convertFromUInt16(static_cast(value));
+}
+
+QVector ModbusDataUtils::convertFromUInt32(quint32 value)
+{
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << value;
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ QVector values;
+ for (int i = 0; i < 2; i++) {
+ quint16 registerValue = 0;
+ outputStream >> registerValue;
+ values.prepend(registerValue);
+ }
+ return values;
+}
+
+QVector ModbusDataUtils::convertFromInt32(qint32 value)
+{
+ return ModbusDataUtils::convertFromUInt32(static_cast(value));
+}
+
+QVector ModbusDataUtils::convertFromUInt64(quint64 value)
+{
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << value;
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ QVector values;
+ for (int i = 0; i < 4; i++) {
+ quint16 registerValue = 0;
+ outputStream >> registerValue;
+ values.prepend(registerValue);
+ }
+ return values;
+}
+
+QVector ModbusDataUtils::convertFromInt64(qint64 value)
+{
+ QByteArray data;
+ QDataStream inputStream(&data, QIODevice::WriteOnly);
+ inputStream << value;
+
+ QDataStream outputStream(&data, QIODevice::ReadOnly);
+ QVector values;
+ for (int i = 0; i < 4; i++) {
+ quint16 registerValue = 0;
+ outputStream >> registerValue;
+ values.prepend(registerValue);
+ }
+ return values;
+}
+
+QVector ModbusDataUtils::convertFromString(const QString &value, quint16 stringLength)
+{
+ 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);
+ QVector values;
+ for (int i = 0; i < stringLength; i++) {
+ quint16 registerValue = 0;
+ stream >> registerValue;
+ values.append(registerValue);
+ }
+ return values;
+}
+
+QVector ModbusDataUtils::convertFromFloat32(float value)
+{
+ quint32 rawValue = 0;
+ memcpy(&rawValue, &value, sizeof(float));
+ return ModbusDataUtils::convertFromUInt32(rawValue);
+}
+
+QVector ModbusDataUtils::convertFromFloat64(double value)
+{
+ quint64 rawValue = 0;
+ memcpy(&rawValue, &value, sizeof(double));
+ return ModbusDataUtils::convertFromUInt64(rawValue);
+}
diff --git a/modbus/modbusdatautils.h b/modbus/modbusdatautils.h
new file mode 100644
index 0000000..f442dab
--- /dev/null
+++ b/modbus/modbusdatautils.h
@@ -0,0 +1,101 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2021, nymea GmbH
+* Contact: contact@nymea.io
+*
+* This file is part of nymea.
+* This project including source code and documentation is protected by
+* copyright law, and remains the property of nymea GmbH. All rights, including
+* reproduction, publication, editing and translation, are reserved. The use of
+* this project is subject to the terms of a license agreement to be concluded
+* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
+* under https://nymea.io/license
+*
+* GNU Lesser General Public License Usage
+* Alternatively, this project may be redistributed and/or modified under the
+* terms of the GNU Lesser General Public License as published by the Free
+* Software Foundation; version 3. This project is distributed in the hope that
+* it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this project. If not, see .
+*
+* For any further details and any questions please contact us under
+* contact@nymea.io or see our FAQ/Licensing Information on
+* https://nymea.io/license/faq
+*
+* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+#ifndef MODBUSDATAUTILS_H
+#define MODBUSDATAUTILS_H
+
+#include
+#include
+
+class ModbusDataUtils
+{
+ Q_GADGET
+public:
+ enum Access {
+ AccessReadOnly,
+ AccessWriteOnly,
+ AccessReadWrite
+ };
+ Q_ENUM(Access)
+
+ enum DataType {
+ UInt8,
+ UInt16,
+ Uint32,
+ Uint64,
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ Float,
+ Float64,
+ String,
+ Bool
+ };
+ Q_ENUM(DataType)
+
+ typedef struct ModbusRegister {
+ quint16 address;
+ quint16 size;
+ DataType dataType;
+ Access access;
+ QString description;
+ QString unit;
+ QVector rawData;
+ } ModbusRegister;
+
+ typedef QVector ModbusRegisters;
+
+ explicit ModbusDataUtils();
+
+ // Convert to
+ static quint16 convertToUInt16(const QVector ®isters);
+ static qint16 convertToInt16(const QVector ®isters);
+ static quint32 convertToUInt32(const QVector ®isters);
+ static qint32 convertToInt32(const QVector ®isters);
+ static quint64 convertToUInt64(const QVector ®isters);
+ static qint64 convertToInt64(const QVector ®isters);
+ static QString convertToString(const QVector ®isters);
+ static float convertToFloat32(const QVector ®isters);
+ static double convertToFloat64(const QVector ®isters);
+
+ // Convert from
+ static QVector convertFromUInt16(quint16 value);
+ static QVector convertFromInt16(qint16 value);
+ static QVector convertFromUInt32(quint32 value);
+ static QVector convertFromInt32(qint32 value);
+ static QVector convertFromUInt64(quint64 value);
+ static QVector convertFromInt64(qint64 value);
+ static QVector convertFromString(const QString &value, quint16 stringLength);
+ static QVector convertFromFloat32(float value);
+ static QVector convertFromFloat64(double value);
+};
+
+#endif // MODBUSDATAUTILS_H
diff --git a/modbus/tools/README.md b/modbus/tools/README.md
new file mode 100644
index 0000000..b8d4127
--- /dev/null
+++ b/modbus/tools/README.md
@@ -0,0 +1,129 @@
+# Generate a modbus read class
+
+In order to make the plugin development for modbus TCP devices much easier and faster, a small tool has been developed to generate a modbus TCP master based class providing get and set methods for the registers and property changed signals.
+
+The workflow looks like this:
+
+* Write the `registers.json` file containing all register information you are interested to.
+* Run the script and provide the class name, output directory and the path to the JSON file
+* Include the generated class in your plugin, connect the `Changed()` signal and update the thing state within the plugin.
+
+
+The class will provide 2 main methods for fetching information from the modbus device:
+
+* `initialize()` will read all registers with `"readSchedule": "init"` and emits the signal `initializationFinished()` once all replies returned.
+* `update()` can be used to update all registers with `"readSchedule": "update"`. The class will then fetch each register and update the specified value internally. If the value has changed, the `Changed()` signal will be emitted.
+
+The reulting class will inhert from the `ModbusTCPMaster` class, providing easy access to all possible modbus operations and inform about the connected state.
+
+
+# JSON format
+
+The basic structure of the modbus register JSON looks like following example:
+
+```
+{
+ "enums": [
+ {
+ "name": "NameOfEnum",
+ "values": [
+ {
+ "key": "EnumValue1",
+ "value": 0
+ },
+ {
+ "key": "EnumValue2",
+ "value": 1
+ },
+ ....
+ ]
+ }
+ ],
+ "registers": [
+ {
+ "id": "registerPropertyName",
+ "address": 4,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "init",
+ "description": "Description of the register",
+ "unit": "V",
+ "defaultValue": "0",
+ "access": "RO"
+ },
+ {
+ "id": "registerWithEnumValues",
+ "address": 5,
+ "size": 1,
+ "type": "uint16",
+ "readSchedule": "update",
+ "enum": "NameOfEnum",
+ "defaultValue": "NameOfEnumEnumValue1",
+ "description": "Description of the enum register like states",
+ "access": "RO"
+ },
+ ...
+ ]
+}
+
+```
+
+## 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 ` = `.
+
+If a register represets an enum, you simply add the property `"enum": "NameOfEnum"` in the register map and the property will be defined using the resulting enum type. All convertion between enum and resulting modbus register value will be done automatically.
+
+## Registers
+
+Earch register will be defined as a property in the resulting class modbus TCP class providing easy access to the register data.
+
+* `id`: Mandatory. The id defines the name of the property used in the resulting class.
+* `address`: Mandatory. The modbus address of the register.
+* `size`: Mandatory. The amount of registers to read for the property.
+* `type`: Mandatory. The data type of this property. Available data types are:
+ * `uint16` : will be converted to `quint16`
+ * `int16` : will be converted to `qint16`
+ * `uint32` : will be converted to `quint32`
+ * `int32` : will be converted to `qint32`
+ * `uint64` : will be converted to `quint64`
+ * `int64` : will be converted to `qint64`
+ * `float`: will be converted to `float`
+ * `float64`: will be converted to `double`
+ * `string` : will be converted to `QString`
+* `readSchedule`: Optional. Defines when the register needs to be fetched. If no read schedule has been defined, the class will provide only the update methods, but will not read the value during `initialize()` or `update()` calls. Possible values are:
+ * `init`: The register will be fetched during initialization. Once all `init `registers have been fetched, the `initializationFinished()` signal will be emitted.
+ * `update`: The register will be feched each time the `update()` method will be called.
+* `enum`: Optional: If the given data type represents an enum value, this propery can be set to the name of the used enum from the `enum` definition. The class will take care internally about the data convertion from and to the enum values.
+* `description`: Mandatory. A clear description of the register.
+* `unit`: Optional. Represents the unit of this register value.
+* `registerType`: Optional. Represents the type of the register and how to read/write it. Default is `holdingRegister`. Possible values are:
+ * `holdingRegister`
+ * `inputRegister`
+ * `coils`
+ * `discreteInputs`
+* `access`: Mandatory. Describes the access to this register. Possible valies are:
+ * `RO`: Read only access. Only the get method and the changed singal will be defined.
+ * `RW`: Read and write access. Also a set mehtod will be defined.
+ * `WO`: Write only. Only the set method will be defined.
+* `scaleFactor`: Optional. The name of the scale factor register to convert this value to float. `floatValue = intValue * 10^scaleFactor value`. The scale factor value is normally a `int16` value, i.e. -10 or 10
+* `staticScaleFactor`: Optional. Use this static scale factor to convert this register value to float. `floatValue = registerValue * 10^staticScaleFactor`. The scale factor value is normally a `int16` value, i.e. -10 or 10
+* `defaultValue`: Optional. The value for initializing the property.
+
+# Example
+
+Change into your plugin sub directory.
+Assuming you wrote the registers.json file you can run now following command to generate your modbus class:
+
+`$ python3 ../modbus/tools/generate-connection.py -j registers.json -o . -c MyModbusConnection`
+
+You the result will be a header and a source file called:
+
+* `mymodbusconnection.h`
+* `mymodbusconnection.cpp`
+
+You can include this class in your project and provide one connection per thing.
+
+
+
+
diff --git a/modbus/tools/generate-connection.py b/modbus/tools/generate-connection.py
new file mode 100644
index 0000000..f6be585
--- /dev/null
+++ b/modbus/tools/generate-connection.py
@@ -0,0 +1,557 @@
+#!/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):
+ if 'enum' in registerDefinition:
+ return registerDefinition['enum']
+
+ if 'scaleFactor' 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 getValueConversionMethod(registerDefinition):
+ if 'enum' in registerDefinition:
+ enumName = registerDefinition['enum']
+ if registerDefinition['type'] == 'uint16':
+ return ('static_cast<%s>(ModbusDataUtils::convertToUInt16(unit.values()))' % (enumName))
+ elif registerDefinition['type'] == 'int16':
+ return ('static_cast<%s>(ModbusDataUtils::convertToInt16(unit.values()))' % (enumName))
+ elif registerDefinition['type'] == 'uint32':
+ return ('static_cast<%s>(ModbusDataUtils::convertToUInt32(unit.values()))' % (enumName))
+ elif registerDefinition['type'] == 'int32':
+ return ('static_cast<%s>(ModbusDataUtils::convertToInt32(unit.values()))' % (enumName))
+
+ if 'scaleFactor' in registerDefinition:
+ scaleFactorProperty = 'm_%s' % registerDefinition['scaleFactor']
+ if registerDefinition['type'] == 'uint16':
+ return ('ModbusDataUtils::convertToUInt16(unit.values()) * 1.0 * pow(10, %s)' % (scaleFactorProperty))
+ elif registerDefinition['type'] == 'int16':
+ return ('ModbusDataUtils::convertToInt16(unit.values()) * 1.0 * pow(10, %s)' % (scaleFactorProperty))
+ elif registerDefinition['type'] == 'uint32':
+ return ('ModbusDataUtils::convertToUInt32(unit.values()) * 1.0 * pow(10, %s)' % (scaleFactorProperty))
+ elif registerDefinition['type'] == 'int32':
+ return ('ModbusDataUtils::convertToInt32(unit.values()) * 1.0 * pow(10, %s)' % (scaleFactorProperty))
+
+
+ elif registerDefinition['type'] == 'uint16':
+ return ('ModbusDataUtils::convertToUInt16(unit.values())')
+ elif registerDefinition['type'] == 'int16':
+ return ('ModbusDataUtils::convertToInt16(unit.values())')
+ elif registerDefinition['type'] == 'uint32':
+ return ('ModbusDataUtils::convertToUInt32(unit.values())')
+ elif registerDefinition['type'] == 'int32':
+ return ('ModbusDataUtils::convertToInt32(unit.values())')
+ elif registerDefinition['type'] == 'uint64':
+ return ('ModbusDataUtils::convertToUInt64(unit.values())')
+ elif registerDefinition['type'] == 'int64':
+ return ('ModbusDataUtils::convertToInt64(unit.values())')
+ elif registerDefinition['type'] == 'float':
+ return ('ModbusDataUtils::convertToFloat32(unit.values())')
+ elif registerDefinition['type'] == 'float64':
+ return ('ModbusDataUtils::convertToFloat64(unit.values())')
+ elif registerDefinition['type'] == 'string':
+ return ('ModbusDataUtils::convertToString(unit.values())')
+
+
+def writePropertyGetMethodDeclarations(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))
+ writeLine(fileDescriptor)
+
+
+def writePropertyGetMethodImplementations(fileDescriptor, className, registerDefinitions):
+ for registerDefinition in registerDefinitions:
+ propertyName = registerDefinition['id']
+ propertyTyp = getCppDataType(registerDefinition)
+ 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)
+
+
+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 writePropertyUpdateMethodImplementations(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, ' 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, ' %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 writeInternalPropertyReadMethodDeclarations(fileDescriptor, registerDefinitions):
+ for registerDefinition in registerDefinitions:
+ propertyName = registerDefinition['id']
+ writeLine(fileDescriptor, ' QModbusReply *read%s();' % (propertyName[0].upper() + propertyName[1:]))
+
+
+def writeInternalPropertyReadMethodImplementations(fileDescriptor, className, registerDefinitions):
+ for registerDefinition in registerDefinitions:
+ propertyName = registerDefinition['id']
+ writeLine(fileDescriptor, 'QModbusReply *%s::read%s()' % (className, propertyName[0].upper() + propertyName[1:]))
+ writeLine(fileDescriptor, '{')
+ 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 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, '{')
+
+ 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, ' }')
+ writeLine(fileDescriptor)
+
+ for registerDefinition in registerDefinitions:
+ propertyName = registerDefinition['id']
+ propertyTyp = getCppDataType(registerDefinition)
+
+ if 'readSchedule' in registerDefinition and registerDefinition['readSchedule'] == 'init':
+ 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, ' }')
+ writeLine(fileDescriptor)
+
+ writeLine(fileDescriptor, ' ')
+ 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:]))
+
+ writeLine(fileDescriptor, '}')
+ writeLine(fileDescriptor)
+
+
+############################################################################################
+# 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)
+
+#############################################################################
+# Write header file
+#############################################################################
+
+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
+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
+writePropertyGetMethodDeclarations(headerFile, registerJson['registers'])
+
+# 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'])
+writeLine(headerFile)
+
+# Private members
+writeLine(headerFile, 'private:')
+writeLine(headerFile, ' quint16 m_slaveId = 1;')
+writeLine(headerFile, ' QVector m_pendingInitReplies;')
+writeLine(headerFile)
+writePrivatePropertyMembers(headerFile, registerJson['registers'])
+writeLine(headerFile)
+writeLine(headerFile, ' void verifyInitFinished();')
+writeLine(headerFile)
+writeInternalPropertyReadMethodDeclarations(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()
+
+
+
+#############################################################################
+# Write source file
+#############################################################################
+
+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
+writePropertyGetMethodImplementations(sourceFile, className, registerJson['registers'])
+
+# Write init and update method implementation
+writeInitializeMethod(sourceFile, className, registerJson['registers'])
+writeUpdateMethod(sourceFile, className, registerJson['registers'])
+
+# Write update methods
+writePropertyUpdateMethodImplementations(sourceFile, className, registerJson['registers'])
+
+# Write property read method implementations
+writeInternalPropertyReadMethodImplementations(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))
+for registerDefinition in registerJson['registers']:
+ 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(sourceFile, ' debug.nospace().noquote() << %s << "\\n";' % (line))
+
+writeLine(sourceFile, ' return debug.quote().space();')
+writeLine(sourceFile, '}')
+writeLine(sourceFile)
+
+sourceFile.close()
\ No newline at end of file