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:
DEVICE ID
FUNCTION
ADDRESS
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:
DEVICE ID
FUNCTION
ADDRESS
COUNT
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
.