Skip to main content
Skip table of contents

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:

CODE
{
  "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:

CODE
{
  "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.

JSON
{
  "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:

CODE
{
  "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):

JSON
{
  "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:

CODE
{
  "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

setNumber(string, number) -> void

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 serial_master_slave.X.answer.. This means a field "f" on endpoint 0 will result in the value serial_master_slave.0.answer.f. The value must be a Javascript number type.

uint8(byte[1]) -> number

Read a single byte, interprets it as an unsigned integer and returns it as a number.

int8(byte[1]) -> number

Read a single byte, interprets it as a signed integer and returns it as a number.

uint16(byte[2]) -> number

Read a two byte, interprets them as a big-endian unsigned integer and returns it as a number.

int16(byte[2]) -> number

Read a two byte, interprets them as a big-endian signed integer and returns it as a number.

uint32(byte[4]) -> number

Read a four byte, interprets them as a big-endian unsigned integer and returns it as a number.

int32(byte[4]) -> number

Read a four byte, interprets them as a big-endian signed integer and returns it as a number.

float32(byte[4]) -> number

Read four bytes, interprets them as a big-endian IEEE-754 floating point number and returns this as a number.

round(value, precision) -> number

Returns value rounded to precision decimal places.

Note that you can decode multiple registers in a single script by separating the setNumber function calls with a ;:

CODE
{
  "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
        }
      }
   }
}

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.