Modbus

Note

Modbus protocol follows the master / slave principle. Modbus features offered by OnSphere are Master features.

Capabilities

Capabilities

Support

Comment

Modbus slave

Not supported feature

This feature is currently not supported. In case of interest please contact us at info@swissdotnet.ch

Modbus master

Supported feature

Modbus RTU

Partial support

It’s necessary to use a physical device (ex: MOXA for converting protocols).

Modbus TCP

Supported feature

Slave connectivity monitoring

Supported feature

See slave connectivity monitoring.

Read

Supported feature

See Reading

Write

Supported feature

See Writing

Data conversion

Supported feature

See data conversion for read and write.

Multi-register usage

Supported feature

Read/write value spanning over multiple Modbus registers. See multi-register usage.

Sub-ranges

Supported feature

Allows to select some bits of a register to read / write. See sub ranges.

Type of values

Type

Support Read

Support Write

Comment

BOOLEAN

Supported feature

Supported feature

INT8

Supported feature

Not supported feature

See sub ranges

UINT8

Supported feature

Not supported feature

See sub ranges

INT16

Supported feature

Supported feature

UINT16

Supported feature

Supported feature

INT32

Supported feature

Supported feature

UINT32

Supported feature

Supported feature

INT64

Supported feature

Supported feature

UINT64

Partial support

Partial support

This only support for a value up to 263-1 or 9,223,372,036,854,775,807.

FLOAT32

Supported feature

Supported feature

ASCII

Supported feature

Supported feature

BITMAP

Not supported feature

Not supported feature

DATETIME

Not supported feature

Not supported feature

Examples

Concept

OnSphere allows you to easily read and write data on a Modbus slave without being bothered with conversion between high level abstracted data you want to read / write (e.g string, signed integer, floating value …) and what the slave will understand (bits). It even go a step further since it extends its mechanism converting what you need to what the slave is expecting to partial Modbus register as well as multi-register. This means you can read an 8 bits unsigned integer on an Input register or write a float on consecutive Holding registers as easily as you can get a boolean out of a Coil.

Since it is often useful to know the “state” of a slave OnSphere also provides a feedback on slaves connectivity.

A lot of parameters have been made available to allow you to best tailor OnSphere behavior to your needs and those of the devices you are interacting with.

Note

While Modbus protocol defines many requirements a lot of choices have been left to manufacturers. It is important to keep this in mind as you might encounter devices with constraints which might differ from what you are used to. Below is a list of some of those aspects:

  • Not all slaves have all four different tables (coil, discrete input, input register and holding register). Only the slave documentation should be used to determine which tables it provides

  • Not all slaves use system reserved port 502. Only the slave documentation should be used to determine out of factory configured port.

  • Not all slaves use 0 based address (OnSphere does, but other devices have addresses starting a 1).

Use-case

OnSphere will read/write anything a slave allows it to, which means there are an infinite of usages

  • Monitor server room temperature

  • Open gate

  • Count people passing through a door

  • Control a room lighting

Examples

Connection on slave

First step once your osp-modbus module is deployed is to configure it to connect on a slave.

Some slaves only allow one connection at a time (concurrentConnections), others limit the number of values which can be read/written in a single request (bitMaxSize / registerMaxSize) or even the number of requests which can be done in a given time frame. To be able to match this kind of constraints OnSphere makes a lot of parameters available.

Slave connectivity monitoring

OnSphere publishes a value whose content reflects the connectivity state of the slave it is associated with: True when the connection with the slave is established, False otherwise.

For slave with multiple concurrentConnections configured :

  • True when the first connection on the slave is successfully established

  • False when the last connection on the slave is lost

Warning

Monitoring of slave connectivity is only done when at least one value is read. When there is no configuration to read any value, slave connectivity is reported as False if write fails. It means if you really need a quick feedback on slave connectivity then you should configure your slave with a short readPeriod and at least one value to read.

Reading

Note

OnSphere handles reading periodically. Every changes in-between readings is lost.

OnSphere publishes the content of values it reads on its communication bus for other modules to use. If a value does not change, nothing new is published on the communication bus. It is also possible to provide a delta parameter to indicate new value should be published only if it changed more than this provided delta.

Values are read periodically with a readPeriod configured at slave level. This means if you have different refresh rate needs from one value to another on the same slave you should either configure the slave with the highest readPeriod or, if the slave allows it, create two slaves configuration both for the same end device (with same host parameter) but with different readPeriod.

Writing

While reading is done periodically, writing on the other hand is on demand.

OnSphere allows Modbus writing through callbacks: define your write and simply reference it in a callback.

OnSphere handle the following errors :

  • content to write not matching configured modbusType (e.g request to write a number on a coil)

  • content to write not matching configured valueType (e.g request to write 70000 on an INT16 is out of range)

  • content to write not properly formatted (e.g missing field, not json compliant, …)

Note

If a writing fails, OnSphere will retry every 2s during 20s

Data conversion

OnSphere handles data conversion on its own so that you can focus on what you want to read/write instead of how you should read/write it. Simply specify the valueType you want to read / write and OnSphere will do the rest.

Note

OnSphere is able to detect invalid valueType / modbusType combination (e.g trying to read a FLOAT spanning on two register out of a single bit COIL) for you and report it before the configuration is applied.

Both read and write expose an endianness parameter. Despites its name, the endianness parameters allows to set the endianness to use as well the the bytes ordering.

Warning

The device manufacturer documentation is the ONLY source of truth. The valueType you can expect the slave to provide, the modbusType it is stored in or the endianness used to store it are all at the manufacturer discretion.

Multi-register

Modbus registers are 16 bits registers which means by default, Modbus does not handle FLOAT, DOUBLE, UINT32 or any value needing more than one register. Fortunately, OnSphere does not have such restriction. Simply specify the valueType and OnSphere will do the rest.

Note

Most valueType use a fix number of registers but not all. For the TEXT valueType the number of registers cannot be known beforehand. In this case you need to provide the number of characters (length) you want to read / write and OnSphere will handle the rest.

Sub-ranges

Sub-ranges are provided through a combination of offset and binary AND mask. The offset is applied first to discard unwanted bits then the mask to select needed bits among the remaining ones (those not discarded by the offset).

Warning

The mask is a binary AND mask but it must be configured in decimal. With AND mask, to keep a bit you set your mask to 1 and to 0 to discard it. Let’s say you want to keep 2 bits, mask is 1 1 which in the configuration should be set to 3 (11 in binary is 3 in decimal)

Note

  • Reading 8 bits valueType requires sub-ranges since they do not occupy a full Modbus register. If none is provided a default one is used to read only the first part of the register (offset: 0 / mask: 255)

The offset is applied first, then the mask. This is always true and should be enough to keep in mind most of the time. But, for more complicated usage, know that the complete pipeline is as follow :

raw bytes -> valueType -> offset -> mask -> ToType (where TOType is the type configured in the associated value.ospp

Examples

Defining offset and mask to get the needed sub-range can be a bit daunting at first. To help you better understand how to define those here are some examples of sub-ranges usage to read part of a Modbus register.

Most common usage will most likely be reading a single bit of an UINT16 valueType which can easily be done with a 1 mask and an offset used as an index (starting at 0) e.g :

Given a Modbus register (16 bits) containing the following value : 0 0 0 0 0 0 0 0 | 1 0 1 1 0 1 0 1

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

Note

  • Index must be read from right to left to match default BIG endian endianness (most significant bit stored first)

  • To help understanding following example, bits discarded by offset are replaced by a X while those selected by the mask are int in bold

So, let’s say we want 3 sub-ranges allowing to get :

  • first bit (at index 0, whose value here is 1)

  • fourth bit (at index 3, whose value here is 0)

  • sixth bit (at index 5, whose value here is 1)

Sub-range for the first bit

Index starts at 0 so first bit index is 0 which gives us offset: 0. To select mask: 1

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

0

mask

1

1

mask selection

1

res

1

1

-> (offset: 0, mask: 1) result value : 1

Sub-range for the fourth bit

Index starts at 0 so fourth bit index is 3 which gives us offset: 3. To select mask: 1

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

X

X

X

3

mask

1

1

mask selection

0

res

0

0

-> (offset: 3, mask: 1) result value : 0

Sub-range for the sixth bit

Index starts at 0 so sixth bit index is 5 which gives us offset: 5. To select mask: 1

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

X

X

X

X

X

5

mask

1

1

mask selection

1

res

1

1

-> (offset: 5, mask: 1) result value : 1

Now let’s see a few examples selecting more than 1 bits :

Sub-range for first three bits

Index starts at 0, we want the first three bits which gives us offset: 0. The sub-range mask is a binary AND mask. With an AND mask, to keep bits, bits of mask but be set to 1. We want to keep three bits so : 1 1 1 which gives us a mask of 7

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

0

mask

1

1

1

7

mask selection

1

0

1

res

1

0

1

5

-> (offset: 0, mask: 7) result value : 1 0 1 (5 in decimal)

Sub-range for second and third bits

Index starts at 0, first bit we want to keep is the second one which gives us offset: 1. We want to keep two bits so : 1 1 which gives us a mask of 3

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

X

1

mask

1

1

3

mask selection

1

0

res

1

0

2

-> (offset: 1, mask: 3) result value : 1 0 (2 in decimal)

Sub-range for fourth and seventh bits

Index starts at 0, first bit we want to keep is the fourth one which gives us offset: 3. We want to keep (1) the fourth bits, discard (0) fifth and sixth and keep the seventh bit so : 1 0 0 1 which gives us a mask of 9

Index

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

decimal value

e.g val

0

0

0

0

0

0

0

0

1

0

1

1

0

1

0

1

181

offset

X

X

X

3

mask

1

0

0

1

9

mask selection

0

1

1

0

res

0

0

0

0

0

-> (offset: 3, mask: 9) result value : 0 0 0 0 (0 in decimal)

Requirement