Nagios (or its recent fork Icinga) implement an Open Source network and application monitoring system. I use Nagios extensively, and I wanted to build an Arduino monitor for it – a little gadget that sits next to my screen, where I can glance at it quickly to see if everything is running.

As you’ll see below, this project doesn’t require a particular network or service monitoring system; you should be able to easily adapt the code to display whatever you want onto the LCD.

The Naguino (I couldn’t come up with a worse name) is incredibly easy to assemble, neading neither a breadboard nor a soldering iron. It consists of the Arduino itself (I’m using a Duemillanove), the Arduino Ethernet Shield, which I’ve described before, and the S65 shield for the Arduino.

The S65 shield is designed and sold by Watterott in Germany. It has a backlit 176x132 pixel LCD display, a microSD socket, and a rotary encoder. (The small yellow tag on the top left is to peel off the protective covering of the LCD; I still have it on here.) Code and schematics for the S65 shield are here.

These photographs show the Arduino, and the Ethernet and S65 shields from left to right, respectively (stacked upon eachother) from bottom to top.

After stacking the two shields onto my Arduino, I’m ready to rumble. As I said: no soldering. :-)

The Sketch

My little Naguino monitor is fed Nagios events over HTTP POST messages. These include a state which indicates whether a particular service is OK, has a WARNING or is CRITICAL, as well as a 21 character message that shows up on the Naguino display. The messages are colour-coded: green is OK, yellow is Warning, and red means Critical.

Messages are displayed on the bottom of the LCD and “scroll up” automatically, as soon the display is full. If you want to, you can use the rotary encoder to page up and down by turning the encoder left or right respectively. (I’m doing that in the photo above.) The time on the LCD title bar is obtained during the HTTP POST to the Naguino – it is the time of the last message.

Processing HTTP on the Arduino

The Arduino Ethernet shield can be a Web (or any other TCP) server, and there exist examples for doing so, e.g. here. The best I found however, is Ben Combee’s webduino, an extensible Web server library. Its documentation is a bit scarce, but Ben gives us four very useful and well-commented examples with which to start off. Highly recommendable, and that is what Naguino uses to process Nagios alerts. So, without further ado, here is the Naguino sketch:

    #include <S65Display.h>
    #include <RotaryEncoder.h>
    #include "Ethernet.h"
    #include "WebServer.h"
    #include <stdio.h>
    static uint8_t mac[6] = { 0xBE, 0xEF, 0xEE, 0x00, 0x20, 0x01 };
    static uint8_t ip[4] = { 192, 168, 1, 177 }; 
    #define MSGLEN  21              // max length of text message (LCD line is 21 chars)
    #define MLISTSIZE       20      // size (height) of message list
    #define WINHEIGHT       6       // height of display window
    #define TIMELEN		8	// HH:MM:SS
    typedef struct msg {
            char state;             // -1 ... 2
            char msg[MSGLEN+1];
    } msg_t;
    msg_t mlist[MLISTSIZE];
    char nagtime[TIMELEN + 1] = "??:??:??";		// current time
    S65Display lcd;			// controls the LCD display
    RotaryEncoder encoder;		// controls the rotary encoder
    /* colours for LCD */
    #define C_BLACK		(RGB(0, 0, 0))
    #define C_WHITE		(RGB(255, 255, 255))
    #define C_RED		(RGB(255, 0, 0))
    #define C_YELLOW	(RGB(255, 240, 0))
    #define C_GREEN		(RGB(51, 204, 51))
    #define NAG_OK		C_GREEN
    #define NAG_WARN	C_YELLOW
    #define NAG_CRIT	C_RED
    /* all URLs on this server will start with / because of how we
     * define the PREFIX value.  We also will listen on port 80, the
     * standard HTTP service port */
    #define PREFIX "/"
    WebServer webserver(PREFIX, 80);
    /* interrupt Arduino to service the rotary encoder.
     * Arduino interrupts are described at
      TCNT2 -= 250; //1000 Hz
    /* This command is set as the default command for the Web server. It handles
     * both GET and POST requests.  For a GET, it returns a simple page.  For a
     * POST, it retrieves the POSTed values and appends lines to the display.
    void webhandler(WebServer &server, WebServer::ConnectionType type)
      uint8_t nagstate;			// Nagios state: UNKNOWN, OK, ...
      char nagmsg[MSGLEN+1];			// current message
      if (type == WebServer::POST)
        bool repeat;
        bool havestate = false, havemsg = false;
        char name[16], value[MSGLEN+1];
          /* readURLParam returns false when there are no more parameters
           * to read from the input.  We pass in buffers for it to store
           * the name and value strings along with the length of those
           * buffers. */
          repeat = server.readURLParam(name, 16, value, MSGLEN+1);
          if (strcmp(name, "state") == 0) {
    	nagstate = strtoul(value, NULL, 10);
    	havestate = true;
          } else if (strcmp(name, "msg") == 0) {
              strncpy(nagmsg, value, MSGLEN);
    	  nagmsg[MSGLEN] = '\0';
              havemsg = true;
          } else if (strcmp(name, "time") == 0) {
          	strcpy(nagtime, value);
        } while (repeat);
        /* append message to display only if we have state and text */
        if (havestate == true && havemsg == true) {
          append(nagstate, nagmsg);
      /* return success to HTTP client */
      /* no output of body for a HEAD request */
      if (type == WebServer::GET)
    	int n;
        /* store the HTML in program memory using the P macro */
        P(beginpage) = 
    "<!DOCTYPE html><html><head><title>Naguino</title>"
    "<style type='text/css'>"
    " body { color: purple; background-color: #fff }"
    " ol { margin-left: 20px; font-family: Courier; }"
    " .ok   {  white-space:pre; color: #339900; }"
    " .warn {  white-space:pre; color: #FF9900; }"
    " .crit {  white-space:pre; color: #FF0000; }"
    " .unknown {  white-space:pre; color: #909090; }"
        P(endpage) = "</ol></body></html>";
    	for (n = 0; n < MLISTSIZE; n++) {
    		server.print("<li><span class='"); 
    		switch (mlist[n].state) {
    			case 0: server.print("ok"); break;
    			case 1: server.print("warn"); break;
    			case 2: server.print("crit"); break;
    			default: server.print("unknown"); break;
    void setup()
      /* initialize LCD */
      lcd.init(4); //spi-clk = Fcpu/4
      char str[MSGLEN+1];
     /* initialize rotary encoder */
      /* initialize timer2 */
      TCCR2B  = (1<<CS22); //clk=F_CPU/64
      TCNT2   = 0x00;
      TIMSK2 |= (1<<TOIE2); //enable overflow interupt
      /* enable interrupts */
      /* clear screen */
      lcd.clear(RGB(255, 255, 255));
      sprintf(str, "Ready for HTTP");
      append(0, str);
      sprintf(str, "on %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
      append(2, str);
     /* setup the Ehternet library to talk to the Wiznet board */
      Ethernet.begin(mac, ip);
      /* register our default command (activated with the request of
       * http://x.x.x.x/ */
      /* start the server to wait for connections */
    void loop()
      int8_t move, press;
      static int lpos = MLISTSIZE - 1;
      /* process incoming HTTP connections one at a time forever. If there is no
       * connection at this moment, this function does *not* block; Arduino will
       * continue on past here, servicing the rotary encoder
      move  = encoder.step();
      press = encoder.sw();
      if (move) {
        if (move < 0) {
          lpos -= WINHEIGHT;
        } else {
          lpos += WINHEIGHT;
        lpos = (lpos < 0) ? 0 : lpos;
        lpos = (lpos >= MLISTSIZE) ? MLISTSIZE - WINHEIGHT : lpos;
    #if 0
      if (press) {  
        if(press == SW_PRESSED)
        else if(press == SW_PRESSEDLONG)
    #define CHARHEIGHT 16		// character height in pixels (incl. spacing)
    void display(int pos)
            int i, line, n, color;
    	char str[MSGLEN+1];
    	/* print title on top of lcd */
            sprintf(str, "Naguino      %-8.8s", nagtime);
            lcd.drawText(4, 0, str, C_RED, C_BLACK);
            if (pos > (MLISTSIZE - WINHEIGHT))
                    pos = MLISTSIZE - WINHEIGHT;
    	/* refresh the display. Each message is padded with blanks to
    	 * clear away the previous (longer) text on the line. Depending
    	 * on the Nagios message state, we colour the line accordingly.
            for (i = pos, line = 1; (i < (pos + WINHEIGHT)) && (i < (MLISTSIZE)); i++, line++) {
    		strcpy(str, mlist[i].msg);
    		for (n = strlen(str); n < (MSGLEN); n++) { 
    			str[n] = ' ';
    		str[n] = '\0';					// null terminate
    		switch (mlist[i].state) {
    			case  0: color = NAG_OK; break;
    			case  1: color = NAG_WARN; break;
    			case  2: color = NAG_CRIT; break;
    			default: color = NAG_UNKNOWN; break;
    		lcd.drawText(1, (line * CHARHEIGHT), str, color, C_WHITE);
    void append(int nagstate, char *nagmsg)
      int n;
      /* shuffle entries in mlist up manually, and then append current */
      for (n = 0; n < (MLISTSIZE - 1); n++) {
        mlist[n].state = mlist[n+1].state;
        memmove(mlist[n].msg, mlist[n+1].msg, MSGLEN+1);
      mlist[n].state = nagstate;
      strcpy(mlist[n].msg, nagmsg);
    /* ends */

Download the sketch for Naguino and upload that to the Arduino, after ensuring you’ve changed the IP address (the values in the ip[] array) to match your network. When Naguino initializes, it will print its IP address to the LCD.

You can easily test it by sending it a simple message with cURL:

    curl -d 'state=0&msg=hello+world'

which should print the string “hello world” in green letters on the last line of the LCD.

Configuring Nagios

Nagios is not difficult to configure, but you should familiarize yourself with it before attempting what follows.

There are two distinct methods you might choose to get Nagios to notify Naguino: notifications and event handlers. Which you choose depends a lot on your setup. I opted for event handlers because it suits my configuration better.

So, I configured a global service event handler in my nagios.cfg. This is invoked whenever a service state changes. (Note: I’m interested in service events only, but the process is similar for host events.)


I then described nagev command in the appropriate commands resource; in my setup commands are in commands.cfg:

    # Global Service Event handler for Naguino
    define command {
       command_name    nagev
       command_line    /usr/local/nagios/libexec/nagev 

My service event handler command nagev is quite simple: whenever it detects a HARD Nagios transition, it extracts the service state (OK, WARNING, …), the name of the host on which the service is running, the service name itself and POSTs that via HTTP to the Arduino Ethernet shield.

    # nagev -- Nagios Service Event Handler for Naguino
    use strict;
    use lib "/usr/local/nagios/libexec" ;
    use utils qw($TIMEOUT %ERRORS &print_revision &support);
    use vars qw($PROGNAME);
    use LWP;
    use constant ARDUINOIP => '';
    my $nagstate = $ENV{'NAGIOS_SERVICESTATE'};            # OK, CRITICAL, ...
    my $nagstatetype = $ENV{'NAGIOS_SERVICESTATETYPE'};    # HARD, SOFT
    my $nagattempt = $ENV{'NAGIOS_SERVICEATTEMPT'};        # 1 ... 3
    if ($nagstatetype eq 'HARD') {
        post2naguino($ERRORS{$nagstate}, $host, $service);
    sub post2naguino {
        my ($state, $host, $service) = @_;
        my $ua = LWP::UserAgent->new; 
        $ua->agent("Naguino 0.1");
        my $content;
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
        my $timestr = sprintf("%02d:%02d:%02d", $hour, $min, $sec);
        my $response = $ua->post('http://' . ARDUINOIP,
                "msg"   => sprintf("%-10.10s %-11.11s", $host, $service),
                "state" => "$state",
                "time"  => "$timestr",
        warn "error: ", $response->status_line
            unless $response->is_success;
        $content = $response->content; 
        print $content; 

Nagios passes the macros we need to determine service and hostnames, states, etc. into the Perl program as environment variables, making it easy to retrieve the information we need. Note how I am formatting the message in such a way as that the host and service names get a maximum width.

(Note that this will work fine for smallish Nagios installations, but on very large ones with hundreds of event notifications, the nagev program can become a bottleneck, because the Arduino Ethernet shield allows only one simultaneous connection to its TCP server stack, causing parallel requests to block until the “Web server” is available. (This is not a limitation of webduino but of the TCP code in the Ethernet.h library.) If you want to cater for large installations, I’d recommend serializing events into a file or database of some sort, having a separate standalone program feed those serialized events to Naguino.)

GETting something back

If you access Naguino with a HTTP GET request (e.g. by simply pointing a Web browser to it), Naguino will show you the list of messages it has in it – typically more than what you see on the LCD of the S65 shield. The “height” of the internal Naguino list is controlled by the definition of MLISTSIZE in the sketch.

Going further

The Naguino is extremely easy to assemble and works rather well. Some ideas for improvement:

  • Add a small piezo buzzer that sounds a tone (high-low) when a critical message is posted, and another (low-high) when the all-clear arrives.
  • Do something clever with the push-button on the rotary encoder: maybe change colours, reset Naguino, “ping” Nagios host, get/display alternate information on LCD.
  • Use the “long push” on the button to store state on a microSD card on the S65 shield, and reload that state when Arduino powers up.

If you have any other ideas, I’d be delighted to here them.

What I now still have to do is to build a nice enclosure for Naguino (sigh).

Nagios and Arduino :: 11 May 2009 :: e-mail