Supporting a new Modbus device

Modbus is a communication standard that is common in the SCADA business. It's usually used to connect to pump controllers, fan controllers, energy meters and other such devices. It has also been seen to control Television sets, lights and blinders in hotel rooms, and other places.

However, just because something communicates using Modbus, does not mean very much. A proper analogy could be to communicate using the Latin alphabet. You need a few more things to send a physical letter to someone.

To start with, you need an address. This is usually pre-configured on the devices physically, or set up in a special "configuration" mode.

After that, you need to know a bit more about the language used. Latin alphabet can mean a lot of languages, from German, to Latin or English. Knowing that something uses Modbus is not enough.

First off, you need a list of valid data points that you can read from a device. Usually this comes in a data sheet, PDF or manual. Sometimes, it's pieced together, or a single page of text.

A register has an address, a number between 0 and 65535 (16 bit). An address is combined with a function, like READ HOLDING REGISTER or READ INPUT REGISTER. A typical example could return the current software version on address 0 as INPUT REGISTER, while returning if the device is active or not as a HOLDING REGISTER. Note that in Modbus, the command is expressed as a single number, READ INPUT REGISTER would then be 04

Some devices map the same data to both holding and input registers, others do not. Knowing which is which is important.

Once you know these, you have the following data points:

This is not enough. You also need to know how large the data point you want to read is. Usually registers are 16 or 32 bits wide, but sometimes you're expected to read more than that.

This brings our fields to:

Here, we break for a small interlude. Count and ordering. Take the string ABCDE. Then read "4 letters starting from the first", you would expect to get ABCD. And then a second read command of "3 letters starting from the second" should give you CDE.
With modbus, you may get ABCD and XYZ, or ABCD and CDE, depending on the vendor.

Once you know the above, you are likely to get some data back. However, this data is not likely to mean much. First, you need to know how to decode the data into something useful. You need to know, for each register, if it is containing a signed or unsigned number, and which bit is the Most Significant Bit.
Note that this is an over-simplification, sometimes it contains BCD-encoded data or ASCII strings, and sometimes more exotic things.

Next up, you need to know what the data you get back means. Just because you know the answer is 3485, that doesn't tell you anything. Sometimes, 3485 should be understood as 34.85 (two decimals, fixed precision), other times it's 13:37 (256 time units per hour), other times its 74.5 (1 fixed decimal, zero offset at 2740)

So, then we have the following data: DEVICE ID FUNCTION CODE, ADDRESS, COUNT And the following meta-data: NAME, WORD COUNT, BYTE ORDER, SIGNED/UNSIGNED, CONVERSION

Where CONVERSION may be an offset to add/remove to a number, a factor to multiply/divide with, an amount of fixed decimals, or how to convert 3485 into 13:37.

In some cases, the meaning of a number can be discerned from the name of the register Current voltage is likely to be some kind of voltage, while E2-KTY may also be a voltage, which might not tell the implementator anything. Finding a proper unit for the numbers, and presenting it is part of the CONVERSION.

Now, back to the Voltage. It should be fairly simple, read a number, find out how many decimals it is, and convert? Except it might be expressed as milliVolt with 1 fixed decimal. In other cases it's a more obscure conversion. An example being a Voltage expressed as incremental numbers above 2740, or a temperature expressed as a number between 0 and 32768 where the meaning of both 0 and 32768 depend on some device-specific configuration.

And then there are the exceptions. Some vendors will use the same address to express different things in different settings or configurations. They overload functionality onto the same register address, where it sometimes is a Temperature, other times is a Pressure and yet again may be a Voltage. And others allow you to on the device configure how many decimals worth of data there should be. These things then cause even more headache for implementations.

Fortunately, most vendors will simply declare different kinds sensors as different registers, and either return an Error code when something isn't suitable, or a specifically chosen number (0 and 65535 are common). Yet others will simply return some random garbage, which may cause other problems.

After all this, it's usually good with some kind of description of the field. What should it be used for, and what does it mean. A name like E2 KTY may be self explanatory for people who work all the days in HVAC, but may mean nothing at all to the rest of the world.

In the end, for each register, for each device, you end up with something that looks like the following:

    "name": "RemoteFlow",
    "function": 4,
    "address": 313,
    "data_type": "u16",

    "factor": 10,
    "offset": 0,
    "unit": "m^3/h",
    "description": "Measured flow at external sensor"

Where the field data_type tells us both how large the data we read from the device is, and how it should be decoded.

Going further from this point, you may need meta-data that tells you about error values, or acceptable range of input data. For validation purposes, a min,max and usually a default.

D.S. Ljungmark

Release 3.8

Release 3.7