Modbus Reference

Modbus is a reasonably old protocol but is popular for enabling communications between PLCs and other industrial equipment, and is one of the protocols supported for connecting to your asset in Octave (see Modbus Guides for instructions).

A Modbus system consists of one client that initiates communication with multiple servers, each of which are accessed by unique addresses. It uses either a multi-drop serial bus (RTU) over RS485 or RS232, or a socket-based TCP over IP connection. Secondary RTU buses can be connected to TCP servers for extending the bus over IP.

πŸ“˜

Note

Octave Edge Devices running firmware 3.4.0 and above can monitor up to 128 RTU assets. Lower firmware versions support up to 32 RTU assets.
Modbus over Ethernet: 4 assets are supported

Registers

The Modbus protocol uses a simple register read/write architecture. A register can be one of four types:

  • Coil, Read-write 1 bit
  • Discrete input, Read-only 1 bit
  • Input register, Read-only 16 bits
  • Holding register Read-write 16 bits

Registers are only 16-bits wide and therefore this requires some manipulation when using more complex variables, such as floats and structures. For example, 32-bit floats are typically distributed across two 16-bit registers. Also, data which is smaller than 16-bits is sometimes packed into a single register with other data. For example, a 6-bit sensor value may occupy 6 of the 16 bits in a register and the remaining 10 are used for something else.

πŸ“˜

Maximum number of registers

The maximum number of consecutive registers that can be read in a single register group is 121. The maximum number of register groups is 1024 (cumulative total across all servers).

Additional Modbus Information

For more information on the Modbus protocol, see:

Octave Modbus Overview

An asset can interact with Octave by sending and receiving information through an Octave edge device such as an AirLink FX30S over Modbus to one or more servers. The Octave edge device contains the Data Hub which is responsible for communicating with the Octave cloud service.

Functionality and Support

Octave provides the following functionality and support for Modbus:

  • Communication over an RS485 RTU bus.
  • Communication over a TCP bus.
  • Communication with multiple servers on a bus.
  • Read and write access to all registers of each server.
  • Can create distinct resources in the Data Hub for the movement of data to and from a server.
  • Periodic reading (polling) of arbitrary registers of any server and post the results to the Data Hub.
  • Multiple simultaneous polling rates for different registers.
  • Provide an indication in the Data Hub of the status of any read or write operation.
  • Allow all Modbus-specific parameters, such as bus type, server addresses, etc. to be changed through the Octave dashboard.
  • Allow the names of resources created in the Data Hub to be configured through the UI.

Manipulating Register Data

The use and manipulation of register data is specific to the customer equipment and needs. As such, Octave's Modbus feature does not perform any built-in data manipulation. Instead, customers may use either Edge Actions or Cloud Actions to manipulate register values as needed.

Octave Modbus Architecture

The following diagram shows an Octave edge IoT edge device (1) and how it supports Modbus:

The device has an RS232 serial port (2), RS485 serial port (3), and Ethernet port (4).

Within the Octave edge device, Octave consists of:

  • a serial service (5) that defines the serial interface connection settings (e.g. baud rate, parity, etc.) which is configured via Device > Services in Octave;
  • the Modbus client driver (6) that handles Modbus protocol communications with an attached server.
  • the Data Hub (7) which performs all Octave communications with Octave cloud; and
  • the mapping configuration (8) which stores information about the Modbus configuration.

In this architecture, the Data Hub communicates with the Modbus client driver, which in turn performs read and write operations that were invoked via Octave (either via the cloud interface or through the Octave REST API).

Octave allows you to organize servers into one or more groups. A group is used to avoid having to create an individual resource for each register and provides a path as a resource for the registers on the Datahub.

Data Hub Resources

Modbus data is communicated through the Data Hub. Values which are read from Modbus server registers will appear as an input resource, and values which are written to server actuators will appear under an output resource. A configuration resource will reside at the top level and will determine the configuration of sub-levels. The top-level, or client, is capable of supporting multiple buses and each bus can support multiple servers. Server-specific resource paths will therefore consist of a bus name and server name.

Modbus Configuration JSON Format

The format for the modbus/config resource is shown below:

{
  "buses": [                                           // Array of buses with configuration values and list of attached servers
    {
      "name": "< bus resource name >",                 // String, Optional: Name to appear above server name in resource path.  Must be unique, if supplied
      "ethname": "< eth name >",                       // String, Mandatory for TCP: Physical device
      "rtu_dev": "UART0" | ... | "UARTn",              // String, Mandatory for RTU: Physical device
      "ipaddr": "< IP addr >",                         // String, Mandatory for TCP: IP address
      "ipport": "< port >",                            // Integer, Optional - TCP only: IP port.  Default 502
      "config": [
      {
         "slaves": [
              {
               "name": "< server resource name >",      // String, Mandatory: Name to appear in resource path.  Must be unique on this bus
               "address": < server address >,           // Integer, Mandatory: Server address.  Valid values [1:247]
               }, {...}
           ],
          "period": < polling period >                 // Integer, Mandatory: Polling period for servers table.  Valid values [0:(2^32)-1]
          "groups": [                                  // Array, Mandatory: Array of register address ranges and polling periods
            {
              "name": "< group resource name >",       // String, Mandatory: Name to appear in resource path.  Must be unique in this server
              "type": "DI" | "DO" | "AIN" | "HLD",     // String, Mandatory: Register type to poll
              "address": < first register >,           // Integer, Mandatory: First address in range to poll (decimal, zero-based).  Valid values [0:65535]
              "number": < number of registers >,       // Integer, Optional: Number address to poll, starting at first.  Valid values [1:121]. Default 1
              "id": < identifier >                     // String, Optional: Label to appear in read structure
            }
            , {...}
          ]
        }
      ]
    }
  ]
}

Polling

Registers which are configured for polling under the groups section, will have their read data formatted as a JSON object which is then pushed to: /app/modbus/<bus name>/<server name>/value.

The read JSON format for a group is shown below:

{
    "< register group name 1 >":
    {
        "error":
        {
            "errno": < int >,                 // Integer, Error code.  0 = success
            "errstr": < status of request >   // String, Error message.  Absent on success
        },
        "data":
        [
            < value 0 >, ... < value M >      // Array, Integer, Values read from the register range or null on failure
        ] 
    },
    "< register group name 2 >":
    {
        ...
    }
    ...
}

Asynchronous Requests

πŸ“˜

Note:

  • Support for Modbus Single Register write (FC=06) requires Octave edge device firmware >= 3.2.0.
  • Support for Modbus Single Coil write (FC=05) requires Octave edge device firmware >= 3.4.0.

Read and write requests can be made on a given server through the following request resources:

  • /app/modbus/<bus name>/<server name>/request/send
  • /app/modbus/<bus name>/<server name>/request/value

Requests are made by pushing JSON to the send node. Results, including data, are obtained from the value node.

Read Request

Arbitrary registers can be read from a server using read request JSON in the following format:

Asynchronous Read Request

{
    "id": < identifier >,                     // String, Optional, ID to be returned with result
    "request": "R",                           // String, Mandatory, Read request
    "type": < register type >,                // String, Mandatory, Register type: Discrete Out (coil), Discrete In (contact), Input Register, or Hold Register
    "address": < first register address >,       // Integer, Mandatory, First address in range to read (decimal, zero-based).  Valid values [0:65535]
    "number": < number of registers >         // Integer, Optional: Number address to poll, starting at first.  Valid values [1:121]. Default 1
}

Asynchronous Read Response

{
    "id": < identifier >,                     // String, Transaction label supplied in request.  If not provided, identifier is absent
    "data":[< data >],                        // Array, Data read from registers, NULL on failure
    "error":                                  // Object, Error status
    {
        "errno": < error code >,              // Integer, Error code, Zero on success
        "errstr": < error description >       // String, Error description, Absent on success
    }
}

Write Request

Arbitrary registers can be written to a server using a write request JSON in the following format:

Asynchronous Write Request

{    
   "id": < identifier >,                     // String, Optional, ID to be returned with result
   "request": "W" | "WM",                    // String, Mandatory, Write request
                                             // W: FC 05/06 for single, FC 15/16 for multi
                                             // WM: FC 15/16 for single and multi
   "type": "DO" | "HLD",                     // String, Mandatory, Register type:
                                             // Discrete Output (coil) or Hold Register
   "address": < first register address >,    // Integer, Mandatory, First address in range to write (decimal, zero-based)
                                             // Valid values [0:65535] 
   "data": [ < value 0 >, ... < value N > ]  // Array, Integer, Mandatory: Sequence of values to write, starting at base (dec)
                                             // Valid values [0:65535], Max number 121
}

Asynchronous Write Response

{
    "id": < identifier >,                     // String, Transaction label supplied in request.  If not provided, identifier is absent
    "data":[null],                            // Array, Null for write requests
    "error":                                  // Object, Error status
    {
        "errno": < error code >,              // Integer, Error code, Zero on success
        "errstr": < error description >       // String, Error description, Absent on success
    }
}

Request with User Defined Function Code

πŸ“˜

User Defined Function Codes

  • Octave Edge Devices running firmware 3.5.0 and above can send Requests with User Defined Function Codes.
  • The feature is available on both Modbus RTU and TCP/IP.

Asynchronous Request with User Defined Function Code

{
    "id": < identifier >,                     // String, Optional, ID to be returned with result
    "request": "USR",                         // String, Mandatory, To specify that we want a User Defined FC
    "type": < function code >,                // String, Mandatory, Function Code number in decimal. Eg. "110" for FC=0x6E
    "data": [ < value 0 >, ... < value N > ], // Array, Integer, Mandatory: Sequence of values to write
                                              // Valid values [0:65535], Max number 121
    "size": < size >                          // Integer, Mandatory, Size in bytes of the expected response
}

Asynchronous Response with User Defined Function Code

The value JSON will contain all the response data, except the CRC content.

{
    "id": < identifier >,                     // String, Transaction label supplied in request.  If not provided, identifier is absent
    "data":[< data >],                        // Array, Data, NULL on failure
    "error":                                  // Object, Error status
    {
        "errno": < error code >,              // Integer, Error code, Zero on success
        "errstr": < error description >       // String, Error description, Absent on success
    }
}

Error codes values

Here after the list of possible errno/errstr values returned in the response data.

  1: "Illegal function"
  2: "Illegal data address"
  3: "Illegal data value"
  4: "Server device failure"
  5: "Acknowledge"
  6: "Server device busy"
  7: "Negative acknowledge"
  8: "Memory parity error"
  9: "Undefined error"
 10: "Gateway path unavailable"
 11: "Target device failed to respond"
 12: "Invalid CRC"
 13: "Invalid data"
 14: "Invalid exception code"
 15: "Too much data"
 16: "Unknown exception code"
104: "Connection reset by peer"
110: "Connection timed out"
111: "Connection refused"
121: "Remote I/O error"

Additional Information

For additional information on using Modbus in Octave see: Modbus Guides.