У нас в организации развёрнут сервер Zabbix для мониторинга работоспособности серверов и АРМов. Из-за особенностей техпроцесса оборудование «размазано» по нескольким помещениям и разнесено по территории предприятия. Естественно, вместе с основными параметрами компьютеров (работает/не работает) хочется контролировать и микроклимат в серверных. При этом, как обычно, возможности весьма ограничены, и «выбить» значительные средства на сложные системы мониторинга температуры (к ним я отношу и платы управления с термодатчиками для стоечных ИБП APC) — это отдельный квест.
В основной серверной всё просто — установлена одна такая плата (закуплена давным-давно предшественником вместе с основным оборудованием), воткнут APC-шный термодатчик, заведён агент в Заббиксе, всё работает по SNMP. Скучно :) На мониторинг удалённой аппаратной оборудования нет, средств тоже — см. выше. Поэтому было решено проявить смекалку, сэкономить бюджет и заодно прокачать новый навык путём конструирования простого и дешёвого «наколенного» решения, вписывающегося, тем не менее, в существующую инфраструктуру мониторинга Zabbix.
Необходимые компоненты:
- Основа системы — Arduino Nano V3
- Модуль локальной сети (ethernet-shield)
- И, собственно, цифровой датчик температуры на базе DS18B20
Общая стоимость компонентов — $10 с доставкой.
Сборка устройства не составляет труда. Сетевой модуль надевается на основную плату «бутербродом», термодатчик припаивается к его пинам. Подключение датчика: красный +5 В, чёрный — земля, жёлтый — данные; между +5V и Data припаиваем подтягивающий резистор 4,7 кОм.
Пин для данных выбирается с учётом пинов, используемых сетевым модулем (D10 – SS; D11 – MOSI; D12 – MISO; D13 – SCK; D2 – IRQ).
Грабли: в прототипе устройства столкнулся с конфликтом — данные о температуре выдавались случайным образом, «через два на третий». Причиной оказалось то, что я прицепил термодатчик на пин 2, который, как потом нашёл на просторах интернета, используется сетевым модулем для генерации прерывания при поступлении пакета. Переставил на 4-й — заработало как часы.
После сборки аппаратной части переходим к программной.
Устройство будет работать в сети и притворяться заббикс-агентом, для этого ему нужен MAC и IP-адрес. Решаем, как удобнее — жёстко зашить при программировании, генерировать MAC из адреса температурного датчика и получать IP по DHCP, и т.д. Я пошёл по простейшему пути и захардкодил оба параметра.
Протокол обмена с заббикс-сервером описан в документации. Наше устройство будет откликаться на две команды — agent.ping и env.temp (здесь оставлен простор для дальнейшего творчества, можно привязать любой из модулей расширения, доступных для ардуино — хоть датчик влажности, хоть освещённости — да что душе угодно). На все остальные команды оно будет ругаться отвечать стандартным ответом, понятным заббикс-серверу.
Для тех, кто начинает с нуля (как я) — программирование Arduino выполняется с помощью Arduino IDE, установка и настройка которой элементарны. Для работы компонентов необходимы библиотеки UIPEthernet и OneWire, которые устанавливаются и подключаются к проекту через меню Скетч — Подключить библиотеку — Управлять библиотеками…
Если у вас будут другие компоненты (например, сетевой модуль не на enc28j60, а на другом чипе) — понадобятся и другие библиотеки!
Код работы с сетевым модулем и с датчиком температуры — типовой, из интернета, с некоторыми допущениями и упрощениями.
После заливки кода в контроллер и подключения ethernet-кабеля проверяем из консоли:
$ zabbix_get -s 192.168.4.5 -k agent.ping
1
$ zabbix_get -s 192.168.4.5 -k env.temp
23.12
$ zabbix_get -s 192.168.4.5 -k bla-blah
ZBX_NOTSUPPORTED
Грабли: выложенная на zabbix.com скомпилированная версия zabbix_get для Windows устарела и использует другой протокол (с заголовком ZBXDx01 в запросе сервера). Линуксовая версия актуальна и протокол соответствует приведенному коду.
Всё работает, как и задумано. В админке заббикса создаём новый хост с выбранным IP, в нём — два ключа, Numeric (unsigned) agent.ping и Numeric (float) env.temp, наслаждаемся работой. Графики, триггеры — всё как обычно.
Питание устройства — через родной USB. Корпус — по желанию: подходящая пластиковая коробочка, термоусадка, синяя изолента.
Разрешение датчика — примерно 0.06 (точнее, 1/16) °С, точность — при погружении в таящий снег показал 0.19 °С (может, опустился бы и ниже, но снега было мало и он весь быстро растаял). Считаю, для устройства стоимостью 10 долларов и описанных целей — более чем достаточно.
#include <OneWire.h>
#include <UIPEthernet.h>
byte mac[] = { 0xDE, 0x05, 0xB6, 0x27, 0x39, 0x19 }; // random MAC
byte ip[] = { 192, 168, 4, 5 }; // IP address in local network
String readString = String(20);
byte addr[8];
OneWire ds(4); // DS18B20 at pin 4
EthernetServer server(10050); // Zabbix port
void setup() {
Ethernet.begin(mac, ip);
server.begin();
ds.search(addr);
}
void loop() {
byte data[2];
float celsius;
readString = "";
if (EthernetClient client = server.available())
{
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (c == 'n') // end of query from zabbix server
{
client.print("ZBXDx01"); // response header
if (readString == "agent.ping") {
byte responseBytes [] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, '1'};
client.write(responseBytes, 9);
} else
if (readString == "env.temp") {
ds.reset();
ds.select(addr);
ds.write(0x44); // start conversion with regular (non-parasite!) power
delay(1000);
ds.reset();
ds.select(addr);
ds.write(0xBE); // read Scratchpad
data[0] = ds.read();
data[1] = ds.read();
int16_t raw = (data[1] << 8) | data[0];
celsius = (float)raw / 16.0;
byte responseBytes [] = {(byte) String(celsius).length(), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
client.write(responseBytes, 8);
client.print(celsius);
}
else {
byte responseBytes [] = {0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
client.write(responseBytes, 8);
client.print("ZBX_NOTSUPPORTED");
}
break;
}
else if (readString.length() < 20) {
readString = readString + c;
}
}
}
delay(10);
client.stop();
}
}
Автор: kisaa
В скетче отсутствуют обратные слеши в заголовке:
client.print(“ZBXDx01”);
и в знаке конца запроса:
(c == ‘n’).
Правильно:
client.print(“ZBXD\x01”);
(c == ‘\n’)