Modbus Guides

This describes how to connect your Octave-enabled device to a Modbus asset.

An edge device can interact with Octave by sending and receiving information through an Octave-ready device such as an AirLink FX30S over Modbus. The Octave-enabled device contains an Octave Datahub which is responsible for communicating with the Octave cloud service.

This topic provides the following tutorials:

📘

Notes

  • Your FX30S must be running firmware 2.0.0 or higher to use the Modbus functionality in Octave.
  • The FX30S can communicate using Modbus over RS-232/RS-485 and via its Modbus connectors.

Tutorial for Modbus Communications via RS232

This tutorial uses a development PC as an edge device that communicates with an FX30S using Modbus over a USB-to-serial cable. In this configuration, the FX30S acts as the master device and the PC is a slave device running Modbus simulation software to experiment with reading/writing values. In practice. the FX30S would typically be wired directly to a Modbus device via its Modbus connector.

📘

Note

This tutorial assumes a working knowledge of Modbus.

Setting up the Hardware

  1. Connect the development PC to the FX30S using a USB-to-serial cable.
  2. Install Modbus simulation software onto the development PC and configure it to use the USB-to-serial cable with the following RS-232 settings:
  • Baud Rate: 9600
  • Parity: None
  • Data bits: 8
  • Stop bits: 1

📘

Note

You can optionally choose to use RS-485 instead of RS-232 on the FX30S only.

Setting up Modbus on the FX30S Through Octave

Follow the steps below to configure Octave to communicate with a Modbus slave device:

  1. Select your FX30S in the Octave dashboard, and then navigate to Device > Services
  2. Locate the Modbus section, and click the sprocket icon to the right of UART1 to configure it.

📘

Notes

  • Ensure the device's UART is not already allocated in a Service, otherwise the UART cannot be configured.
  • The FX30S will reboot the first time that a new bus is created, and when a bus is switched between RS-232 and RS-485.
  1. Set Baud Rate to 9600, Standard to RS-232, Parity to None, Data bits to 8, and Stop bits to 1. This configures the master device. If you're using RS-485, set the Standard accordingly. Selecting RS-485 gives you the option to enable the Terminal resister field.
  2. Click the "+" button at the bottom to add a new Modbus Configuration. Each Modbus Configuration defines a set of slave devices and groups.
  3. Click in the Slave name field under Slaves and enter the name of the first slave device. Note that the Configuration's name will automatically update to include a comma-separated list of the slave names entered.
  4. Click in the Unknown Address field and enter the slave's address.
  5. (Optional) Click the "+" icon to the right of the slave to add additional slaves.
    bus1/slave1/group1```).
  6. Click in the Polling Period field under Register Groups and enter the amount of time to wait, in seconds, before accessing the registers.
  7. Click in the Group name field below the Polling Period and enter the name of a group.
  8. Set the Type to the appropriate Modbus slave type: Discrete Inputs, Coils, Input Registers, or Holding Registers.
  9. Set the address using either the Dec or Hex fields to define the first register in the group (using decimal or hexadecimal respectively), and set the Amount field to define the number of registers in the group. Together these define the starting address and length (number) of registers in the group.
  10. Enter an ID in the ID field.
  11. (Optional) Click the "+" button to the right of the ID to add additional groups.
  12. Click Save in the bottom right-hand corner to save the Modbus Configuration(s).

Verifying Resources

Follow the steps below to identify and verify the resources that have been created for your bus:

  1. Navigate to Device > Resources in the Octave dashboard.
  2. Locate and expand modbus > bus1.
  3. Verify that a slave Resource (e.g. slave1) exists under bus1 and expand it.
  4. Verify that a group Resource (e.g. group1/) and a request/ Resource were created under slave1. Note that the request/ resource will be used to send requests to the slave.

Reading Register Values

  1. Use the Modbus simulator on the development PC to change the values of the register(s) allocated to the Group.
  2. Use any of the following methods to view the value(s) of the register(s) in Octave:
  • Navigate to Device > Streams > and locate events related to these changes in the register values. The Value field for a successful Modbus event should look similar to the following example:
{
  "modbus": {
    "bus1": {
      "slave1": {
        "group1": {
          "group1": {
            "data": [0, 0, 0, 0, 0, 0, 0, 0],
            "errno": 0,
            "errstr": "Success"
          }
        }
      }
    }
  }
}
  • Navigate to Device > Observations, create a new Observation that sends events to the Cloud Stream, and save it. Wait for Octave to poll the device and then view the Observation's Last Value field to see the raw data read from the slave.

Sending Requests to the Slave Device

There are currently two methods for sending requests to a slave device:

Sending a Request Using the Request Entry

The request entry (e.g. /modbus/bus1/slave1/request/send) that was created for the Group when the bus was added, can be used to send requests to the slave device (e.g. to write a value to a Modbus register).

The following steps illustrate how to use a request to write to registers on the slave device via Octave:

  1. Navigate to Device > Resources.
  2. Locate and expand the request entry for your bus (e.g. modbus > bus1 > slave1 > request).
  3. Click the edit button to the right of the send property. This displays the JSON definition for the send request in a popup.
  4. Enter the JSON value request in the popup. For example, the following JSON will configure modbus to write the values 100 and 101 to holding registers 1 and 2 using a request named write_test:
{
  "id": "write_test",
  "request": "W",
  "type": "HLD",
  "address": 1,
  "data": [100, 101]
}
  1. Click Set. This sends the request to the slave device.
  2. In the Modbus simulator software, verify that the register(s) were updated.

Sending a Request via an Edge Action

A request can be programmatically sent in response to an Edge Action:

  1. Navigate to Device > Edge Action and click Add Edge Action.
  2. Configure the Observation.
  3. In the code window, add the following code:
function(event) {
   // Your code here
   return {
      "dh://modbus/bus1/slave1/request": [{"id":"write_test","request":"W","type":"HLD","address":1,"data":[100,101]}],
   }
}

The JSON key returned refers to the request entry on the Datahub (dh entity) and sets the value to the request to send. In the example above, the JSON will write values 100 and 101 to holding registers 1 and 2 using a request named write_test.

  1. When the Edge Action occurs, use the Modbus simulator software to verify that the register(s) were updated on the slave device.

Identifying Request Errors

Use any of the following methods to identify error responses from requests:

  • Navigate to Device > Streams > and locate events related to the request. If an error occurred (e.g. a timeout occurred because the device could not be reached) the JSON will contain a non-zero errorno and a description in errstr, similar to the following example:
{
  "modbus": {
    "bus1": {
      "slave1": {
        "group1": {
          "group1": {
            "data": [],
            "errno": -110,
            "errstr": "Connection timed out"
          }
        }
      }
    }
  }
}
  • Navigate to Device > Resources, expand modbus > bus1 > slave1 > request > value and locate the errstr property. The Value column will display error related information. For example:
/modbus/bus1/slave1/request/value.errno Yes Yes numeric -110 about 3 hours ago
/modbus/bus1/slave1/request/value.errstr Yes Yes string Connection timed out   about 3 hours ago
/modbus/bus1/slave1/request/value.data Yes Yes json [null] about 3 hours ago
/modbus/bus1/slave1/request/value.id Yes Yes string  write_test  about 3 hours ago

Configuring the Modbus Resource Directly on the Datahub

The underlying JSON that configures Modbus on the Datahub can be directly edited using the following steps:

  1. Navigate to Device > Resources.
  2. Locate the modbus/config resource.
  3. (Optional) Click the expansion icon to see the whole JSON structure.
  4. Click the edit icon to display the JSON editor.
  5. Modify the JSON as required and click Set. A reference guide for this JSON is provided in the next section.

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 slaves
    {
      "name": "< bus resource name >",                 // String, Optional: Name to appear above slave name in resource path.  Must be unique, if supplied
      "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": "< slave resource name >",      // String, Mandatory: Name to appear in resource path.  Must be unique on this bus
               "address": < slave address >,           // Integer, Mandatory: Slave address.  Valid values [1:247]
               }, {...}
           ],
          "period": < polling period >                 // Integer, Mandatory: Polling period for slaves 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 slave
              "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>/<slave name>/value.

The read JSON format for a group is shown below:

{
    "groupName":{
        "error": { "errstr": < status of request >,  "errCode":<int>
        "data": [ < value 0 >, ... < value M > ]  // Array, Integer, Values read from the register range starting at addr 0 (decimal) or null on failure
    },
    "groupName2":{...}
}

Asynchronous Requests

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

  • /app/modbus/<bus name>/<slave name>/request/send
  • /app/modbus/<bus name>/<slave 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 slave using read request JSON in the following format:

Asynchronous Request: Read (JSON)

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

Response to Async Read (JSON)

{
    "id": < identifier >,                     // String, Transaction ID supplied in request.  If not provided, identifier is null
    "status": < status of request >,          // Integer, Result code
    "data": [ < value 0 >, ... < value M > ]  // Array, Integer, Values read from the register range starting at addr 0 (decimal) or null on failure
}

Write Request

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

Asynchronous Request: Write (JSON)

{
    "id": < identifier >,                     // String, Optional, ID to be returned with result
    "request": "W",                           // String, Mandatory, Write request
    "type": < register type >,                // 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 (decimal).  Valid values [0:65535], Max number 121
}

`
Response to Async Write (JSON)

{
    "id": < identifier >,                     // String, Transaction ID supplied in request.  If not provided, identifier is null
    "status": < status of request >,          // Integer, Result code
    "data": null                              // Not used
}

Status

Slave Status

Information about each slave is stored as JSON under the status resource: /app/modbus/<bus name>/<slave name>/status/value.

The slave status JSON format is shown below:

{
  "errors": [
    // Array, information about last N errors
    TBD
  ],
  "statistics": {
    // Statistics - e.g. total reads/writes, transaction times, etc.
    TBD
  }
}