Эта статья описывает процесс настройки мониторинга источников бесперебойного питания (ИБП) с использованием системы мониторинга Zabbix. Основные моменты:

Автор решал задачу мониторинга разных моделей ИБП (Ippon, Powercom, Krauler) недорогим способом.

Вместо SNMP-модулей было решено использовать подключение по последовательным портам (RS-232).

Описывается процесс подбора и изготовления кабелей для подключения ИБП к серверам.

Автор разработал программу на C++ для опроса ИБП через COM-порт и получения данных о состоянии.

Рассказывается о настройке виртуальной машины на VMware ESXi для размещения программы опроса и скриптов.

Описывается процесс отправки полученных данных в Zabbix с использованием утилиты zabbixsender и самописного скрипта на Perl.

Приводится bash-скрипт для автоматизации процесса опроса ИБП и отправки данных в Zabbix.

Обсуждаются некоторые проблемы и их решения, например, коррекция данных о напряжении батарей для разных моделей ИБП.

Статья представляет собой подробное техническое руководство по реализации бюджетного решения для мониторинга ИБП в среде Zabbix. CopyRetry

#include <stdio.h>   /*   / */
#include <string>
#include <iostream>
#include <cstring>

using namespace std;
#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];/*      */
int main (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;
            }
            else if (!strcmp(argv[2],"help"))
            {
                printf ("Денег нет. Но вы держитесь!\r\n");
            }
            else if (!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);

            }
            else if (!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);
}
else
printf("Usage %s /dev/ttySx", argv[0]);

}

root@zabbix:~# ./uniups /dev/ttyS0

204.4 204.4 204.4 035 49.9 54.8 54.5

root@zabbix:~# ./uniups /dev/ttyS0 name

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:~#

zabbixsender -z адрессервера -p 10051 -s ИмяИБПвЗаббикс -k Ключ -o Значение

С отправкой данный получился зоопарк на все случаи жизни. На сервере Заббикс воспользовался утилитой zabbixsender для ускорения процесса.

zabbixsender -z адрессервера -p 10051 -s ИмяИБПвЗаббикс -k Ключ -o Значение

Вся соль заключается в кодировании данных в Base64. Для проверки я использовал команду

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

    zabbix_sender.pl    ,     :
#!/usr/bin/perl

use 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();
#!/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[@]}"
do
   if [[ ${correct[$j]} ]]
      then
         param=$( echo "scale = 0; $i * ${correct[$j]}" | bc)
       else.
      param=$i
    fi
   //zabbix_sender.pl 192.168.53.23 KraulerMemoRT2000 ${Names[$j]} $param
   j=$j+1;
done
else
echo "No data on A"
fi

Небольшие комментарии:

array — массив, который возвращает программа опроса ИБП, Names — массив с именами Item, на Zabbix сервере должны быть заведены элементы с такими же именами. KraulerMemoRT2000 — имя бесперебойника, должно совпадать с именем хоста на сервере.

возник баг, последнее возвращаемое скриптом значение, воспринимается сервером Zabbix как некорректное, независимо, причину не нашел, просто добавил элемент NA, который в принципе отсылается в никуда, на сервере его заводить не надо, после чего все стало нормально.

производится проверка на количество элементов массива ${#array[@]}, возвращаемых при опросе ИБП. Эта проверка отсекает ошибки при чтении с СОМ порта, в том числе когда ИБП выключен. В последнем случае до патча возникала интересная ошибка: Первый в массиве элемент данных отсылался с пустым значением, соответственно на стороне сервера возникала ошибка данных, а триггер по этому элементу не срабатывал. И что самое печальное, что данные числились полученными, т.е мониторинг выводил полученные данные под видом актуальных, в то время, как ИБП был отключен.

correct[5]=«24.0» это коррекция напряжения для батарей (5ый элемент в массиве), в том случае если ИБП возвращает напряжение на элементе (battery voltage/cell). У меня 6 элементов в батарее, 4 шт последовательно, итого 24. В принципе это описано в протоколе. Я посчитал лишним создавать отдельный элемент, так как все ИБП у меня мониторятся по шаблону и у всех 48 вольт. При мониторинге разновольтовых ИБП, конечно, надо будет несколько поменять структуру, возможно оптимальным будет забить параметры батарей на сервере.

Вышеописанный скрипт добавлен в cron на ежеминутное выполнение. Настройка заббикса в данной статье не рассматривается.

https://habr.com/ru/articles/397637/