Loadable module to integrate Modbus (RTU, TCP and encapsulated) into Zabbix. Bulk data collection included.
This is the Loadable module that adds support for Modbus (TCP, RTU and "RTU over TCP" (encapsulated)) in Zabbix.
This module features:
READ COILS
, READ DISCRETE INPUT STATUS
, READ HOLDING REGISTERS
and READ INPUT REGISTERS
Big Endian
, Little Endian
, Mid-Big Endian
, Mid-Little Endian
Download sources from here, then do:
tar zxvpf libzbxmodbus-0.9.tar.gz
cd libzbxmodbus-0.9
./configure --prefix=/etc/zabbix --enable-zabbix-[2|3|3.2]
make
make install
Note: If you want to install from Github sources, see hints here.
One more note: use --zabbix-3.2
in configure
for all newer Zabbix versions such as 3.4, 4.0 and so on.
LoadModulePath = /etc/zabbix/lib
LoadModule = libzbxmodbus.so
Timeout
to 10 seconds or more: Timeout = 10
usermod -a -G dialout zabbix
Configure the new item with the following type:
Simple check if the module is on zabbix_server or zabbix_proxy
Zabbix Agent or Zabbix Agent (active) if the module is on zabbix_agentd
and then input the key:
modbus_read[<connection>,<slave_id>,<reg_to_read>,<modbus_function>[,<datatype_expression>][,<endiannes>][,<first_reg>]]
where:
connection:
for Modbus TCP:
IPv4 of Modbus TCP client/gate, for example: 192.168.1.1
you may also use this form: tcp://192.168.1.1
TCP port may also be redefined (from Modbus TCP default 502) if needed: 192.168.1.1:5000
for Modbus Encapsulated (RTU over TCP):
IPv4 of Modbus gate, for example: enc://192.168.1.1
TCP port may also be redefined (from Modbus default 502) if needed: enc://192.168.1.1:5000
Note: DNS names are not supported for TCP and RTU over TCP
for Modbus RTU over serial:
Serial connection parameters in a form of:
portname [baudrate] [parity:N|E|O] [databits] [stopbits]
for example
/dev/ttyS0 9600 N 8 2
/dev/ttyUSB0 115200 E 8 1
or enter only the portname, defaults for the rest will be used:
/dev/ttyS1
defaults are: 9600 N 8 1
slave_id:
Modbus slave ID or Unit Identifier(for TCP)
reg_to_read:
First Modbus register to read
modbus_function:
Modbus function in integer form:
1
- for READ COILS
2
- for READ DISCRETE INPUT STATUS
3
- for READ HOLDING REGISTERS
4
- for READ INPUT REGISTERS
datatype_expression (optional):
Datatypes for READ COILS and READ DISCRETE INPUT STATUS:
b
or bit
- for MODBUS_BIT.
Datatypes for READ HOLDING REGISTERS and READ INPUT REGISTERS:
i
or uint16
- for MODBUS_UINT16, 16bit (unsigned)
s
or int16
- for MODBUS_SIGNED_INT, 16bit (NOTE: in Zabbix use 'Type of information' Numeric (float))
l
or uint32
- for MODBUS_UINT32, 32bit (unsigned)
S
or int32
- for MODBUS_SIGNED_INT32, 32bit (NOTE: in Zabbix use 'Type of information' Numeric (float))
f
or float
- for MODBUS_FLOAT, 32bit
I
or uint64
- for MODBUS_UINT64, 64bit (unsigned) (NOTE: in Zabbix use 'Type of information' Numeric (unsigned))
d
or double
- for MODBUS_FLOAT64, 64bit
If datatype is not provided, defaults will be used:
MODBUS_BIT if modbus_function is 1
or 2
.
MODBUS_UINT16 if modbus_function is 3
or 4
.
Note: Datatypes can be combined in the datatype expression here to request more than one register at once. See section (5) below for how to do this.
endianness(optional):
Modbus endianness for 32bit/64bit datatypes:
0
or MLE
- for MODBUS_MLE_CDAB (Mid-Little Endian (CDAB))
1
or BE
- for MODBUS_BE_ABCD (Big Endian (ABCD))
2
or MBE
- for MODBUS_MBE_BADC (Mid-Big Endian (BADC))
3
or LE
- for MODBUS_LE_DCBA (Little Endian (DCBA))
Default is BE(1). Normally, you don't need to change this.
first_reg(optional):
Modbus addressing scheme:
0
- MODBUS_PDU_ADDRESSING is used. First data object has address 0. This is the default value.
1
- MODBUS_PROTOCOL_ADDRESSING is used. First data object has an address of 1.
Example keys:
modbus_read[/dev/ttyS0,32,4,3]
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},59,3,float,BE,0]
modbus_read[{HOST.CONN},{$MODBUS_SLAVE},59,3,float,BE,0]
modbus_read[/dev/ttyS0 9600 N,32,4,3,float,BE,0]
modbus_read[192.168.1.1,1,6,1]
modbus_read[192.168.1.1:514,1,5,1]
modbus_read[{$MODBUS_PORT},32,4,3,uint32,BE,0]
modbus_read[enc://192.168.1.1,1,6,1]
modbus_read[tcp://192.168.1.1:5000,1,5,1]
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,1,100*bit]
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,1,10*double]
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,2*uint16+skip+float]
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,2*uint16+double+float*3,MLE]
It is now possible to get as many Modbus registers as needed with just a single command and return it as the JSON object. JSON object is then processed by Zabbix (version 3.4 or newer is required) and all single values routed to different dependent items.
In order to get data in bulk, you first need to define which registers you want to retrieve using datatype expression.
The simplest datatype expression would be
2*uint16
that you write in the key: modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,2*uint16]
This expression is quite simple - it just retrieves two unsigned integers starting from register 10. Here is what you would get:
{
"10":123,
"11":321
}
To process it by Zabbix, first create master item:
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,2*uint16]
Then, create two dependent items: The first item:
$.10
And the second item:
$.11
Congratulations! You've just collected two metrics with the single Modbus command.
Note: You can find examples how mass data collection works in Zabbix here.
But datatype expressions can be more advanced. First, you can combine together different datatypes -
uint16+double+float
, and you can define how many times each datatype should be repeated -
2*uint16+double+float*3
. So in a key modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,2*uint16+double+float*3]
you would get a JSON:
{
"10":123,
"11":321,
"12":0.12345,
"16":100.10,
"18":100.10,
"20":100.10
}
Note two things here. First, since double
is 4 words long (64bit/16bit = 4), next request register address is actually 16
. Same applies to the following registers: since float
datatype is 2 words long (32bit/16bit = 2) you would see next keys as 18
and 20
.
Modbus protocol supports only sequential read of registers. But what if you don't need all of them? For that, there is a special keyword skip
you can use in order to retrieve multiple registers that are not located together. Example: Let's retrieve two int16
from registers with addresses of 10
and 20
. Zabbix key
modbus_read[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,int16+10*skip+int16]
would give you:
{
"10":123,
"20":-123
}
As you may noticed, skip
is one word (16bit) long.
Testing Modbus connectivity is easy with modpoll command utility.
You may try to grab some Modbus registers with it before you try to do it with Zabbix.
If you use libzbxmodbus with Zabbix agent, then you can also save time and test responses first with zabbix_get
, for example:
zabbix_get -s localhost -k'modbus_read[/dev/ttyS1 9600 N,9,0x1518,3,l,1,0]'
Use case example in IoT project: https://www.zabbix.com/files/zabconf2017/fabrizio_fantoni-zabbix_in_iot_architecture.pdf
More examples in Habrhabr article (RU): https://habrahabr.ru/company/zabbix/blog/268119/
About Modbus: http://www.ni.com/white-paper/52134/en/