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.