Simple read/write on Modbus

🟒 Beginner

modbus variable

This example shows how to read and write Modbus coil registers by configuring a slave connection, an owner file, and an output file with a callback trigger.

  • Register a Modbus slave device using slave.modbus with IP address and read frequency.

  • Read coil register address 0 via owner.modbus and expose it as a value.ospp.

  • Write to the Modbus device using output.modbus.

  • Trigger the write automatically on value change using a callback.ospp with expression-based inversion.

git checkout origin/osp-modbus-configuration .
git checkout origin/example-modbus-simple-read-write-example .

Prerequisites

  • Modbus connector

  • A Modbus device or a virtualized Modbus device like oitc/modbus-server:1.1.2

docker container run -p 5020:5020 --name=modbus-test oitc/modbus-server:1.1.2

Steps

1. Register a Modbus slave

The first step is to create a β€œslave” file in which we define how to connect to the slave. Replace the IP by the IP of the Modbus device. We are using a read frequency of 1 second for our test. This means the input from the Modbus will be updated every second.

Content : root/plc/slave-1/slave.modbus

{
    "host": "10.0.2.15:5020", 
    "moduleId": "modules.modbus.modbus-1",
    "readPeriod": {
        "value": 1,
        "unit": "SECONDS"
    }
}

2. Create a coil input

Content : root/input-coil/value.ospp

{
  "name": "input-coil",
  "description": "input-coil",
  "type": "BOOLEAN"
}

We introduce an entity called owner.modbus. This file serves the purpose of indicating our intention to interpret address 0 as a coil for reading. The slave ID field serves as the connection identifier to the corresponding slave device.

Content : root/input-coil/owner.modbus

{
  "address": 0,
  "endianness": "BIG",
  "modbusType": "COIL",
  "slaveId": "root.plc.slave-1",
  "valueType": "BOOLEAN"
}

1. Create an output file to write value on the modbus device

The output.modbus file describes how we want to write to the Modbus device, but does nothing if nobody interacts with it.

Content : root/output-coil/output.modbus

{
    "address": 0,
    "endianness": "BIG",
    "modbusType": "COIL",
    "slaveId": "root.plc.slave-1",
    "valueType": "BOOLEAN"
}  

4. Call the output from an event.

To create a state inversion, this test adds some simple logic to read the value from address 0 then call the output with the inverted value using the expression capabilities.

Content : root/input-coil/callback.ospp

{
    "linkedOutputs": [
        {
            "outputId": "root.output-coil",
            "transform": "not(value)"
        }
    ]
}

1. Push the configuration and check the result

git add .
git commit -m "Add simple modbus test"
git push

Check the value of the device :

docker container logs -f modbus-test

Logs must show :

The connexion from `osp-modbus` :
2023-08-14 14:10:48,922 MainThread       DEBUG    sync           :347      Started thread to serve client at ('172.17.0.1', 51850)
2023-08-14 14:10:48,922 Thread-1         DEBUG    sync           :46       Client Connected [172.17.0.1:51850]

A write request between 0 and 1 on the coil address (each second) :

2023-08-14 14:11:23,922 Thread-1         DEBUG    sync           :229      send: [ReadBitResponse(1)]- b'003d0000000401010100'
2023-08-14 14:11:23,922 Thread-1         DEBUG    sync           :199      Handling data: 0x0 0x3e 0x0 0x0 0x0 0x6 0x1 0x5 0x0 0x0 0xff 0x0
2023-08-14 14:11:23,923 Thread-1         DEBUG    socket_framer  :147      Processing: 0x0 0x3e 0x0 0x0 0x0 0x6 0x1 0x5 0x0 0x0 0xff 0x0
2023-08-14 14:11:23,923 Thread-1         DEBUG    factory        :137      Factory Request[WriteSingleCoilRequest: 5]
2023-08-14 14:11:23,923 Thread-1         DEBUG    context        :63       validate: fc-[5] address-1: count-1
2023-08-14 14:11:23,923 Thread-1         DEBUG    context        :90       setValues[5] 1:1
2023-08-14 14:11:23,923 Thread-1         DEBUG    context        :77       getValues fc-[5] address-1: count-1
2023-08-14 14:11:23,923 Thread-1         DEBUG    sync           :229      send: [WriteCoilResponse(0) => 1]- b'003e0000000601050000ff00'
2023-08-14 14:11:24,921 Thread-1         DEBUG    sync           :199      Handling data: 0x0 0x3f 0x0 0x0 0x0 0x6 0x1 0x1 0x0 0x0 0x0 0x1
2023-08-14 14:11:24,922 Thread-1         DEBUG    socket_framer  :147      Processing: 0x0 0x3f 0x0 0x0 0x0 0x6 0x1 0x1 0x0 0x0 0x0 0x1
2023-08-14 14:11:24,922 Thread-1         DEBUG    factory        :137      Factory Request[ReadCoilsRequest: 1]
2023-08-14 14:11:24,922 Thread-1         DEBUG    context        :63       validate: fc-[1] address-1: count-1
2023-08-14 14:11:24,922 Thread-1         DEBUG    context        :77       getValues fc-[1] address-1: count-1
2023-08-14 14:11:24,922 Thread-1         DEBUG    sync           :229      send: [ReadBitResponse(1)]- b'003f0000000401010101'
2023-08-14 14:11:24,923 Thread-1         DEBUG    sync           :199      Handling data: 0x0 0x40 0x0 0x0 0x0 0x6 0x1 0x5 0x0 0x0 0x0 0x0
2023-08-14 14:11:24,923 Thread-1         DEBUG    socket_framer  :147      Processing: 0x0 0x40 0x0 0x0 0x0 0x6 0x1 0x5 0x0 0x0 0x0 0x0
2023-08-14 14:11:24,923 Thread-1         DEBUG    factory        :137      Factory Request[WriteSingleCoilRequest: 5]
2023-08-14 14:11:24,923 Thread-1         DEBUG    context        :63       validate: fc-[5] address-1: count-1
2023-08-14 14:11:24,923 Thread-1         DEBUG    context        :90       setValues[5] 1:1
2023-08-14 14:11:24,923 Thread-1         DEBUG    context        :77       getValues fc-[5] address-1: count-1
2023-08-14 14:11:24,924 Thread-1         DEBUG    sync           :229      send: [WriteCoilResponse(0) => 0]- b'004000000006010500000000'