Zabbix Arduino

Zabbix Arduino

Вторая статья на тему применения Arduino в сочетании с Zabbix
В связи с тем, что первый эксперимент прошел удачно Zabbix мониторинг температуры через Arduino и проработал уже несколько месяцев, было решено оборудовать вторую серверную таким же решением
На этот раз Arduino nano будет использоваться вместе с сетевым модулем.

Закупаемся необходимыми компонентами

  • 1 датчик DHT11
  • 2 датчика DHT22 (один из них был куплен уже готовым. с припаянным резистором на 10кОм)
  • 1 Arduino nano
  • 1 Сетевой модуль Enc28J60. Можно и другой, тут уж какой был
  • 1 Датчик воды
  • рассыпуха из светодиодов и резисторов
  • кабель mini usb
  • зарядник от телефона с usb разъемом

Сборка

Собираем всё по схеме. Можно на макетной плате, можно сразу запаивать
Сама arduino nano оказалась китайским клоном Nano 3.0 (CH340G)
Вывод dmesg

[246148.113293] usb 3-4: new full-speed USB device number 14 using xhci_hcd
[246148.262257] usb 3-4: New USB device found, idVendor=1a86, idProduct=7523
[246148.262261] usb 3-4: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[246148.262263] usb 3-4: Product: USB2.0-Serial
[246148.262709] ch341 3-4:1.0: ch341-uart converter detected
[246148.263117] usb 3-4: ch341-uart converter now attached to ttyUSB0

Система Ubuntu 18.04
Изначально прошивка не заливалась programmer is not responding
Решением: на этапе прошивки пощелкать кнопку RESET на arduino. Лишь до первой, удачной прошивки.
Заливаем чистую прошивку. Просто открыть arduino ide и залить базовый код
Так же выбрать плату Arduino Nanon и тип процессора выбрать atmega328p old bootloader (почему именно old не знаю, но работает только с ним)
На этапе сборки и отладки у меня, что-то где-то закоротило и из arduino nano пошел дымок. Путем изучения интернетов было установлено что это перегорел диод шоттки.
Поэтому всю конструкцию я собрал на макетной плате arduino uno, которую использовал в первом проекте (см. ссылку)
Изучения интернета показали что можно перепаять диод, но можно и запаять перемычку. Так как я не электронщик то диодов у меня нет, после запайки перемычки arduino nano ожила, стала определятся компьютером и после сборки все работает как надо по сей момент.
Расположение сетевого модуля, не позволяет его компактно разместить в коробке. Именно из-за разъемов ethernet и usb.
Сам сетевой модуль запаивать тоже оказалось не очень удобно.

Код

#include <DHT.h>
#include "etherShield.h"
#include "ETHER_28J60.h"

// Объявляем светодиоды
#define SYS_LED 7
#define DATA_LED 8
#define WATER_LED 9

// Объявляем температуры
#define TMIN 1
#define TMAX 25

// Объявляем датчики температуры
DHT data1(3, DHT11, 3);
DHT data2(4, DHT22, 3);
DHT data3(5, DHT22, 3);

// Объявляем датчик воды
int data4 = 6;

float temp1;
float temp2;
float temp3;
boolean water;

unsigned long previousMillis = 0;
unsigned long interval = 15000;

// Определяем MAC и IP дрес
static uint8_t mac[6] = {0x54, 0x55, 0x58, 0x10, 0x00, 0x24};
static uint8_t ip[4] = {192, 168, 116, 34};
static uint16_t port = 80;

ETHER_28J60 e;

// Функция перезагрузки
// Пользуется когда отваливается датчик
void(* resetFunc) (void) = 0;

void setup()
{
  pinMode(SYS_LED, OUTPUT); pinMode(DATA_LED, OUTPUT); pinMode(WATER_LED, OUTPUT);
  digitalWrite(SYS_LED, HIGH); delay(100); digitalWrite(SYS_LED, LOW); delay(100);
  digitalWrite(DATA_LED, HIGH); delay(100); digitalWrite(DATA_LED, LOW); delay(100);
  digitalWrite(WATER_LED, HIGH); delay(100); digitalWrite(WATER_LED, LOW); delay(100);

  // Начинаем опрос датчиков
  data1.begin();
  data2.begin();
  data3.begin();

  // Сразу читаем данные чтобы не были 0
  temp1 = data1.readTemperature();
  temp2 = data2.readTemperature();
  temp3 = data3.readTemperature();
  water = !(digitalRead(data4));

  // Вызов сети
  e.setup(mac, ip, port);

  digitalWrite(SYS_LED, HIGH);
}

void loop()
{
  // Пытаемся параллельно читать данные каждые 15 секунд
  if (millis() - previousMillis > interval) {
    previousMillis = millis();
    dread();
  }

  // Вывод вэб интерфейса
  web();
}

// Веб интерфейс
void web()
{
  if (e.serviceRequest())
  {
    // Мыргаем обращением из вне
    digitalWrite(SYS_LED, !digitalRead(SYS_LED)); delay(20); digitalWrite(SYS_LED, !digitalRead(SYS_LED));

    // Выводим в браузер
    e.print("<meta charset=utf-8 http-equiv=refresh content=60>");
    e.print("<h1 align=center><br><br><b>Тайная комната</b></h1><br>");
    e.print("<h2 align=center>Кондиционер : <font color=red>"); e.print(temp1); e.print("</font>C</h2>");
    e.print("<h2 align=center>Серверный шкаф : <font color=red>"); e.print(temp2); e.print("</font>C</h2>");
    e.print("<h2 align=center>Улица : <font color=red>"); e.print(temp3); e.print("</font>C</h2>");
    e.print("<h2 align=center>Затопление : <font color=red>"); e.print(water); e.print("</font></h2>");
    e.respond();
  }
  delay(5);
}

// Чтение данных
void dread()
{
  // Читаем данные датчиков
  temp1 = data1.readTemperature();
  temp2 = data2.readTemperature();
  temp3 = data3.readTemperature();
  water = !(digitalRead(data4));

  // Если отвалился перезагружаемся, вдруг оживет
  if (isnan(temp1) || isnan(temp2) || isnan(temp3)) {
    resetFunc();
    return;
  }

  // Мыргаем Чтением
  digitalWrite(DATA_LED, !digitalRead(DATA_LED)); delay(20); digitalWrite(DATA_LED, !digitalRead(DATA_LED));
  digitalWrite(WATER_LED, !digitalRead(WATER_LED)); delay(20); digitalWrite(WATER_LED, !digitalRead(WATER_LED));

  // Мыргаем ошибками
  if ( temp1 > TMAX || temp1 < TMIN ) digitalWrite(DATA_LED, HIGH); else digitalWrite(DATA_LED, LOW);
  if ( temp2 > TMAX || temp2 < TMIN ) digitalWrite(DATA_LED, HIGH); else digitalWrite(DATA_LED, LOW);
  if (water) digitalWrite(WATER_LED, HIGH); else digitalWrite(WATER_LED, LOW);
}

Логика работы кода

На старте опрашиваем датчики. Затем каждые 15 секунд
Красный светодиод горит постоянно. изображая работу системы
Если проблема с температурой, горит зеленый светодиод
Если затопление. горит желтый
Когда идет опрос web, моргает красный
Если проблема решена, светодиоды гаснут (желтый, зеленый)
Проблема – превышение температуры выше 25 градусов, или ниже 1 градуса. Так же затопление

Фиксы
Через некоторое время, больше нескольких часов, температура с третего датчика, а именно DHT22 в готовом виде, становилась равная 0. После нажатия кнопки перезагрузки сразу появлялась.
Решение – введение функции RESET. Если любой из термо датчиков возвращает полный ноль, то система перезагружается

void(* resetFunc) (void) = 0;
resetFunc();

Любая задержка в коде при выводе web интерфейса приводила к тому что он переставал работать
Сейчас каждый 15 секунд на момент опроса датчиков, сильно проседает PING. Поэтому для узла Arduino в мониторинге Zabbix, отлючены триггеры High ICMP ping loss и High ICMP ping response time из шаблона Template Module ICMP Ping, иначе заспамит ошибками
Библиотека для сетевого модуля бралась из разных источников
Можно взять готовый zip с форума амперки
Можно взять на гитхабе для ручной установки
В обоих случаях у меня была ошибка
Arduino/libraries/ACEduinoEthernetShield/etherShield.cpp:8:25: fatal error: EtherShield.h: No such file or directory
Решилось сменой регистра в файле etherShield.cpp

#include "EtherShield.h"

Меняем на

#include "etherShield.h"

Читаем данные

На web страницы данные представлены достаточно просто.
Автообновление страницы задано в коде на каждую минуту

refresh content=60

Основная задача считать эти данные в Zabbix.
Так как устройство сетевое, то необходим заббикс агент. Будем все делать на том же сервере где и первый
Поискав адекватные методы парсинга вэб страниц пришел к выводу что такие если и есть, то их скрывают =.=”
Ни на python ни где бы то либо еще.
В итоге будем делать через curl, опираясь на то что в принципе на странице меняются только данные. Каждый пробел становится критичным. Но работает
Будем приводить к виду как в первой статье про Zabbix чтобы не путаться на сервере.
Опишу боле менее просто. Подробнее в первой статье
Скрипт serv_temp2

#!/bin/bash
echo "Temp_1:" "$(curl 192.168.116.34 2>/dev/null | awk '{print $10}' | cut -d ">" -f2 | cut -d "<" -f1)"
echo "Temp_2:" "$(curl 192.168.116.34 2>/dev/null | awk '{print $15}' | cut -d ">" -f2 | cut -d "<" -f1)"
echo "Temp_3:" "$(curl 192.168.116.34 2>/dev/null | awk '{print $19}' | cut -d ">" -f2 | cut -d "<" -f1)"
echo "Water:" "$(curl 192.168.116.34 2>/dev/null | awk '{print $23}' | cut -d ">" -f2 | cut -d "<" -f1)"
exit 0

Cron

*/5 * * * * /home/virtual/script/server2_temp > /home/virtual/script/temp2.txt

Zabbix Agent

UserParameter=s2temp1[*],cat /home/virtual/script/temp2.txt | grep Temp_1 | cut -d " " -f2
UserParameter=s2temp2[*],cat /home/virtual/script/temp2.txt | grep Temp_2 | cut -d " " -f2
UserParameter=s2temp3[*],cat /home/virtual/script/temp2.txt | grep Temp_3 | cut -d " " -f2
UserParameter=s2water[*],cat /home/virtual/script/temp2.txt | grep Water | cut -d " " -f2

Zabbix

В Zabbix создаем новый шаблон. В нем данные, триггеры, графики
Добавляем на карту. Все как в первой статье, только с новыми переменными
Триггера на улицу нет
В отличии от первой статьи, в триггерах нет режима восстановление.
Оказалось в этом случае он не обязателен

PS: Важные доработки

Спустя более месяца наблюдений, выявились недостатки
Периодические зависания
Просто останавливалась сеть. Так же было выявлено что если с любого ПК запускать сканер сети, а именно

arp-scan -l

Вернуть обратно можно было только отключением питания, или если arduino подключен к ПК то повторной прошивкой скетча
Решение:
Увеличить интервал в выводе WEB интерфейса

// Веб интерфейс
void web()
{
  if (e.serviceRequest())
  {
    // Мыргаем обращением из вне
    digitalWrite(SYS_LED, !digitalRead(SYS_LED)); delay(10); digitalWrite(SYS_LED, !digitalRead(SYS_LED));

    // Выводим в браузер
    e.print("<meta charset=utf-8 http-equiv=refresh content=60><center><h1><br><br><b>Тайная комната</b></h1><br><h2>Кондиционер : <font color=red>");
    e.print(temp2);
    e.print("</font>C</h2><h2>Серверный шкаф : <font color=red>");
    e.print(temp1);
    e.print("</font>C</h2><h2>Улица : <font color=red>");
    e.print(temp3);
    e.print("</font>C</h2><h2>Затопление : <font color=red>");
    e.print(water);
    e.print("</font></h2></center>");
    e.respond();
  }
  delay(100);
}

Так же немного сократил количество e.print, но это не главное. Как итог получаем очень большой ping, зато на данный момент месяц непрерывной работы. И на момент запуск сканера сети ping увеличивается но потом восстанавливается

64 bytes from 192.168.116.34: icmp_seq=1 ttl=64 time=1459 ms
64 bytes from 192.168.116.34: icmp_seq=2 ttl=64 time=549 ms
64 bytes from 192.168.116.34: icmp_seq=3 ttl=64 time=49.1 ms
64 bytes from 192.168.116.34: icmp_seq=4 ttl=64 time=1014 ms
64 bytes from 192.168.116.34: icmp_seq=5 ttl=64 time=410 ms
64 bytes from 192.168.116.34: icmp_seq=6 ttl=64 time=10.4 ms
64 bytes from 192.168.116.34: icmp_seq=7 ttl=64 time=111 ms
64 bytes from 192.168.116.34: icmp_seq=8 ttl=64 time=10.6 ms
64 bytes from 192.168.116.34: icmp_seq=9 ttl=64 time=112 ms
64 bytes from 192.168.116.34: icmp_seq=10 ttl=64 time=113 ms
64 bytes from 192.168.116.34: icmp_seq=11 ttl=64 time=229 ms
64 bytes from 192.168.116.34: icmp_seq=12 ttl=64 time=546 ms
64 bytes from 192.168.116.34: icmp_seq=13 ttl=64 time=462 ms
64 bytes from 192.168.116.34: icmp_seq=14 ttl=64 time=76.7 ms
64 bytes from 192.168.116.34: icmp_seq=15 ttl=64 time=77.2 ms
64 bytes from 192.168.116.34: icmp_seq=16 ttl=64 time=78.5 ms
64 bytes from 192.168.116.34: icmp_seq=17 ttl=64 time=78.8 ms
64 bytes from 192.168.116.34: icmp_seq=18 ttl=64 time=78.0 ms
64 bytes from 192.168.116.34: icmp_seq=19 ttl=64 time=841 ms
64 bytes from 192.168.116.34: icmp_seq=20 ttl=64 time=343 ms
64 bytes from 192.168.116.34: icmp_seq=21 ttl=64 time=44.5 ms
64 bytes from 192.168.116.34: icmp_seq=22 ttl=64 time=45.9 ms
64 bytes from 192.168.116.34: icmp_seq=23 ttl=64 time=47.1 ms
64 bytes from 192.168.116.34: icmp_seq=28 ttl=64 time=9982 ms
64 bytes from 192.168.116.34: icmp_seq=29 ttl=64 time=9460 ms
64 bytes from 192.168.116.34: icmp_seq=30 ttl=64 time=8838 ms
64 bytes from 192.168.116.34: icmp_seq=31 ttl=64 time=8116 ms
64 bytes from 192.168.116.34: icmp_seq=32 ttl=64 time=7795 ms
64 bytes from 192.168.116.34: icmp_seq=33 ttl=64 time=7273 ms
64 bytes from 192.168.116.34: icmp_seq=34 ttl=64 time=6852 ms
64 bytes from 192.168.116.34: icmp_seq=35 ttl=64 time=6330 ms
64 bytes from 192.168.116.34: icmp_seq=36 ttl=64 time=5608 ms
64 bytes from 192.168.116.34: icmp_seq=37 ttl=64 time=5186 ms
64 bytes from 192.168.116.34: icmp_seq=38 ttl=64 time=4990 ms
64 bytes from 192.168.116.34: icmp_seq=39 ttl=64 time=4694 ms
64 bytes from 192.168.116.34: icmp_seq=40 ttl=64 time=4197 ms
64 bytes from 192.168.116.34: icmp_seq=41 ttl=64 time=3799 ms
64 bytes from 192.168.116.34: icmp_seq=42 ttl=64 time=3401 ms
64 bytes from 192.168.116.34: icmp_seq=43 ttl=64 time=2803 ms
64 bytes from 192.168.116.34: icmp_seq=44 ttl=64 time=2306 ms
64 bytes from 192.168.116.34: icmp_seq=45 ttl=64 time=1909 ms
64 bytes from 192.168.116.34: icmp_seq=46 ttl=64 time=1009 ms
64 bytes from 192.168.116.34: icmp_seq=47 ttl=64 time=512 ms
64 bytes from 192.168.116.34: icmp_seq=48 ttl=64 time=215 ms
64 bytes from 192.168.116.34: icmp_seq=49 ttl=64 time=779 ms
64 bytes from 192.168.116.34: icmp_seq=50 ttl=64 time=81.2 ms
64 bytes from 192.168.116.34: icmp_seq=51 ttl=64 time=83.0 ms

Если глюк продолжится, то возможно придется переписывать код с использованием новой библиотеки EtherCard

Отрицательные значения Zabbix
Для отображения отрицательных значений на графике и в данных Zabbix, необходимо у элементов данных установить тип информации “Число (с плавающей запятой)”

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *