Background & Plan

A colleague mentioned he'd brought a set of controllable LED lights fairly cheaply and thought I'd be interested in playing with a set of my own. These were a set of 50 WS2801 leds, available from ebay for £25. A number of applications came to mind, but given the approaching festive season, Christmas tree lights seemed the most appropriate.

Rather than straightforward Christmas tree lights it struck me that we could use them to display useful information. There is very little practical information which can reasonably expressed with 50 RGB LEDs, but, working in an IT operational team, our monitoring system regularly lights up red when something breaks. As such it seemed sensible to tie into our monitoring system (icinga) to try and display useful information.

As well as the basic LED output, I decided an LCD display would be useful as a simple way to describe what the LEDs were displaying. A quick look at example wiring diagrams (as below), showed that there were enough GPIO pins available to control both the LED and LCD outputs, so I went ahead and brought everything which was required.

Shopping list

Wiring the LEDs

This very useful article gave a wiring diagram for the LEDs - simply connect Ground and +5V as appropriate, then the other two inputs to the clock and MOSI connection on the Pi. As this was a quick temporary project, I used a breadboard with the Pi cobbler extension, which made prototyping quick and easy.

The diagram on the link above showed power for the Pi being drawn from an external power supply, hence the addition of the power board on the breadboard. Unfortunately, for reasons unknown, this didn't work and the Pi needed a separate power supply (though the LEDs still drew power from the breadboard).

Wiring the LCD display

Adafruit, the manufacturers of the LCD display, provide a useful PDF detailing how to get the screen working with a wiring diagram. Following that got the LCD display up and working very easily and also provided example python code.

Output from monitoring

This was fairly trivial to achieve with Icinga's API, which allows you to form queries to show the current state of services and to flash when a service went down. I copied an existing script (which originally provided a large-text web page view of the current status), chose a few interesting services to monitor, and formed an output page. The format I chose to output was multiple lines as follows:
ServiceName,CriticalPercent,WarningPercent,OkPercent
OR
#hexcolour,ProblemName,Time
It very quickly became evident that the "current state" of a service was difficult to show in 50 LEDs - if you lit up one LED for every problem, then the majority of the time (assuming all was in working order), it would be hard to spot the one red LED. I opted instead to group together 5 LEDs per 'problem'. This meant that it was easier to spot if there was anything wrong, but meant that if there were over 10 problems, you only saw a fully red tree. As the majority of the time we had fewer than 10 current problems, I decided to stick with this method.

For the "new issue" flashes, more work was required. As it was difficult to get an event to trigger on the Pi every time a new service problem occurred, we needed some way to keep a track of "state", such that it only flashed once (rather than each time the script loaded the page). The simplest method to do this was to include the time of the state change in the response. This way, the Pi could read everything in, compare the timestamp against it's own clock and only flash if it was less recent than 1 minute (assuming the script refreshed every minute and that the clocks were in sync). This worked on the first test, so was left as is (though there are obviously many better ways to do this)
View Source

use POSIX 'strftime';

#Get a list of Unacknowledged critical alerts
my $criticalsProdUnAck = scalar(split /\n/,`export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING=\
	'jsonoutput&style=detail&servicestatustypes=16&serviceprops=10&sortoption=5&sorttype=1\
	&hostgroup=-prod'; /usr/lib64/icinga/cgi/status.cgi | grep "\"host\""`);

my $mg6 = `export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING='jsonoutput&style=detail&sortoption=5&\
	sorttype=1&host=mailgate6.iss.soton.ac.uk'; /usr/lib64/icinga/cgi/status.cgi | grep MAILGATE | sed 's/.*OUT: //;s/BMX.*//'`;

my $mg7 = `export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING='jsonoutput&style=detail&sortoption=5&\
	sorttype=1&host=mailgate7.iss.soton.ac.uk'; /usr/lib64/icinga/cgi/status.cgi | grep MAILGATE | sed 's/.*OUT: //;s/BMX.*//'`;

my $bb = `export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING='jsonoutput&style=detail&sortoption=5&\
	sorttype=1&host=blackboard'; /usr/lib64/icinga/cgi/status.cgi  | grep HTTPS | sed 's/.*bytes in //;s/ second response.*//'`;

$bb = ($bb * 20);

my @servCatOk =  split /\n/,`export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING=\
	'jsonoutput&style=detail&servicestatustypes=2sortoption=5&sorttype=1&hostgroup=service-catalogue';\
	/usr/lib64/icinga/cgi/status.cgi | grep host_name | sed 's/.*duration\": \"//;s/\", \"attempts.*//'`;

my @servCatWarn =  split /\n/,`export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING=\
	'jsonoutput&style=detail&servicestatustypes=4&sortoption=5&sorttype=1&hostgroup=service-catalogue';\
	/usr/lib64/icinga/cgi/status.cgi | grep host_name | sed 's/.*duration\": \"//;s/\", \"attempts.*//'`;

my @servCatCrit =  split /\n/,`export REQUEST_METHOD='GET'; export REMOTE_USER=icingaadmin; export QUERY_STRING=\
	'jsonoutput&style=detail&servicestatustypes=16&sortoption=5&sorttype=1&hostgroup=service-catalogue';\
	/usr/lib64/icinga/cgi/status.cgi | grep host_name | sed 's/.*duration\": \"//;s/\", \"attempts.*//'`;

#Print Header stuff
print "Content-Type: text/html\n\n";

if ($criticalsProdUnAck > 10) {$criticalsProdUnAck=10;}
print "Unacknowleged,".($criticalsProdUnAck*10).",0,".((10-$criticalsProdUnAck)*10)."\n";


my $outbound= ($mg6 + $mg7 - 1000)/10;
if ($outbound < 0) {$outbound = 0;}
if ($outbound > 100) {$outbound = 100;}
#print "MailOut,".$outbound.",0,".(100-$outbound)."\n";

if ($bb > 100) {$bb = 100;}
#print "BB,".$bb.",0,".(100-$bb)."\n";

foreach $ok (@servCatOk)
{
        my $days = $ok;
        $days =~ s/d.*//;

        my $hours = $ok;
        $hours =~ s/.*d //;
        $hours =~ s/h.*//;

        my $mins = $ok;
        $mins =~ s/.*h //;
        $mins =~ s/m.*//;
        my $secs = $ok;
        $secs =~ s/.*m //;
        $secs =~ s/s.*//;

        my $date = time - ($days * 24 * 60 * 60) - ($hours * 60 * 60) - ($mins * 60) - ($secs);
        #print "#00ff00,$ok,".$date."\n";
}
foreach $warn (@servCatWarn)
{
        my $days = $warn;
        $days =~ s/d.*//;

        my $hours = $warn;
        $hours =~ s/.*d //;
        $hours =~ s/h.*//;

        my $mins = $warn;
        $mins =~ s/.*h //;
        $mins =~ s/m.*//;

        my $secs = $warn;
        $secs =~ s/.*m //;
        $secs =~ s/s.*//;

        my $date = time - ($days * 24 * 60 * 60) - ($hours * 60 * 60) - ($mins * 60) - ($secs);
        print "#ff8000,$warn,".$date."\n";
}
foreach $crit (@servCatCrit)
{
        my $days = $crit;
        $days =~ s/d.*//;

        my $hours = $crit;
        $hours =~ s/.*d //;
        $hours =~ s/h.*//;

        my $mins = $crit;
        $mins =~ s/.*h //;
        $mins =~ s/m.*//;

        my $secs = $crit;
        $secs =~ s/.*m //;
        $secs =~ s/s.*//;

        my $date = time - ($days * 24 * 60 * 60) - ($hours * 60 * 60) - ($mins * 60) - ($secs);
        print "#ff0000,$crit,".$date."\n";
}

Controlling LEDs and LCD

Given the majority of the example code found at this point, it made sense to write the script in python, pulling in code from the pre-existing examples.

Parsing the output

Putting it all together


View Source
SOURCE CODE GOES HERE

The finished product