Modbus

The Modbus runtime component is responsible for interfacing between Modbus and DDS. It handles data flow both ways and over both serial and Ethernet. There are four distinct modes of this component, named after the modbus side of this component: Serial client, serial server, TCP/IP client, TCP/IP server. They all have the following fields in common:

  • Options:
    • PeriodMs: The update period in milliseconds.
  • ModbusSetup:
    • Type: Type of client or server. Can be SerialClient, SerialServer, TcpClient, or TcpServer
    • Port: Typically COM-port for serial components, and network ports for TCP/IP components
  • ModbusToDDS: Signals to read from Modbus and publish on DDS.
  • DDSToModbus: Signals to read from DDS and write to Modbus.

Both the Serial-based components have the following additional fields:

  • ModbusSetup:
    • Baud: The rate of transmition in bits per second. Possible rates are 1200, 2400, 4800, 9600, 19200.
    • Parity: Parity bit for checking of bit errors. Possible parity: N (none), E (even), O (odd).
    • Databit: Number of data bits in each frame.
    • Stopbit: Number of stop bits in each frame.
    • SlaveId: Id of the server-device.

Both server components have the following additional optional fields:

  • HoldingRegisterMinSize: Minimum size of allocated holding register. Default: 0.
  • InputRegisterMinSize: Minimum size of allocated input register. Default: 0.
  • BitHoldingRegisterMinSize: Minimum size of allocated bit holding register (coils). Default: 0.
  • BitInputRegisterMinSize: Minimum size of allocated bit input register (discrete inputs). Default: 0.

These settings ensure that the client will be able to address values up to the given index (i.e. size).

Both client components have the following additional optional fields:

  • MaxConnectionAttempts: The maximal number of times the client should try to connect to the server at startup, before giving up. Default: 5 for TCPClient, 3 for SerialClient.
  • ReconnectionDelaySecs: A delay (in seconds) that the client should wait before retrying to connect after a failed connection attempt. Default: 3 for TCPClient, 5 for SerialClient.

Including the above, the components have the following additional fields:

  • TCP/IP client
    • ModbusSetup:
      • Ip: IP-address of the server
      • SlaveId: [Optional] The standard states that use of this id for TCP/IP is incorrect. However, some server implementation will not work if it is not set. It is therefore included here to enable communication in such cases.
  • TCP/IP server
    • ModbusSetup:
      • Ip: Interface to listen on (IP), empty means all interfaces.

All components can also have a field ParameterPacks containing collections of settings which can be referred to in the signal lists under ModbusToDDS and DDSToModbus. Each collection of parameters (parameter pack) should be listed under an identifying name (key).

DDS topic name is used as key for each signal listed under ModbusToDDS and DDSToModbus. The following properties specify the signal communicated on DDS:

  • Type: The type used to communicate messages on the DDS topic.
  • Partition: [Optional] The partition for which to publish/subscribe the DDS topic. Default: ModbusToDDS: "", DDSToModbus: "*".
  • LifespanMs: [Optional] This is only valid for signals under ModbusToDDS. Set the lifespan QoS of the DDS data writer to this numer of milliseconds. Default is infinite.
  • TakeSample: [Optional] (Default true) This is only valid for signals under DDSToModbus. If set to false the sample will be read from the DDS data reader without being removed from the buffer. Subsequent read operations will read the same sample until it is overwritten by a new sample, or its lifespan QoS is exceeded.
  • ParameterPacks: [Optional] List of parameter packs to add to the signal.

Modbus properties for each field in the Type sample are listed under the Fields using the variable field name as key. The following properties may be included:

  • Table: (HoldingRegister, InputRegister, BitHolding or BitInput) The table on the Modbus server in which the signal is stored. The Bit registers are indexed by single bits, whereas the other registers are indexed by 16-bit words. BitInput and InputRegister are read-only for clients.
  • Index: The starting position of the signal in the Modbus table. For Modbus types which are smaller that 16 bits, the index can be specified as a word index (w) and a bit index (b) within the word as w+b. For bit registers, the indexing is b.
  • ModbusType: [Optional] (Default uint16) The representation of the signal in the modbus table. One of: bit, float, double, int8, uint8, int16, uint16, int32, uint32, int64, uint64
  • Scale: [Optional] Scale the signal so that: modbus signal * Scale + Offset = dds signal
  • Offset: [Optional] Offset the signal so that: modbus signal * Scale + Offset = dds signal
  • Range: [Optional] Two element array specifying minimum and maximum value of the (DDS) signal. Calculate Scale and Offset so that the modbus type will span the specified range of the DDS signal value. Note! Should only be included if neither Scale nor Offset are specified.
  • BigEndianWordOrder: Valid and mandatory for ModbusType spanning more than a single 16-bit word.
  • BigEndianByteOrder: [Optional] This can be set to false to change the default byte order of each 16-bit word. The default true value is in line with the specification in the standard. Note that libmodbus will change the byte order to host order between the data buffers and the actual Modbus transmission. This means that leaving this property set to true will give correct values on a little endian system, unless the remote party has incorrectly reversed the byte order.
  • ParameterPacks: [Optional] List of parameter packs to add to the Modbus signal. These are similar to YAML Anchors to be referenced later and merged with other variables. The purpose is to define common configurations once and reference them later, possibly multiple times.
Note
For a DDS type with a single data field, the Modbus signal properties may be included directly under the topic name key. That is, Fields and the variable field name may be omitted. The example files will make this feature clearer.

Use Case

Suppose you are tasked with configuring RatatoskModbus to send and receive signals between DDS and a Modbus device, whose mode is yet to be determined. You have received a signal list from an engineer that knows the Modbus configuration. Your first assignment is to write the ModbusToDDS block of the YAML configuration file. The signals coming from the Modbus device are expected to be populated into specific data structures on the DDS bus. In addition, some topics are to be sent on named partitions. The tables below summarise the information.

Id Signal name Modbus index Modbus type Data table Range BigEndian
1 Temperature 0 int16 InputRegister [-20, 100]
2 Salinity 1 uint16 InputRegister [0, 100]
3 Submerged 0 bit BitHolding
4 Active 1 bit BitHolding
5 TimeStamp 2 int64 InputRegister true
6 Pressure 6 uint64 InputRegister true
7 Position lon 10 double InputRegister true
8 Position lat 14 double InputRegister true
Id DDS topic DDS type DDS partition Lifespan
1 temp DoubleVal environment 1 s
2 salinity DoubleVal environment 1 s
3 inwater BoolVal device 0.1 s
4 isactive BoolVal device 0.1 s
5 timestamp Int64Val
6 pressure DoubleVal environment 1 s
7 position PositionGlobal2D
8 position PositionGlobal2D
ParameterPacks:
Env:
Partition: environment
LifespanMs: 1000
Table: InputRegister
Dev:
Partition: device
LifespanMs: 100
Table: BitHolding
ModbusType: bit
ModbusToDDS:
temp: {Type: DoubleVal, Index: 0, ModbusType: int16, Range: [-20, 100], ParameterPacks: [Env]}
salinity: {Type: DoubleVal, Index: 1, ModbusType: uint16, Range: [0, 100], ParameterPacks: [Env]}
pressure: {Type: DoubleVal, Index: 6, ModbusType: uint64, BigEndianWordOrder: true, ParameterPacks: [Env]}
inwater: {Type: BoolVal, Index: 0, ParameterPacks: [Dev]}
inactive: {Type: BoolVal, Index: 1, ParameterPacks: [Dev]}
timestamp:
Type: Int64Val
Index: 2
ModbusType: int64
Table: InputRegister
BigEndianWordOrder: true
position:
Type: PositionGlobal2D
Fields:
lon: {Type: DoubleVal, Index: 10, Table: InputRegister, BigEndianWordOrder: true}
lat: {Type: DoubleVal, Index: 14, Table: InputRegister, BigEndianWordOrder: true}

You create the above YAML configuration, which is well received. Your next assignment is to extend the configuration with some DDSToModbus entries. The table below contains the information need to produce the new yaml block.

DDS topic DDS type Modbus index Modbus type Modbus table Range BigEndian
docontrol BoolVal 0+8 bit HoldingRegister
setpoint PositionGlobal2D 0,4 double HoldingRegister true
ParameterPacks:
Set:
Double: DoubleVal
Table: HoldingRegister
ModbusType: double
BigEndianWordOrder: true
DDSToModbus:
docontrol: {Type: BoolVal, Index: 0+8, ModbusType: bit, Table: HoldingRegister}
setpoint:
Type: PositionGlobal2D
Fields:
lon: {Index: 0, ParameterPacks: [Set]}
lat: {Index: 4, ParameterPacks: [Set]}

Examples

We provide example files, which are useful as starting points when configuring RatatoskModbus for the mode you need.

Mode Example
Serial Client modbus_sc.yml
Serial Server modbus_ss.yml
TCP/IP Client modbus_tc.yml
TCP/IP Server modbus_ts.yml

Modbus Serial client

---
# Modbus serial client
Options:
PeriodMs: 1000
ModbusSetup:
Type: SerialClient
Baud: 9600
Parity: N
Databit: 8
Stopbit: 1
SlaveId: 17
Port: COM7
ParameterPacks:
PositionField: {Table: HoldingRegister, Range: [-10, 10]}
ModbusToDDS:
TestTopic:
Index: 0
Table: InputRegister
Type: DoubleVal
Partition: Example
Scale: 1.0
Offset: 0
VesselPosition:
Type: PositionGlobal2D
Partition: Example
Fields:
lon: {Index: 1, ParameterPacks: PositionField}
lat: {Index: 2, ParameterPacks: PositionField}
DDSToModbus:
SecondTestTopic:
Type: DoubleVal
Partition: Example
# The following entries are those usually in Fields:
# 'Fields:' was skipped here, since the Type is a scalar
Index: 0
Table: HoldingRegister
Range: [-10.0, 10.0]
SecondVesselPosition:
Type: PositionGlobal2D
Partition: Example
Fields:
lon: {Index: 3, Table: HoldingRegister, Scale: 100.0, Offset: 0.0}
lat: {Index: 4, Table: HoldingRegister, Scale: 100.0, Offset: 0.0}

Modbus Serial server

---
# Modbus serial server
Options:
PeriodMs: 1000
ModbusSetup:
Type: SerialServer
Baud: 9600
Parity: N
Databit: 8
Stopbit: 1
SlaveId: 17
Port: COM1
ParameterPacks:
PositionField: {Table: HoldingRegister, Range: [-10, 10]}
ModbusToDDS:
TestTopic:
Index: 0
Table: InputRegister
Type: DoubleVal
Partition: Example
Scale: 1.0
Offset: 0
VesselPosition:
Type: PositionGlobal2D
Partition: Example
Fields:
lon: {Index: 1, ParameterPacks: PositionField}
lat: {Index: 2, ParameterPacks: PositionField}
DDSToModbus:
SecondTestTopic:
Type: DoubleVal
Partition: Example
# The following entries are those usually in Fields:
# 'Fields:' was skipped here, since the Type is a scalar
Index: 0
Table: HoldingRegister
Range: [-10.0, 10.0]
SecondVesselPosition:
Type: PositionGlobal2D
Partition: Example
Fields:
lon: {Index: 3, Table: HoldingRegister, Scale: 100.0, Offset: 0.0}
lat: {Index: 4, Table: HoldingRegister, Scale: 100.0, Offset: 0.0}

Modbus TCP/IP client

---
# Modbus TCP client
Options:
PeriodMs: 1000
ModbusSetup:
Type: TcpClient
Ip: 127.0.0.1
Port: 5020
ParameterPacks:
DefaultDDS:
Partition: MYPart
LifespanMs: 3000
DefaultModbus:
Table: HoldingRegister
BigEndianWordOrder: true # This will be disregarded for types <=16 bits
BitOnRegister:
Type: BoolVal
ModbusType: bit
Table: HoldingRegister
BitOnCoil:
Type: BoolVal
Table: BitHolding
ModbusToDDS:
Out_TestCounter:
Type: Uint64Val
Partition: System
LifespanMs: 4000
Table: InputRegister
Index: 0
ModbusType: uint32
BigEndianWordOrder: true
Out_int16: {Index: 0, ModbusType: int16, Type: Int16Val, ParameterPacks: [DefaultDDS, DefaultModbus]}
Out_int32_BigE: {Index: 1, ModbusType: int32, Type: Int32Val, ParameterPacks: [DefaultDDS, DefaultModbus]}
Out_int16Rescld:
Index: 3
Table: HoldingRegister
ModbusType: uint32
Type: DoubleVal
Range: [0, 1000]
LifespanMs: 3000
BigEndianWordOrder: true
Out_bool: {Index: 5+0, ParameterPacks: [DefaultDDS, BitOnRegister]}
Out_i8neg: {Index: 5+1, ParameterPacks: [DefaultDDS, BitOnRegister]}
Out_float3viaInt16:
Type: Float3
LifespanMs: 3000
Fields:
x: {Index: 6, Table: HoldingRegister, ModbusType: int16, Scale: 0.001, Offset: 0}
y: {Index: 7, Table: HoldingRegister, ModbusType: int16, Scale: 10, Offset: 10000}
z: {Index: 8, Table: HoldingRegister, ModbusType: int16, Range: [-100, 100]}
Out_bit_coil: {Index: 0, ParameterPacks: [DefaultDDS, BitOnCoil]}
DDSToModbus:

Modbus TCP/IP server

---
# Modbus Tcp Server
Options:
PeriodMs: 1000
ModbusSetup:
Type: TcpServer
Port: 5020
ParameterPacks:
DefaultDDS:
Partition: MYPart
LifespanMs: 3000
DefaultModbus:
Table: HoldingRegister
BigEndianWordOrder: true # This will be disregarded for types <=16 bits
BitOnRegister:
Type: BoolVal
ModbusType: bit
Table: HoldingRegister
BitOnCoil:
Type: BoolVal
Table: BitHolding
ModbusToDDS:
Out_TestCounter:
Type: Uint64Val
Partition: System
LifespanMs: 4000
Table: InputRegister
Index: 0
ModbusType: uint32
BigEndianWordOrder: true
Out_int16: {Index: 0, ModbusType: int16, Type: Int16Val, ParameterPacks: [DefaultDDS, DefaultModbus]}
Out_int32_BigE: {Index: 1, ModbusType: int32, Type: Int32Val, ParameterPacks: [DefaultDDS, DefaultModbus]}
Out_int16Rescld:
Index: 3
Table: HoldingRegister
ModbusType: uint32
Type: DoubleVal
Range: [0, 1000]
LifespanMs: 3000
BigEndianWordOrder: true
Out_bool: {Index: 5+0, ParameterPacks: [DefaultDDS, BitOnRegister]}
Out_i8neg: {Index: 5+1, ParameterPacks: [DefaultDDS, BitOnRegister]}
Out_float3viaInt16:
Type: Float3
LifespanMs: 3000
Fields:
x: {Index: 6, Table: HoldingRegister, ModbusType: int16, Scale: 0.001, Offset: 0}
y: {Index: 7, Table: HoldingRegister, ModbusType: int16, Scale: 10, Offset: 10000}
z: {Index: 8, Table: HoldingRegister, ModbusType: int16, Range: [-100, 100]}
Out_bit_coil: {Index: 0, ParameterPacks: [DefaultDDS, BitOnCoil]}
DDSToModbus: