Tutorial: setting up your NKE Modbus LoRaWAN device
The NKE Modbus bridge allows connecting Modbus devices over a LoRaWAN connection to the thingsHub.
Note that the guide describes version 2.3 of the driver smartmakers/nke-modbus
. Earlier version of the driver do not support script-based answer decoding.
Setting the serial line parameters
The NKE Modbus akts as a Modbus Master, i.e. it sends out Modbus requests to one or multiple Modbus Slaves. This communicates happens over an RS-485 serial line connection. This connection needs to be configured matchingly for the master and all slaves.
How (and if) a Modbus Slave’s serial line configuration can be set depends on the device’s manifacturer and can be read up on in the device’s manual. The NKE Modbus needs to be configured to match the Slave’s configuration. This needs to be done over the LoRaWAN connection by sending LoRaWAN downlink messages. The thingsHub makes this extemely easy, by provding a declaration configuration interface on the device details page.
The NKE’s serial line configuration is made up of four configuration options, databits
, parity
, speed
, and stopbits
. The precise meaning of these values exceeds the scope of this documentation, the correct settings can be taken from the Slave device documentation. Let’s assume, for the scope of this guide, that the number of databits needs to be 8, the speed should be set to 19200 baud, parity is not used (i.e. neither even nor odd) and there’s only one stop bit used. Then, a valid configuration would look like the following:
{
"serial_interface": {
"databits": 8,
"parity": "none",
"speed": 19200,
"stopbits": 1
}
}
Copy this configuration into the device’s configuration input field end click on “Save Configuration”. The configuration is then converted into four downlink messages, one for each configuration option, and these downlink messages are queued on the network. Once the next uplink arrives from the device (you can speed up this process using the reed contact), the first downlink will be sent to the device. This process needs to repeat four times, i.e. the sensor needs to send four uplink messages, so that the network can respond four times with a configuration downlink.
Configuring a first Modbus request
The first Modbus request can be configured in serial_master_slave.0.request
. If this should be a read request, e.g. reading coils, input registers or holding registers, then this would look like follows:
{
"serial_interface": {
// as above ...
},
"serial_master_slave": {
"0": {
"request": {
"read": {
"address": 19050,
"function": "readInputRegisters",
"number": 2,
"slave": 1
}
}
}
}
}
The request reads 2 input registers starting at register 19050 up to 19051 from the Modbus Slave with ID 1.
Configuring the request’s reporting interval
The request can then be configured to send data at least every 15 minutes and at most every hour, where data will be send earlier of the answer to the Modbus request changes by at least one bit.
{
"serial_interface": {
// as above ...
},
"serial_master_slave": {
"0": {
"request": {
// as above ...
},
"answer": {
"standard_config": {
"maximum_reporting_interval": "1h",
"minimum_reporting_interval": "15m",
"reportable_change": true
}
}
}
}
}
Evaluating the answer
Finally, the answer can be evaluated to turn the byte array of the raw Modbus answer into a proper integer or floating point value which can then be visualized and monitored:
{
"serial_interface": {
// as above ...
},
"serial_master_slave": {
"0": {
"request": {
// as above ...
},
"answer": {
// as above ...
"script": "setNumber(\"f\", uint32(answer.slice(3,7)))",
}
}
}
}
The answer
contains the raw Modbus byte array, e.g. 01030400000878
. The first byte is the slave ID (0x01
), the second byte is the function (0x03
stands for reading multiple holding registers), the third byte is the length of the following payload (0x04
because there are four actual value bytes). Finally, the remaining bytes are the concatenated bytes of the requested registers (0x00000878
). The slice
takes two numbers, the first and one past the last index (indexing starts at zero), i.e. a half-open interval and returns those bytes as another byte array. The function uint32
then interpretes this as an unsigned integer value. Finally, this value is stored in the field f
as a number using the setNumber
function. This implicitly uses the time stamp of the uplink reception for the time stamp of the measurement.
Configuring an additional request
Finally, up to 10 Modbus requests can be configured in the same way (though the serial interface only needs to be configured once):
{
"serial_interface": {
// as above ...
},
"serial_master_slave": {
"0": {
// as above ...
},
"1": {
"answer": {
"script": "setNumber(\"L1_N_V\", round(float32(answer.slice(3,7)), 2))",
"standard_config": {
"maximum_reporting_interval": "1h",
"minimum_reporting_interval": "15m",
"reportable_change": true
}
},
"request": {
"read": {
"address": 19000,
"function": "readInputRegisters",
"number": 2,
"slave": 1
}
}
}
}
}
Note that in this case we’re using float32
instead of uint32
and we’re also rounding the result to 2 places after the comma using the function round
, for better readability.
The final configuration
The final configuration, with all previously discussed options then looks like the following:
{
"serial_interface": {
"databits": 8,
"parity": "none",
"speed": 19200,
"stopbits": 1
},
"serial_master_slave": {
"0": {
"answer": {
"script": "setNumber(\"f\", uint32(answer.slice(3,7)))",
"standard_config": {
"maximum_reporting_interval": "30m",
"minimum_reporting_interval": "30m",
"reportable_change": true
}
},
"request": {
"read": {
"address": 19050,
"function": "readInputRegisters",
"number": 2,
"slave": 1
}
}
},
"1": {
"answer": {
"script": "setNumber(\"L1_N_V\", round(float32(answer.slice(3,7)),2))",
"standard_config": {
"maximum_reporting_interval": "30m",
"minimum_reporting_interval": "30m",
"reportable_change": true
}
},
"request": {
"read": {
"address": 19000,
"function": "readInputRegisters",
"number": 2,
"slave": 1
}
}
}
}
}
Next steps
The following functions are provided by the NKE Modbus driver’s javascript runtime:
Name | Description |
---|---|
| Send an update to the data table with the uplink's time stamp named. The update’s key must be a string and is implicity appended to the path |
| Read a single byte, interprets it as an unsigned integer and returns it as a number. |
| Read a single byte, interprets it as a signed integer and returns it as a number. |
| Read a two byte, interprets them as a big-endian unsigned integer and returns it as a number. |
| Read a two byte, interprets them as a big-endian signed integer and returns it as a number. |
| Read a four byte, interprets them as a big-endian unsigned integer and returns it as a number. |
| Read a four byte, interprets them as a big-endian signed integer and returns it as a number. |
| Read four bytes, interprets them as a big-endian IEEE-754 floating point number and returns this as a number. |
| Returns |
Note that you can decode multiple registers in a single script by separating the setNumber
function calls with a ;
:
{
"serial_master_slave": {
"0": {
"answer": {
"script": "setNumber(\"V1\", round(float32(answer.slice(3,7)),2));setNumber(\"V2\", round(float32(answer.slice(7,11)),2))",
"standard_config": {
"maximum_reporting_interval": "30m",
"minimum_reporting_interval": "30m",
"reportable_change": true
}
}
}
}