Modbus Driver

Modbus is a data communications protocol originally published by Modicon (now Schneider Electric) in 1979 for use with its programmable logic controllers (PLCs). Modbus has become a de facto standard communication protocol and is now a commonly available means of connecting industrial electronic devices.

Modbus is popular in industrial environments because it is openly published and royalty-free. It was developed for industrial applications, is relatively easy to deploy and maintain compared to other standards, and places few restrictions on the format of the data to be transmitted.

Requirements

Since Modbus is a client/server (formerly master/slave) protocol, the client node must routinely poll each field device and look for changes in the data.

The Core Services of the Edge Compute Platform need to have been installed.

The following services need to be installed on the edge in order to use the Modbus Driver:
  • ase-modbus-driver

These services can be deployed by adding the "App - ECP Modbus Driver" application from the Marketplace to your own space (see "Edge Ops > Studio > Marketplace").

Figure 1.


Objective

The goal is to create a "Modbus device driver" to communicate with modbus server devices to control and gather telemetry. Only Modbus TCP/IP version is considered for implementation.

Details

Next object types provided by a Modbus server device to a Modbus client device:
  • Coil (Read-write, 1 bit)
  • Discrete input (Read-only, 1 bit)
  • Input register (Read-only, 16 bits)
  • Holding register (Read-write, 16 bits)

Each type has its own address space (0-0xFFFF). Server can have coil and input register with the same address. User should know available objects and their meaning for specific server. More complex data types (float, int32) can be built on top of registers and it is next level responsibility to handle/display it properly.

Thing Description

Example:
{
  "@type": [
    "swx:MBSlave"
  ],
  "id": "/things/mb_slave1",
  "title": "Modbus server #1",
  "properties": {
    "ip": {
      "title": "IP address",
      "description": "Device (slave) IP address",
      "type": "string",
      "readOnly": false
    },
    "port": {
      "title": "Port",
      "description": "Device (listening) port",
      "type": "integer",
      "minimum": 1,
      "maximum": 65525,
      "readOnly": false
    },
    "slaveid": {
      "title": "Slave Id",
      "description": "Id of slave device (may be omitted for TCP)",
      "type": "integer",
      "minimum": 1,
      "maximum": 65535,
      "readOnly": false
    },
    "pollperiod": {
      "title": "Poll Period",
      "description": "Data will be retrieved every <pollperiod> seconds, 0 - disabled",
      "type": "integer",
      "minimum": 0,
      "maximum": 7200,
      "readOnly": false
    },
    "input-status,1": {
      "title": "Input Conatct",
      "description": "Binary input (addr=1)",
      "type": "integer",
      "minimum": 0,
      "maximum": 1,
      "readOnly": true
    },
    "coil,1": {
      "title": "Output Coil",
      "description": "Binary output (addr=1)",
      "type": "integer",
      "minimum": 0,
      "maximum": 1,
      "readOnly": false
    },
    "input-register,33": {
      "title": "Input Register",
      "description": "Input register (addr=33)",
      "type": "integer",
      "minimum": 0,
      "maximum": 65535,
      "readOnly": true
    },
    "holding-register,44": {
      "title": "Output register",
      "description": "Output register (addr=44)",
      "type": "integer",
      "minimum": 0,
      "maximum": 65535,
      "readOnly": false
    }
  },
  "actions": {
    "readvalue": {
      "title": "Read Value",
      "description": "Reads input/output coil/register value at address <address>",
      "input": {
        "@type": "ValueAddress",
        "type": "object",
        "properties": {
          "address": {
            "type": "integer",
            "minimum": 0,
            "maximum": 65535
          },
          "type": {
            "type": "string"
          }
        }
      }
    }
  }
}

Important properties that define modbus server are: ip, port - IP address and port of modbus server. slaveid can be used if server requires it (mostly ignored in case of modbus TCP/IP).

pollinterval allows periodically poll data for each data object (global property, not per data object). If value is 0 - polling is disabled, the only way to get the value is readvalue action.

The rest of the properties represent modbus objects available on the server.

Property name looks like <type>,<address>, (for example "coil,123" ).

Possible types are:
  • input-status - discrete input
  • coil - output coil
  • input-register
  • holding-register
  • address - any from 0-65535 range

Since data can be only acquired by polling, readvalue action is implemented for reading. Example of parameters (type, address limitations are the same as for property name):

Action input example:
{
  "readvalue": {
    "input": {
      "type": "coil",
      "address": 1
    }
  }
}

Extended Functionality of the Modbus Driver

Standard Modbus uses IEC 61131 addressing where only 16-bit registers are used. Installers can choose to use the 16-bit addresses to store 1-bit ("bool") and 32-bit ("real") variables as described below.

Note: This extended functionality is experimental; it does not follow the standard specification.

In case a single bit of a 16-bit register is used like a "bool" variable; these are stored in one of the 16 bit areas of the 16-bit "word memory" address.

In case two 16-bit registers are combined into a 32-bit "real" variable; these are stored in two consecutive 16-bit "word memory" addresses which are combined using "little endian" ordering by default. Big endian ordering is configurable, as well as byte swap; see below for details.

For these two cases, new "types" have been introduced:

  • bit-register - which is used in property names with the bit area appended, for example "bit-register,141,10"
  • real-register – which is used in property names with the first address only, the second one is assumed consecutive, for example "real-register,240"

The ordering of the "real" variable can be configured using one of the following options are part of the "@type" of the property: "swx:LittleEndian (default), "swx:BigEndian", "swx:LittleEndianByteSwap", or "swx:BigEndianByteSwap".

For example:
"real-register,240": {
  "@type": [
    "swx:BigEndian"
  ],
  "description": "Real Example",
  "readOnly": false,
  "title": "Real-example",
  "type": "number"
},