Modbus slave (server) I/O

Managing Modbus slave context is a bit more complicated as Modbus data can not be sent directly from the PLC context due to different endianness (modern CPUs generally use little-endian, while Modbus requires big-endian numbers) and register layout.

Note

The described functionality requires modbus crate feature.

Defining Modbus servers

The servers should be defined in PLC config YAML. One PLC can have unlimited number of servers but all of them share the single Modbus context.

version: 1
server:
  - kind: modbus
    config:
      proto: tcp # or rtu
      listen: host:port (e.g. 0.0.0.0:503)
      # for RTU e.g.
      #/dev/ttyS0:9600:8:N:1
      timeout: 1 # remote timeout
      maxconn: 5 # max connections

Thread names are auto-assigned and shared between all server connections. E.g. the first defined Modbus server gets thread name Ssrv1_modbus. Modbus server threads can be also assigned to dedicated CPUs (see Real-time threads) but all threads of the same server share the same CPU and scheduler priority.

Defining Modbus context

In the context part of PLC config YAML, put the following:

# ....
context:
  modbus:
    c: 1000
    d: 1000
    i: 1000
    h: 1000

This defines a context with 1000 coils, 1000 discretes, 1000 inputs and 1000 holding registers. The Modbus context is automatically available in PLC context as modbus subfield.

PLC programs must read/write context data manually and parse/assign it to another context variables if required. rPLC uses rmodbus as Modbus stack and Modbus context API documentation can be found at https://docs.rs/rmodbus/latest/rmodbus/server/context/struct.ModbusContext.html

Program example

The program uses the register h0 as a 16-bit counter, which is increased every time when coil #0 is set.

#[plc_program(loop = "500ms")]
fn p1() {
  let mut ctx = plc_context_mut!();
  if ctx.modbus.get_coil(5).unwrap() { // get a coil
    let val = ctx.modbus.get_holding(0).unwrap();
    ctx.modbus.set_holding(0, val + 1).unwrap();
    ctx.modbus.set_coil(0, false).unwrap();
  }
}

As the context size is strictly defined, it is usually safe to use unwraps instead of checking context API calls for errors. However in production such code should be precisely covered with tests to avoid PLC process panic.