WolkGateway module for connecting Modbus devices to WolkAbout IoT Platform by communicating with WolkGateway.
Supported protocol(s):
- Wolkabout Protocol
This repository must be cloned from the command line using:
git clone --recurse-submodules https://github.com/Wolkabout/WolkGatewayModule-Modbus.git
Following tools/libraries are required in order to build WolkGateway Module Modbus
- cmake - version 3.5 or later
- autotools
- automake
- autoconf
- g++
- m4
- zlib1g-dev
- libtool
- openssl
Former can be installed on Debian based system from terminal by invoking:
sudo apt-get install automake g++ autotools-dev autoconf m4 zlib1g-dev cmake libtool libssl-dev
Afterwards dependencies are built, and Makefile build system is generated by invoking:
./configure
Generated build system is located inside out
directory To build the module, invoke:
cd out
make -j$(nproc)
sudo make install
Installing will place the application in PATH, and create a systemctl unit, which you can use with:
sudo service modbus_module status/start/stop/restart
The configuration files used are placed in /etc/modbusModule/
, which you should configure before you start your
service. If you don't know how to configure the module, continue on to the next part.
Module configuration consists of 2 configurations files
- moduleConfiguration.json
- devicesConfiguration.json
Below are sections describing each of these configuration files that need to be edited with the parameters of your
modbus devices before running the application. These files are located in out
directory, and are passed to Modbus
module executable in following manner:
./modbusModule moduleConfiguration.json devicesConfiguration.json
Module configuration file contains settings that relate to communication with WolkGateway, and outgoing Modbus connection, reading period time and response timeout.
{
"mqttHost": "tcp://localhost:1883",
// Address of local MQTT broker (connection with WolkGateway)
"connectionType": "SERIAL/RTU",
// Modbus connection type, choice between "SERIAL/RTU" and "TCP/IP"
"tcp/ip": {
// TCP/IP connection properties (ignored if connectionType is "SERIAL/RTU")
"host": "192.168.x.x",
// IP address of Modbus server (mandatory if connectionType is "TCP/IP")
"port": 502
// Port of Modbus server (default is 502, if not stated)
},
"serial/rtu": {
// Serial/RTU connection properties (ignored if connectionType is "TCP/IP")
"serialPort": "SERIAL_PORT",
// Serial port location such as /dev/ttyS0 (mandatory if connection type is "SERIAL/RTU")
"baudRate": 115200,
// Baud rate for serial connection (default is 115200, if not stated)
"dataBits": 8,
// DataBits for Modbus RTU (default is 8, if not stated)
"stopBits": 1,
// StopBits for Modbus RTU (default is 1, if not stated)
"bitParity": "NONE"
// BitParity for Modbus RTU, can be "NONE", "EVEN", "ODD" (default is "NONE", if not stated)
},
"responseTimeoutMs": 200,
// Wait time for respond from slaves/servers (default is 200, if not stated)
"registerReadPeriodMs": 500
// Period of reading all registers/devices (default is 500, if not stated)
}
Devices configuration file contains information necessary to define templates, which include registers that bind to Wolkabout IoT Platform sensors/actuators/alarms/configurations, and then devices, with their information, and a template. This is the guide by which the module will register devices, and then send data for.
You define templates, that are described by their name, and mappings.
{
"templates": [
{
"name": "<TEMPLATE_NAME>",
"mappings": [
// define mappings
]
}
]
}
After doing so, you define all the mappings inside a template.
Single mapping includes one or more Modbus registers that produce a single sensor/actuator/alarm/configuration on a device, on the platform. They're characterized by a name, and a reference key, used to identify them, by their register type (necessary), data type (necessary), operation type (necessary for some output types), and mapping type.
There are optional features you can enable for mappings, such as:
- Unit type
- Deadband and Frequency filtering
- Repeated write
- Default value
- Safe Mode value
- AutoLocalUpdate toggle
- AutoReadAfterWrite toggle
- Read & Write:
COIL
HOLDING_REGISTER
- Read Only:
INPUT_REGISTER
INPUT_CONTACT
- UINT16/INT16
- BOOL
- UINT32/INT32 (with merge operations)
- FLOAT (with merge operation)
- STRING (with stringify operations)
- `MERGE_BIG_ENDIAN` or `MERGE_LITTLE_ENDIAN` (in combination with `"dataType": "UINT32/INT32"`)
- `MERGE_FLOAT_BIG_ENDIAN` or `MERGE_FLOAT_LITTLE_ENDIAN` (in combination with `"dataType": "FLOAT"`)
- `TAKE_BIT` (in combination with `"dataType": "BOOL"`)
- `STRINGIFY_ASCII_BIG_ENDIAN`, `STRINGIFY_ASCII_LITTLE_ENDIAN`, `STRINGIFY_UNICODE_BIG_ENDIAN` or `STRINGIFY_UNICODE_LITTLE_ENDIAN` (in combination with `"dataType": "STRING"`)
You can add a field "unit": "CELSIUS"
to a mapping that will register the feed on the platform with the GUID of the
unit on the platform.
You can add fields "deadBandFilter":0.1
and "frequencyFilterValue":1
to add a deadband and frequency filter to your
mappings.
If this feature is enabled, it will write overwrite the value in the register after some time even if the value has not
changed.
You can add a field "repeat":500
and the value will be overwritten every 500ms.
If you want to set a value that will be written in as soon as the application launches, write it in as the default
value.
You can add a field "defaultValue":123
and it will be written in when the application launches.
If you want a value written into a mapping when the gateway and the module lose connectivity with the platform, you can
write the value here.
You can add a field "safeMode":123
and it will be written in when the communication with the platform is lost.
The software keeps a local copy of the value of all mappings, and the message to the platform about updates is sent when
a new value gets read from the modbus register. By default actuation will write into the register, but not in the local
copy,
so the value will be read in the next read cycle and sent to the platform.
If you want the local copy of mapping values to be updated, set the "autoLocalUpdate":true
for the mapping. This will
result
in a message about the feed to never be sent to the platform, and the platform will just implicitly take it as the
actuation was successful.
On actuation, the module will write the value directly into the registers as needed. But it will also read the same
registers, and notify the platform of value changes if detected - updating the local copy of the values as well.
If you want this behavior to be turned off, set the "autoReadAfterWrite":false
for the mapping. This will disable
the automatic read after writing into a mapping.
{
// Inside of a template
"name": "<TEMPLATE_NAME>",
"mappings": [
{
"name": "mappingName",
// Register name
"reference": "mappingReference",
// Unique reference used to differ register on WolkAbout IoT Platform
"minimum": -32768,
// Minimum value that can be held in register. Required for visualization on WolkAbout IoT Platform
"maximum": 32767,
// Maximum value that can be held in register. Required for visualization on WolkAbout IoT Platform
"address": 0,
// Register address
"registerType": "INPUT_REGISTER",
// Register type - "INPUT_REGISTER" or "HOLDING_REGISTER_ACTUATOR" or "HOLDING_REGISTER_SENSOR" or "INPUT_CONTACT" or "COIL"
"dataType": "INT16"
// Data type stored in register - "INT16" or "UINT16" or "REAL32" for "INPUT_REGISTER"/"HOLDING_REGISTER_ACTUATOR"/"HOLDING_REGISTER_SENSOR" register type, and "BOOL" for "COIL"/"INPUT_CONTACT"
},
{
"name": "mappingName2",
"reference": "mappingReference2",
"minimum": -4000,
"maximum": 4000,
"address": 1,
"registerType": "HOLDING_REGISTER",
"dataType": "FLOAT",
"deadbandValue": 4.0,
// Optional - Indicates a change in value of the mapping that is insignificant data. Applicable to numeric mappings.
"frequencyFilterValue": 250
// Optional - When register changes value often, disregard changes until X milliseconds pass since last accepted value.
},
{
"name": "mappingName3",
"reference": "mappingReference3",
"address": 1,
"registerType": "INPUT_CONTACT",
"dataType": "BOOL"
// This is where for the first time, we override the default Mapping type
},
{
"name": "mappingName4",
"reference": "mappingReference4",
"address": 2,
"registerType": "HOLDING_REGISTER",
"operationType": "MERGE_BIG_ENDIAN",
"dataType": "UINT32"
},
{
"name": "mappingReference5",
"reference": "mappingReference5",
"address": 3,
"registerType": "COIL",
"dataType": "BOOL",
"writeOnly": true
// If the mapping needs to be Write-Only, you put this attribute.
},
{
"name": "mappingReference6",
"reference": "mappingReference6",
"address": 10,
"addressCount": 10,
"registerType": "HOLDING_REGISTER",
"dataType": "STRING",
"operationType": "STRINGIFY_ASCII_BIG_ENDIAN",
"autoLocalUpdate": true,
"autoReadAfterWrite": false
},
{
"name": "mappingReference7",
"reference": "mappingReference7",
"address": 8,
"bitIndex": 0,
"registerType": "INPUT_REGISTER",
"operationType": "TAKE_BIT",
"dataType": "BOOL"
}
]
}
After you completely defined a template, you can list a device.
{
"devices": [
{
"name": "<DEVICE_NAME>",
// User readable device name, necessary to register a device
"key": "<DEVICE_KEY>",
// Unique device key
"slaveAddress": 1,
// Slave address (obligatory if connection type is "SERIAL/RTU"), must be unique in this list
"template": "<TEMPLATE_NAME>"
// Name of defined template
}
// other devices
]
// templates
}
In "TCP/IP" mode, device count is maxed at 1, and you don't need to state a slaveAddress for the device.
If the user happens to enter an invalid template name, the device won't be created. Module will function if at least one device is valid. If there are no devices that have been inputted correctly, the module will exit out, and used will be notified.