Tuesday, September 20, 2011

A Cheap Agile Lamp on Windows 7

At my previous company (FireID) we had an awesome CI queue. One of the components was a dedicated lamp server hosting three lava lamps. The colour of the active lamp reflected the build status. Unfortunately the controller board controlling the lamps had to be custom made and the lamps themselves were also not cheap.

I found this extremely cheap usb controlled lamp on the Deal Extreme site.This light is very cheap and I can plug it directly into my development PC. I do not have to solder a pc-board together and worldwide delivery is free!



When you download and install the custom email notification software you'll notice a MailNotifierLib.dll in the program's directory. I wrote a small C# class to use this dll to change the light's colour.



using MailNotifierLib;

namespace emailnotifier
{

class BuildStatusLamp
{
public static void Main(string[] args)
{
eColors lampColour = eColors.NoColor;

if (args.Length > 0) {

int colour;
int.TryParse(args[0], out colour);

switch (colour)
{
case 1:
lampColour = eColors.Lime;
break;
case 2:
lampColour = eColors.Red;
break;
case 3:
lampColour = eColors.Blue;
break;
case 4:
lampColour = eColors.Aqua;
break;
case 5:
lampColour = eColors.Yellow;
break;
case 6:
lampColour = eColors.Fuchsia;
break;
case 7:
lampColour = eColors.White;
break;
default:
lampColour = eColors.NoColor;
break;
}
}

MailNotifier[] notifiers = MailNotifier.GetNotifiers();
notifiers[0].SetColor(lampColour);
}
}
}
I'm a Java developer, but writing this class in C# with the free SharpDevelop IDE was a breeze. I also used the .NET reflector tool. It was a lot simpler than trying to do it with Java via JNI or trying to traverse the dependency nightmare in Python or Ruby.

I briefly started to do this project in Python, as there was a nice example here . I quickly remembered why I hate most scripting languages... Managing and installing libraries is a nightmare. This is a far cry from the comfortable experience I have with maven, gradle or ivy in my Java environment.

Now that I have an easy way to change the lamp's colours, I wrote a simple Groovy script to monitor our Jenkins server. I used the Status Monitor plugin in Jenkins to simplify the status gathering. I am most comfortable in Java and Groovy. It probably would have been cleaner if I made the effort to write this script in C# too. Maybe someone else can contribute the C# code to make this setup even simpler? Here is the Groovy script:





class StatusChecker implements NotifierColours {

private static String MONITOR_PAGE = "http://XXXX:XXX/monitor/"
private static String SERVER_PAGE = "http://XXXX:XXX/XXX/"
private static String SERVER_TEST = "xxxxx?option=XXXX"

private static String ERROR_STRING = "<a class=\"FAILURE\" href=\"/job/XXXX"
private static String SUCCESS_STRING = "<a class=\"SUCCESS\" href=\"/job/XXXX"
private static String BUSY_STRING = "/plugin/statusmonitor/images/ajax-loader.gif"

private long blinkDelay
private long updateDelay

private int buildColour;
private boolean isBlinking = false
private boolean panic = false

// The unit tests will override these closures with test code
private def execute = { command -> command.execute() }
private def getPage = { url -> new URL(url).getText() }

public StatusChecker(long blinkDelay, long updateDelay) {
this.blinkDelay = blinkDelay
this.updateDelay = updateDelay
}

class Blinker implements Runnable {
private int colour1, colour2

public Blinker (int colour1, int colour2) {
this.colour1 = colour1
this.colour2 = colour2
}

public void run() {
while (isBlinking) {
execute("emailnotifier.exe ${colour1}")
Thread.sleep(blinkDelay)
execute("emailnotifier.exe ${colour2}")
Thread.sleep(blinkDelay)
}
}
}

boolean serverIsRunning() {
boolean siteDeployed = false
try {
String loginPage = getPage(SERVER_PAGE)
siteDeployed = loginPage.indexOf(SERVER_TEST) > 0
} catch (Exception e) {
}

siteDeployed
}

void stopBlinker() {
Thread.sleep(blinkDelay*3)
isBlinking = false
}

void startBlinker(int colour1, int colour2) {
if (isBlinking) {
throw new RuntimeException("Cannot start a new blinker while an instance is still running..")
}

isBlinking = true
new Thread(new Blinker(colour1, colour2)).start()
}

void run () {
while (true) {
updateBuildStatus()
}
}

void updateBuildStatus() {
String statusPage = getPage(MONITOR_PAGE)

boolean isBuilding = statusPage.indexOf(BUSY_STRING) > 0

if (statusPage.indexOf(SUCCESS_STRING) > 0) {
buildColour = LIME
} else if (statusPage.indexOf(ERROR_STRING) > 0) {
buildColour = RED
} else {
// Unexpected behaviour
buildColour = YELLOW
}

println "colour is ${buildColour}, ${isBuilding}\t ${new Date()}"

if (isBuilding) {
if (panic && serverIsRunning()) { // Reset panic state
panic = false
stopBlinker()
}

if (!isBlinking) {
startBlinker(buildColour, OFF)
}
} else if (buildColour == RED && !serverIsRunning()) {
// Our server reset after a successful build (GREEN), we don't want to go into panic state unnecessary
println "SHOCK! HORROR! Server is down!"
if (isBlinking && !panic) {
stopBlinker()
}

if (!panic /* YET */) {
startBlinker(RED, FUCHSIA)
panic = true
}
} else {
panic = false
if (isBlinking) {
stopBlinker()
}

execute("emailnotifier.exe ${buildColour}")
}

Thread.sleep(updateDelay)
}

public static void main(String[] args) {
new StatusChecker(1000, 10000).run();
}
}


Note, this is a simple script. No error handling, etc. I haven't played with the light on Linux yet, maybe I'll have better luck with Python there :).

Install Groovy and change the script to work with your Continious Integration setup. Now, all you have to do is copy the MailNotifierLib.dll, emailnotifier.exe and StatusChecker.groovy into a directory and run StatusChecker.groovy.
Enjoy!

4 comments:

  1. Hey dude,
    Thanks for the link to my Blog.
    It is true that the Python script is written for Windows only, as that's what I needed.
    You can fix it by using the usb.core library instant of the pywinusb. I have a sample script that does just that, drop me an email if you want it. If you want to run my script on Windows, all that you need is to install Python from Python.org and the pywinusb module from http://code.google.com/p/pywinusb/

    ReplyDelete
  2. THANKS for publishing this, exactly the level of info I needed to make a Delphi command-line utility to control the notifier for my purposes. (Though if emailnotifier.exe itself is downloadable somewhere... ?)

    ReplyDelete
  3. Hi, any chance you can post the emailnotifier.exe?
    thanks

    ReplyDelete
  4. Never mind, I compiled it myself
    http://club.dx.com/forums/forums.dx/threadid.1214347

    ReplyDelete