Back to Home

Collective farm monitoring UPS with Megatec protocol in Zabbix

There was a need to monitor the zoo UPS · Ippon · Powercom and Krauler were available. Zabbix is ​​used as a monitoring tool. Naturally · the task needed to be solved 1) ...

Collective farm monitoring UPS with Megatec protocol in Zabbix

    There was a need to monitor the zoo UPS, Ippon, Powercom and Krauler were available. Zabbix is ​​used as a monitoring tool.

    Naturally, the task needed to be solved 1) cheap 2) even cheaper, so the option with SNMP modules was immediately rejected. It was decided to use the serial port connection, since there was experience in developing for Ippon and APC. By the way, to the APC in the secondary market there are SNMP modules at a reasonable price, but for low-cost UPSs I was able to find only new modules at a price of 11-20 thousand rubles.

    During the work the following additional tasks were set:

    1. find and check the cables for connecting each UPS, for those in the kit were missing
    2. Implement a module that, on the one hand, has an RS-232 interface and understands the data exchange protocol with each UPS, and on the other hand, has a network interface and can send data in the form of zabbix_trapper.
    3. Test the data acquisition and transmission model, parts of the code, and data format.


    During the implementation of the second and third paragraph, I wanted to collect in one place all the data that would in the future allow us to implement a separate device at the meter station.

    So:

    • 1. Cables

    There were no cables for any UPS. In principle, the first task is not a problem for people who can count pins on the DB9 connector. As it turned out, not only everyone can do this, including me. Contacts on connectors F and M are mirrored, but signed, in general, it is impossible to make a mistake if you are careful.

    • For Ippon Smart Winner 2000, a cable from the back power pro model came up, the usual straight cable 2-2, 3-3, 5-5. In principle, on this bespereboynik everything was in the documentation, including the exchange protocol.
    • For Krauler Memo RT 2000 connector is also described in the documentation on the site krauler.ru, using the same cable 2-2.3-3, 5-5.
    • For Powercom SXL-2000A cable wiring is also available in the documentation www.pcm.ru/data/docs/cables.zip , found the link to forum.pcm.ru . Cable 2-9, 3-6, 5-7. (PC-UPS).


    Small offtopic. The basis of the server consists of two Vmware ESXi hosts, which are housed in a rack with two UPSs. Some servers have 2 power supplies. And part, unfortunately, only one, why periodically suffer. Zabbix is ​​currently hosted on one of the hosts as a virtual machine. In principle, to deploy a small virtual server on Ubuntu (I use this platform for services) for the implementation of any task is not a problem.

    Thus, the following scheme has emerged: a COM port on ESXi -> Com port on a virtual machine -> C program that returns data from the UPS -> Script or program that sends zabbix trap. The last two points in the future should be combined. Although it works and so.

    Solved the problem sequentially, cable wiring for one UPS, wiring an additional connector for the server (there are two, plus an rj-45 adapter in the DB9), connection test, program check, setting up items in Zabbiks.

    • 2. Interrogation of the COM port by the program

    The first thing you need is to choose a server to host the executable code. I did not puzzle and put it all on Zabbix server.
    Second, I thought how to process the data. Due to the presence in the Bash design
    array=( $(/home/appliance/uniups) )
    
    which breaks a string into an array of data, I decided to simply return the line segment MMM.M NNN.N PPP.P QQQ RR.R S.SS TT.T
    Program code
    #include<stdio.h>   /* Стандартные объявления ввода/вывода */#include<string>#include<iostream>#include<cstring>usingnamespacestd;
    #include<unistd.h>  /* Объявления стандартных функций UNIX */#include<fcntl.h>   /* Объявления управления файлами */#include<errno.h>   /* Объявления кодов ошибок */#include<termios.h> /* Объявления управления POSIX-терминалом */#include<sys/types.h>#include<sys/stat.h>int fd; /* Файловый дескриптор для порта */char buf[512];/*размер зависит от размера строки принимаемых данных*/intmain(int argc, char* argv[]){
        int iIn,iOut;
        string UPSAnswer;
        if (argc>=2)
    {
            fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY); /*'open_port()' - Открывает последовательный порт */if (fd == -1)   {
               printf("error port\n");
               perror("open_port: Unable to open port - ");   }
         else
            {
             struct termios options; /*структура для установки порта*/
             tcgetattr(fd, &options); /*читает пораметры порта*/
             cfsetispeed(&options, B2400); /*установка скорости порта*/
             cfsetospeed(&options, B2400); /*установка скорости порта*/
             options.c_cflag &= ~PARENB; /*выкл проверка четности*/
             options.c_cflag &= ~CSTOPB; /*выкл 2-х стобит, вкл 1 стопбит*/
             options.c_cflag &= ~CSIZE; /*выкл битовой маски*/
             options.c_cflag |= CS8; /*вкл 8бит*/
             tcsetattr(fd, TCSANOW, &options); /*сохронения параметров порта*/
            }
            char Params[64];
            if(argc>=3)
            {
                if (!strcmp(argv[2],"status"))
                {
                    iOut = write(fd, "Q1\r", 3);
                    usleep(800000);
                    if (iOut < 0)  fputs("write() of 4 bytes failed!\n", stderr);
                    iIn=read(fd,buf,250); /*чтения приходящих данных из порта*/strncpy(Params,&buf[38],11);
                     Params[46]=0;
                }
                elseif (!strcmp(argv[2],"help"))
                {
                    printf ("Денег нет. Но вы держитесь!\r\n");
                }
                elseif (!strcmp(argv[2],"name"))
                {
                    iOut = write(fd, "I\r", 3);
                    usleep(800000);
                    if (iOut < 0)  fputs("write() of 4 bytes failed!\n", stderr);
                    iIn=read(fd,buf,250); /*чтения приходящих данных из порта*/strncpy(Params,&buf[0],60);
                }
                elseif (!strcmp(argv[2],"stat2"))
                {
                    iOut = write(fd, "F\r", 3);
                    usleep(800000);
                    if (iOut < 0)  fputs("write() of 4 bytes failed!\n", stderr);
                    iIn=read(fd,buf,250); /*чтения приходящих данных из порта*/strncpy(Params,&buf[1],60);
             Params[20]=0;
                }
                else
                {
                    printf ("Щта? \r\n");
                }
            }
            else
            {
               /* читаем данный по Q1 */
                    usleep(1800);
                    iIn=read(fd,buf,250);
                    usleep(1800);
               iOut = write(fd, "Q1\r", 3);
               usleep(800000);
               if (iOut < 0)  fputs("write() of 4 bytes failed!\n", stderr);
               iIn=read(fd,buf,250); /*чтения приходящих данных из порта*/strncpy(Params,&buf[1],36);
               Params[36]=' ';
            }
        close(fd);
        printf("%s\r\n",Params);
    }
    elseprintf("Usage %s /dev/ttySx", argv[0]);
    }
    


    Found a job with the port in Google, you can do the reading from Bash, but from Bash it was not possible to achieve stable work. In principle, the C code in linux is b. The first was tested by Ippon, then during the study of the Powercom UPS, a port monitor was launched, which showed that PowerCom also works using the Megatec protocol, and the native program polls the UPS when started with the “I” and “F” commands, and then cyclically “Q1”. I hung the name of the port in the form of "/ dev / ttyS0" or "/ dev / ttyS1" on the first argument, the second argument allows you to request additional parameters, the code shows.

    In the directory / home / appliance / placed the program, called it uniups.cpp. Compiled
    g++ -o uniups uniups.cpp 
    .

    In principle, the result is approximately the same (yes, I work under the root, do not even comment)

    root@zabbix:~# ./uniups /dev/ttyS0
    204.4 204.4 204.4 035 49.9 54.8 54.5
    root@zabbix:~# ./uniups /dev/ttyS0 name
    I
    root@zabbix:~# ./uniups /dev/ttyS1 name#POWERCOM        SXL-2000A  LCD  V4.3
    root@zabbix:~# ./uniups /dev/ttyS1
    216.3 216.3 216.3 000 50.0 54.2 30.0
    root@zabbix:~# ./uniups /dev/ttyS1 stat2
    220.0 009 048.0 50.0
    root@zabbix:~#

    What is important, in the end it turned out that all the UPSs use the same protocol, which allowed the use of one program. Connection to 2400, 8N1 is used, everything else is off. Not without a fly in the ointment, the name of the UPS is normally returned only by Powercom, Ippon does not understand the “I” command, and Krauler returns "# R1.1.1".

    In addition, Krauler returns the voltage on the cell, and the rest of the UPS - on the battery, the documentation describes it as
    SS.S or S.SS. For standby units

    In view of this, I had to put a patch on the script. About this below.

    • 3. Sending data to Zabbiks

    With sending this turned out a zoo for all occasions. On the server, Zabbiks used the zabbix_sender utility to speed up the process.
    zabbix_sender -z адрес_сервера -p 10051 -s Имя_ИБП_в_Заббикс -k Ключ -o Значение
    

    In principle, this is enough for interviewing two UPSs, by this time I have a third one to test, and in the future there will be a fourth one, so I chose the first one that came across, and by a strange coincidence, the only Linux server on the second VMware host and dropped to its COM ports.

    I did not use the agent of zabbiks or copy zabbix_sender, I found the description of the last one on

    All salt is to encode the data in Base64. For verification, I used the command

    echo"<req>\n<host>S3JhdWxlck1lbW9SVDIwMDA=</host>\n<key>RnJlcQ==</key>\n<data>NDkuNA==</data>\n</req>\n" | nc -q 0 192.168.53.23 10051
    where 192.168.53.23 is the Zabbix server address. She works.
    I called the above script zabbix_sender.pl and put it on the server, the script looks like this:
    #!/usr/bin/perluse IO::Socket;
    use IO::Select;
    use MIME::Base64;
    my ($zabbixserver,$hostname,$item,$data) = @_;
    $zabbixserver= @ARGV[0];
    $hostname= @ARGV[1];
    $item= @ARGV[2];
    $data= @ARGV[3];
     my $timeout=10;
     my $request=sprintf("<req>\n<host>%s</host>\n<key>%s</key>\n<data>%s</data>\n</req>\n",
     encode_base64($hostname),encode_base64($item),encode_base64($data));
     my $sock = new IO::Socket::INET ( PeerAddr => $zabbixserver, PeerPort =>'10051', Proto =>'tcp', Timeout => $timeout);
     die"Could not create socket: $!\n"unless $sock;
     $sock->send($request);
     my @handles=IO::Select->new($sock)->can_read($timeout);
     if (scalar(@handles) > 0)
     {
      $sock->recv($result,1024);
      print"answer from zabbix server $zabbixserver: $result\n";
     }
     else
     {
      print"no answer from zabbix server\n";
     }
     $sock->close();
    

    Perl was already on the server.

    Next, you need a script that would link all the components together and which could be placed in Cron.

    Here is an example for one port, for another port you can simply copy the same code and substitute another name. The cycle did not, weakly imagine a car with more than 2 COM ports.

    #!/bin/bash
    array=( $(/путь/uniups /dev/ttyS0) )
    Names=( InVolt FaultVolt OutVolt Current Freq UBatt UTemp NA )
    correct[5]="24.0"
    j=0;
    echo"Checking UPS on serial A - ${#array[@]}"if  [ ${#array[@]} -gt "7" ]
    then
    param=""for i in"${array[@]}"doif [[ ${correct[$j]} ]]
          then
             param=$( echo"scale = 0; $i * ${correct[$j]}" | bc)
           else.
          param=$ifi
       /путь/zabbix_sender.pl 192.168.53.23 KraulerMemoRT2000 ${Names[$j]}$param
       j=$j+1;
    doneelseecho"No data on A"fi


    Small comments:
    • array is an array returned by the UPS polling program, Names is an array named Item, items with the same names must be entered on the Zabbix server. KraulerMemoRT2000 is the name of a bespereboynik, must match the host name on the server.
    • there was a bug, the last value returned by the script is perceived by the Zabbix server as incorrect, whatever, I did not find the reason, just added the NA element, which is basically sent to nowhere, you don’t need to start it on the server, after which everything became normal.
    • A check is performed on the number of elements in the array $ {# array [@]} returned when the UPS is polled. This check cuts errors when reading from the COM port, including when the UPS is turned off. In the latter case, an interesting error occurred before the patch: The first data element in the array was sent with an empty value, respectively, a data error occurred on the server side, and the trigger on this element did not work. And what is the saddest thing is that the data were counted as received, that is, the monitoring output the data obtained under the guise of actual, while the UPS was turned off.
    • correct [5] = "24.0" is the battery voltage correction (the 5th element in the array), in the event that the UPS returns the voltage on the element (battery voltage / cell). I have 6 elements in the battery, 4 pieces in series, a total of 24. In principle, this is described in the protocol. I considered it unnecessary to create a separate element, since all the UPS units are monitored by me and all 48 volts. When monitoring non-volt UPSs, of course, it will be necessary to change the structure somewhat, it is possible that the battery parameters on the server will be optimally clogged.


    The above script is added to cron for every minute execution. Setting zabbiksa in this article is not considered.

    Basically, that's all. I would be glad if the information collected is useful to someone.

    Read Next