The Octave Developer Hub

Welcome to the Octave developer hub. You'll find comprehensive guides and documentation to help you start working with Octave as quickly as possible, as well as support if you get stuck. Let's jump right in!

Get Started

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 master that initiates communication with multiple slaves, 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.

Registers

The Modbus protocol uses a simple a 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.

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 slaves. 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 slaves on a bus.
  • Read and write access to all registers of each slave.
  • Can create distinct resources in the Data Hub for the movement of data to and from a slave.
  • Periodic reading (polling) of arbitrary registers of any slave 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, slave 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 Master driver (6) that handles Modbus protocol communications with an attached slave.
  • 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 master 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 slaves 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 slave registers will appear as an input resource, and values which are written to slave 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 master, is capable of supporting multiple buses and each bus can support multiple slaves. Slave-specific resource paths will therefore consist of a bus name and slave 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 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:

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

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

Additional Information

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

Updated about a month ago

Modbus Reference


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.