Apoyándonos en el post anterior como se chequea el sensor de temperatura y humedad DHT22 desde una placa Arduino, decidimos agregarle un poco de complejidad al proyecto e incluir este sensor a nuestro sistema de monitoreo Icinga (ex Nagios), quien sensará la placa mediante el protocolo snmp. Para ello debemos incluir nuevas librerías que nos facilitarán el manejo de este protocolo.
Agentuino
Agentuino es una librería ligera de SNMP que implementa la versión 1 del protocolo. Actualmente la base del desarrollo actual es sincrónico, esto significa que el sistema no ejecutará ningún otro código hasta que procese la solicitud y envíe la respuesta al solicitante. A continuación resaltamos las líneas de mayor relevancia en el proyecto:
SNMP_PDU pdu; // Define la clase const char snmp_temperature[] PROGMEM = "1.3.6.1.3.2016.5.1.0"; // OID Custom definido para Temperatua ... void pduReceived() ... else if ( strcmp_P(oid, snmp_temperature ) == 0 ) { // Si es el OID que nos interesa ... } else { // viene del if ( pdu.type == SNMP_PDU_SET ) status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(temperature,6,2,result)); // Retorna el valor temperature casteado de Float void setup(){ api_status = Agentuino.begin(); // Inicializa el objeto if ( api_status == SNMP_API_STAT_SUCCESS ) { Agentuino.onPduReceive(pduReceived); // Llama a pduReceived() ... void loop(){ Agentuino.listen(); // Espera solicitudes de SNMP entrantes } |
Limitaciones
Las limitaciones de Agentuino están dadas por la cantidad de SRAM disponible.
- Longitud máxima del Nombre de comunidad (Get / Set): 20 bytes
- Longitud máxima del identificador de objetos: 64 bytes
- Longitud máxima del valor: 64 bytes
- Longitud máxima del paquete: 153 bytes
Requerimientos
Los desarrolladores especifican las siguientes dependencias de software:
- Ethernet Library 0019 o superior
- SPI Library 0019 o superior
- Streaming Library
- Flash Library
- MemoryFree Library
Referencias de la librería Agentuino
https://code.google.com/archive/p/agentuino/
https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol
https://github.com/1sw/Agentuino
https://github.com/PaulStoffregen/SPI
https://github.com/geneReeves/ArduinoStreaming
https://github.com/mikalhart/Flash
https://github.com/mpflaga/Arduino-MemoryFree
DHT
Esta librería nos permite realizar de manera sencilla la lectura de los sensores de temepratura y humedad DHT11 y DHT22 sin tener que preocuparnos por el protocolo de comunicación entre el Arduino y los sensores. Las siguientes lineas definen los pasos mas importantes para obtener una lectura del sensor:
#define DHTPIN 2 // Nro. de pin donde se encuentra conectado el sensor #define DHTTYPE DHT22 // Modelo de sensor, puede ser DHT11, DHT21 o DHT22 DHT dht(DHTPIN, DHTTYPE); // Definicion del sensor dht.begin(); // Inicializacion del sensor float humidity = dht.readHumidity(); // Lectura de humedad float temperature = dht.readTemperature(); // Lectura de Temperatura en grados Celsius, con "true" como parametro del metodo readTemperature se obtiene el valor en grados Fahrenheit |
Referencias de la librería DHT
http://www.naylampmechatronics.com/blog/40_Tutorial-sensor-de-temperatura-y-humedad-DHT1.html
https://github.com/adafruit/DHT-sensor-library
El código completo
/************************************************* Como sensar temperatura y humedad con un Arduino Uno y el sensor DHT22 para enviando los datos por snmp. Version: 0.1 Fecha: 08/02/2018 Mas informacion: https://pablo.sarubbi.com.ar **************************************************/ #include <Ethernet.h> // Libreria para manejo de pila de TCP/IP #include <SPI.h> // Requerido por Libreria Agentuino #include <Streaming.h> // Requerido por Libreria Agentuino #include <Flash.h> // Requerido por Libreria Agentuino #include <MemoryFree.h> // Requerido por Libreria Agentuino #include <Agentuino.h> // Libreria que implementa el protocolo SMNPv1 #include <DHT.h> // Libreria que implementa de manera sencilla la lectura del sensor de temperatura/humedad static byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //Mac Address for Arduino Ethernet Shield static byte ip[] = { 192, 168, 1, 100 }; static byte ip_dns[] = { 192, 168, 1, 1 }; static byte ip_gateway[] = { 192, 168, 1, 1}; static byte subnet[] = { 255, 255, 0, 0 }; //Constants #define DHTPIN 2 // Nro. de pin donde se encuentra conectado el sensor #define DHTTYPE DHT22 // Modelo de sensor, puede ser DHT11, DHT21 o DHT22 DHT dht(DHTPIN, DHTTYPE); // Inicializacion del sensor //Variables float humidity=0; float temperature=0; char result[8]; const char sysDescr[] PROGMEM = "1.3.6.1.2.1.1.1.0"; // System Description const char sysContact[] PROGMEM = "1.3.6.1.2.1.1.4.0"; // System Contact const char sysName[] PROGMEM = "1.3.6.1.2.1.1.5.0"; // System Name const char sysLocation[] PROGMEM = "1.3.6.1.2.1.1.6.0"; // System Location const char sysServices[] PROGMEM = "1.3.6.1.2.1.1.7.0"; // System Services //My Custom OIDs const char snmp_temperature[] PROGMEM = "1.3.6.1.3.2016.5.1.0"; // Temperatua en grados Celsius const char snmp_humidity[] PROGMEM = "1.3.6.1.3.2016.5.1.1"; // Humedad porcentual // RFC1213 local values static char locDescr[] = "SNMP - Arduino - DHT22"; // read-only (static) static char locContact[50] = "Contacto"; // should be stored/read from EEPROM - read/write (not done for simplicity) static char locName[20] = "Nombre"; // should be stored/read from EEPROM - read/write (not done for simplicity) static char locLocation[20] = "BA - Argentina"; // should be stored/read from EEPROM - read/write (not done for simplicity) static int32_t locServices = 2; // read-only (static) uint32_t prevMillis = millis(); char oid[SNMP_MAX_OID_LEN]; SNMP_API_STAT_CODES api_status; SNMP_ERR_CODES status; //EthernetServer server(80); void pduReceived() { SNMP_PDU pdu; api_status = Agentuino.requestPdu(&pdu); // if ((pdu.type == SNMP_PDU_GET || pdu.type == SNMP_PDU_GET_NEXT || pdu.type == SNMP_PDU_SET) && pdu.error == SNMP_ERR_NO_ERROR && api_status == SNMP_API_STAT_SUCCESS ) { // pdu.OID.toString(oid); if ( strcmp_P(oid, sysDescr ) == 0 ) { // handle sysDescr (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read-only pdu.type = SNMP_PDU_RESPONSE; pdu.error = SNMP_ERR_READ_ONLY; } else { // response packet from get-request - locDescr status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locDescr); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } } else if ( strcmp_P(oid, sysName ) == 0 ) { // handle sysName (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read/write status = pdu.VALUE.decode(locName, strlen(locName)); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } else { // response packet from get-request - locName status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locName); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } // } else if ( strcmp_P(oid, sysContact ) == 0 ) { // handle sysContact (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read/write status = pdu.VALUE.decode(locContact, strlen(locContact)); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } else { // response packet from get-request - locContact status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locContact); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } // } else if ( strcmp_P(oid, sysLocation ) == 0 ) { // handle sysLocation (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read/write status = pdu.VALUE.decode(locLocation, strlen(locLocation)); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } else { // response packet from get-request - locLocation status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, locLocation); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } // } else if ( strcmp_P(oid, sysServices) == 0 ) { // handle sysServices (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read-only pdu.type = SNMP_PDU_RESPONSE; pdu.error = SNMP_ERR_READ_ONLY; } else { // response packet from get-request - locServices status = pdu.VALUE.encode(SNMP_SYNTAX_INT, locServices); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } // } else if ( strcmp_P(oid, snmp_temperature ) == 0 ) { // handle sysName (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read-only pdu.type = SNMP_PDU_RESPONSE; pdu.error = SNMP_ERR_READ_ONLY; } else { status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(temperature,6,2,result)); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } } else if ( strcmp_P(oid, snmp_humidity ) == 0 ) { // handle sysName (set/get) requests if ( pdu.type == SNMP_PDU_SET ) { // response packet from set-request - object is read-only pdu.type = SNMP_PDU_RESPONSE; pdu.error = SNMP_ERR_READ_ONLY; } else { status = pdu.VALUE.encode(SNMP_SYNTAX_OCTETS, dtostrf(humidity,6,2,result)); pdu.type = SNMP_PDU_RESPONSE; pdu.error = status; } } else { // oid does not exist // response packet - object not found pdu.type = SNMP_PDU_RESPONSE; pdu.error = SNMP_ERR_NO_SUCH_NAME; } Agentuino.responsePdu(&pdu); } Agentuino.freePdu(&pdu); } void setup() { dht.begin(); Ethernet.begin(mac, ip, ip_dns, ip_gateway, subnet); // Se inicializa Ethernet Shield api_status = Agentuino.begin(); // Begin Snmp agent on Ethernet shield if ( api_status == SNMP_API_STAT_SUCCESS ) { Agentuino.onPduReceive(pduReceived); delay(10); return; } delay(10); } void loop() { Agentuino.listen(); //Listen/Handle for incoming SNMP requests if(millis()-prevMillis>2000){ humidity = dht.readHumidity(); temperature = dht.readTemperature(); prevMillis = millis(); } } |
El sistema funcionando se puede consultar desde una consola con el comando snmpget
pablo@laptop:~# watch -n 5 snmpget -v 1 -c public 192.168.1.100 1.3.6.1.3.2016.5.1.0 Every 1.0s: snmpget -v 1 -c public 10.10.0.75 1.3.6.1.3.2016.5.1.0 Thu Feb 8 13:40:08 2018 iso.3.6.1.3.2016.5.1.0 = STRING: " 28.60" |
Configurando Icinga o Nagios
Ene el directorio /etc/nagios-plugins/config/ vamos a crear un nuevo comando llamado check_snmp_temp el cual ejecutará una consulta similar a la que hicimos con el comando snmpget, al que le adicionaremos dos argumentos que serán los valores aceptados para notificaciones por Warning y Error.
# 'snmp_temp' command definition define command{ command_name check_snmp_temp command_line /usr/lib/nagios/plugins/check_snmp -H '$HOSTADDRESS$' -C 'public' -o 1.3.6.1.3.2016.5.1.0 -w$ARG1$ -c$ARG2$ -l temp } |
Una vez definido el comando check_snmp_temp, agregamos un host MonitorDeTemperatura con este servicio de la siguiente manera:
define host{ use generic-host host_name MonitorDeTemperatura alias MonitorDeTemperatura address 192.168.1.100 notification_period 24x7 } define service{ use generic-service host_name MonitorDeTemperatura notifications_enabled 0 service_description Servicio de Temperatura check_command check_snmp_temp!26!30 } |
Estos parámetros hacen que el Icinga de un alerta por Warning cuando el monitor de temperatura pase los 26 grados centrígrados, y un aviso de Error cuando se superen los 30 grados centrígrados.