From 8fe7073025dd4abff7753f94a7d78ef9750a18bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 27 Jul 2022 15:33:45 +0200 Subject: [PATCH] Introduce errorLimitUntilNotReachable property and update README --- bgetech/sdm630-registers.json | 1 + libnymea-modbus/tools/README.md | 46 +++++++++++++------- libnymea-modbus/tools/generate-connection.py | 7 ++- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/bgetech/sdm630-registers.json b/bgetech/sdm630-registers.json index 1bdbabf..6436ff4 100644 --- a/bgetech/sdm630-registers.json +++ b/bgetech/sdm630-registers.json @@ -2,6 +2,7 @@ "className": "Sdm630", "protocol": "RTU", "endianness": "BigEndian", + "errorLimitUntilNotReachable": 15, "registers": [ { "id": "totalCurrentPower", diff --git a/libnymea-modbus/tools/README.md b/libnymea-modbus/tools/README.md index a3fc623..3b5b169 100644 --- a/libnymea-modbus/tools/README.md +++ b/libnymea-modbus/tools/README.md @@ -1,21 +1,15 @@ # 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. +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 entire connection handling and modbus parsing will be covered by the resulting connection class ready to use. -The workflow looks like this: +The basic 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 resulting class will inhert from the `ModbusTCPMaster` class, providing easy access to all possible modbus operations and inform about the connected state. +* Write the `my-registers.json` file containing all register information you are interested to according to this documentation. +* Include the register files into your plugin project file using: `MODBUS_CONNECTIONS += my-registers.json` +* Include the modbus library project include file *after* the connections definition: `include(../modbus.pri)` +* Run qmake on your project. The generated connection classes can be found in the build directory and will be included automatically into your project. +The easiest way to see how to use the generated connection classes is to look at existing implemntations. # JSON format @@ -26,6 +20,7 @@ The basic structure of the modbus register JSON looks like following example: "className": "MyConnection", "protocol": "BOTH", "endianness": "BigEndian", + "errorLimitUntilNotReachable": 10, "enums": [ { "name": "NameOfEnum", @@ -123,6 +118,11 @@ If the modbus device supports both protocols and you want to generate a class fo "protocol": "TCP", ... +# RTU reachable + +For modbus RTU it can be the case that the hardware resource is connected, but the target device is not connected on the serial line or does not respond. For those situations a mechanis has been introduce which will mark a device as not reachable if a certain amount of error occurred in a row without any successfull communication. Both, the RTU hardware resource `connected` state and the communication working state get represented by the `reachable` property of the RTU connection class. + +Depending on your device the amount of errors in a row can vary and can be specified using the `errorLimitUntilNotReachable` property. If not specified, a default of `10` will be assumend. ## Endianness @@ -139,6 +139,22 @@ Many modbus devices provide inforation using `Enums`, indicating a special state 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. + +## Read schedules + +### init + +In most plugins you have the situation where you need to read some registers only once like serialnumbers or product identifiers right after beeing connected or even before you set up the thing in the plugin. + +For this purpose the `initialize()` method has been provided. If you call `initialize()` the connection will start reading all registers and blocks with `"readSchedule": "init"` defined and emits the signal `initializationFinished(bool success)` once all registers and blocks have been read successfully or on the first occured error. If the `success` parameter is `false`, something went wrong during intialization. Since any error will make the initialization process fail, it is important that the `init` registers are *mandatory* on the device. + +This method can also be used to identify the device if implemented properly, or to check if the device has the expected registers available with the given datatype. + +### update + +In order to make the poll process as easy as possible, you can define the `readSchedule` as `update` for all registers and blocks you requier a preiodical update. If you call the `update()` method the connection will start reading all registers and blocks with `"readSchedule": "update"` and the properties will be updated internally. If a property value has changed, the `Changed()` signal will be emitted. If the property has been read (independet if changed or not) the `ReadFinished()` signal will be emitted. + + ## Registers Earch register will be defined as a property in the resulting class modbus TCP class providing easy access to the register data. @@ -156,8 +172,8 @@ Earch register will be defined as a property in the resulting class modbus TCP c * `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. +* `readSchedule`: Optional. Defines when the register needs to be fetched. If no read schedule has been defined, the class will provide only the default access methods, but will not read the value during `initialize()` or `update()` calls. See [#read-schedules](Read schedules) for more information. Possible values are: + * `init`: The register will be fetched during initialization. * `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. diff --git a/libnymea-modbus/tools/generate-connection.py b/libnymea-modbus/tools/generate-connection.py index 72f8a95..51b6fd4 100644 --- a/libnymea-modbus/tools/generate-connection.py +++ b/libnymea-modbus/tools/generate-connection.py @@ -390,7 +390,7 @@ def writeRtuHeaderFile(): writeLine(headerFile, ' quint16 m_slaveId = 1;') writeLine(headerFile, ' bool m_reachable = false;') writeLine(headerFile, ' bool m_communicationWorking = false;') - writeLine(headerFile, ' quint8 m_communicationFailedMax = 10;') + writeLine(headerFile, ' quint8 m_communicationFailedMax = %s;' % (errorLimitUntilNotReachable)) writeLine(headerFile, ' quint8 m_communicationFailedCounter = 0;') writeLine(headerFile) writeLine(headerFile, ' QVector m_pendingInitReplies;') @@ -641,10 +641,15 @@ endianness = 'BigEndian' if 'endianness' in registerJson: endianness = registerJson['endianness'] +errorLimitUntilNotReachable = 10 +if 'errorLimitUntilNotReachable' in registerJson: + errorLimitUntilNotReachable = registerJson['errorLimitUntilNotReachable'] + logger.debug('Scrip path: %s' % scriptPath) logger.debug('Output directory: %s' % outputDirectory) logger.debug('Class name prefix: %s' % classNamePrefix) logger.debug('Endianness: %s' % endianness) +logger.debug('Error limit until not reachable: %s' % errorLimitUntilNotReachable) protocol = 'TCP' if 'protocol' in registerJson: