In the Zabbix 2.2 version, loadable modules were added, which allowed us to expand the capabilities of the system at a new level. “Why is this needed?”, You ask, because it was always possible to run external scripts and programs from Zabbix. Of course, first of all, this is speed - modules, like Zabbix itself, are written in C and, with the right approach, work as fast as possible, unlike external programs that need to be run on each poll. Many may be frightened by the need to write code, but today I want to show you that everything is not so difficult.
For example, we will write a module that will allow Zabbix to collect information from devices operating according to the industrial automation protocol, which is widely used in the world - Modbus, and we will take readings of temperature sensors using it, as well as receive electricity parameters from the Mercury 230 meter. portal
share.zabbix.com , where users can share their work on Zabbix.

A few words about Modbus
Modbus is a very common protocol used in industrial automation networks (factories, workshops, other industrial facilities, various engineering systems — everywhere you can stumble upon Modbus). The protocol was developed back in 1979 by Modicon, and over the following decades, thanks to its advantages, Modbus was implemented on a huge number of different devices:
')
SimplicityModbus is very simple. This allows you to run and implement it on the most simple and cheap pieces of iron. Nor does it discourage developers from using them in their solutions.
Ethernet supportModbus was on time prepared for the widespread penetration of Ethernet networks, including industrial facilities, and now it works not only via serial RS-232, RS-485 (Modbus RTU / ASCII) lines, but also via TCP / IP (Modbus TCP) .
OpennessAll specifications of the protocol are open and accessible to all who wish to use it in their decisions, without any license fees to anyone.
Now I’ll say a few words about how Modbus works. Data is accessed in the device’s memory through special logic tables:
Discrete Input, Coils, Input Registers, Holding Registers . In the first two data are presented in the form of single bits, in the last two - in the form of 16-bit words:
Table | Data type | Access |
---|
Discrete input | 1 bit | only reading |
Coils | 1 bit | read and write |
Input Registers | 16 bits | only reading |
Holding registers | 16 bits | read and write |
The first two tables can be used to work with simple discrete values: “open-closed”, “accident is not an accident”, etc. And
Input and Holding Registers can be used for everything else. Moreover, the problem when the data does not fit in 16 bits is easily solved by using two adjacent registers. So you can work with 32bit floats or long.
Data elements in Modbus can be accessed through pass-through addressing from 0 to 65535 across all tables.
Data access may not overlap, and each table leads to a separate memory block:

Similarly, several tables can be used to access the same data:

For example, if we have a controller with 16 dry contacts, and we want to know how it is now, closed or open? Then we can take the values of all 16 contacts at once through the
Input Registers table, or read all the contacts in turn through the
Input Discrete table.
If Modbus devices are on a serial line (RS-485), then each device in Modbus has its own unique address (slave id, from 1 to 247), the polling device is always one and is called a master. In the case of a Modbus TCP device, an IP address is sufficient.
Where do we place Zabbix here with our future module?We will be able to place it as a Modbus master by connecting to the RS-485 network with controllers through a converter, or working with devices that support Modbus TCP directly:

Setting up the environment
Let's prepare a small test stand that will help us test our Modbus module in Zabbix. We will take the equipment from the domestic manufacturer
Aries , whose input-output devices will allow us to control the temperature, various discrete sensors, and power supply parameters. Also, as a bonus, we will take readings from the common three-phase electric meter Mercury 230R with the help of the DU-1M MAX-Logic converter, which can turn the embedded Mercury protocol into a Modbus. All these devices are connected to the RS-485 bus, to which Zabbix will gain access as a master via an RS232 / RS485 converter.
Configuring Aries modules for Modbus operation is described in the device documentation. In general, here is what you need to do:
Through the Aries configurator, connecting to all modules in turn through the RS232 / RS485 converter:
- Change slaveid addresses to be unique.
- Set the Modbus RTU exchange protocol
- For MK110.224.2A: select the type of temperature sensors connected
For DU-1M MAX-Logic (Mercury 230
) everything is a bit more complicated: First, carefully follow the instructions on the DU-1M to connect to the Mercury 230 counter, then instead of using a cozy graphical configurator like in Aries we will use the console utility
modpoll , sending commands to the configuration registers to configure pairing with the counter (phew!).
Here in the end what happens:

And this is how it looks live:

Having assembled a stand, we will test the performance with the help of
modpoll , for example, having connected a temperature sensor to the MB110-2A, we will poll it through register 10, as stated in the documentation for the device:

Or remove the energy consumption of the meter Mercury at the rate of T1:

As we can see, there is a connection, and data is collected. It is time to deliver this data to Zabbix.
Writing module
How to write a module for Zabbix?As in the case of running scripts through a
UserParameter, you first need to think about how the
item key will look like in Zabbix itself. Let's start with this, in our case it will be such a key:
modbus_read_registers[ <connection>, <slave_id>, <reg_to_read>, <modbus_function>, [<datatype>],[<endiannes>],[<first_reg>] ]
Inside this key, we will pass four required and three optional parameters:
- connection - IP address or serial port with connection parameters
- slave_id — Modbus Device ID
- reg_to_read - treasured register with data
- modbus_function is one of the functions of reading Modbus tables, 1 for reading COILS
- 2 for DISCRETE INPUT , 3 for HOLDING REGISTERS and 4 for INPUT REGISTERS
- datatype - the type of the returned data from the register. bit, 16bit int, 32bit int or 32bit float.
- endianness , first_reg - needed to correctly set the order of bits for 32bit values, as well as to indicate which addressing is used - Modbus data model (table entries, where the first element has address 1 and last n) or PDU addressing (addressing from 0 to 65535 ).
So, come up with, great, now let's go directly to the module. In fact, we need to write a function to collect data from the Modbus registers and add a special Zabbix interface to it from the following functions:
two required functions:
int zbx_module_api_version(void); int zbx_module_init(void);
And also three optional:
ZBX_METRIC *zbx_module_item_list(void); void zbx_module_item_timeout(int timeout); int zbx_module_uninit(void);
But we completely copy them from the
dummy.c example, which is kindly described in the documentation on Zabbix. Great, saved time and effort, we go further.
And then the question is how to write the collection function itself? By the way, let's call it this way:
int zbx_modbus_read_registers(AGENT_REQUEST *request, AGENT_RESULT *result)
And here the work is reduced to a minimum, because in order not to screw up the implementation of the Modbus protocol and not reinvent the wheel, we will take the C library, which already does all this well:
libmodbus .
Using the libmodbus interface, we will only have to
validate the incoming data from Zabbix, then run
the polling functions from libmodbus , and then
convert the resulting values into Zabbix data types and
return them. Well, or
return an error.
The rest of the details that I missed can be found
here .
And I once again emphasize a couple of key points:
1) If the function is successfully executed, the result is put into
* result via macros:
- SET_UI64_RESULT - if the result is an integer
- SET_DBL_RESULT - if the result is a floating point number
- SET_STR_RESULT - if the result is Char
- SET_TEXT_RESULT - if the result is text
- SET_LOG_RESULT - if the result is a log
For example:
SET_DBL_RESULT(result, modbus_get_float(temp_arr))
And the function itself should return
SYSINFO_RET_OK.2) If something went wrong, then you need to return
SYSINFO_RET_FAIL , and in
* result put an error message that we can see in the Zabbix web interface:
SET_MSG_RESULT(result, strdup("Check datatype provided."))

3) It is very important to remember to
validate all incoming input parameters. Otherwise, the fall of the module will lead to the fall of the Zabbix server or agent itself.
4) Remember that Zabbix launches many parallel polling processes. So if you are going to work with resources that do not tolerate simultaneous access (file or serial port in our case), then you need to implement control of this access on your own. I did this through semaphores, which made the task a bit more complicated, but fortunately this need not always be done.
5) Since the external libmodbus library is used, it must be installed on the system where the Zabbix module will be used, for this:
wget http://libmodbus.org/releases/libmodbus-3.1.2.tar.gz tar zxvpf libmodbus-3.1.2.tar.gz cd libmodbus-3.1.2 ./configure make make install ldconfig
As a result, we got the following
code for the module:
Compile it and add the resulting .so file to Zabbix Server or Zabbix Agent. Here it is only important to remember that the module depends on some headers of Zabbix itself. Therefore, if you want to collect everything separately from Zabbix, then copy at least zbxtypes.h, module.h, sysinc.h from the Zabbix sources to yourself. Well, that's done.
Stand testing
For example, the compiled file
libzbxmodbus.so will be uploaded to Zabbix Server, for this we add in the Zabbiks config file:
LoadModulePath = /usr/local/lib LoadModule = libzbxmodbus.so
Add the
zabbix user to the
dialout group to access the serial port:
usermod -a -G dialout zabbix
... and restart Zabbix:
/etc/init.d/zabbix-server restart
If everything is successfully restarted, then go to Zabbix and create a
template for our Modbus devices.
Creating a template
To begin with,
we will create data elements for the 110-224.2A to read the readings of the temperature sensor:

Where is the key (
key ):
modbus_read_registers[{$MODBUS_PORT},{$MODBUS_SLAVE},10,3,f,1,0]
- {$ MODBUS_PORT} - Macro that will be overwritten by the parameters of the connection to our device. For example, at the host level, we define our {$ MODBUS_PORT} as / dev / ttyUSB0 9600 N 1
- {$ MODBUS_SLAVE} - another user macro, which means slave_id and we will overwrite it as 32
- 10 - the register in which lies the temperature value
- 3 - Modbus function used for reading, in this case 0x03 READ HOLDING REGISTER
- f - data type, float
- 1 - since Aries controllers use Big endian to encode values that do not fit in one register
- 0 - PDU addressing is used
More on how to choose the parameters
here . In addition, you must specify:
Type
Simple check - if the module is loaded on Zabbix Server and Zabbix Proxy,
Zabbix agent - if the module is loaded on Zabbix Agent
Data
Type (
Type of information ):
in our case, this is
Numeric (float) .
Creating a host
Create
a network node , define macros, as agreed:

And attach the template that you just created:

Data
Go to the section Latest data and can begin to observe the data that we managed to collect from the sensor:

And by creating patterns for the remaining device registers, we get the following picture:

Well, then we can create
triggers ,
graphics ,
complex screens in the templates at will.
Conclusion
Using loadable modules, Zabbix developers have given us an interface for creating really fast and well-integrated extensions with Zabbix. Yes, the possibilities are so far limited by the creation of new types of data elements, and, for example, it will not be possible to add a new trigger function (
ZBXNEXT-2650 ), but wait and see.
So that the modules do not roll all over the Internet, a second important step was taken towards the organization of user solutions -
share.zabbix.com - a single repository created for the exchange of templates, modules and other extensions for Zabbix. Looking for a pattern under the sly piece of iron? Or have you written a template for a tricky piece of hardware and are ready to share it with the rest? Looking for a solution on how to
monitor PostgreSQL ? Or
Docker ?
Come and join us! Well, our module is already there too.