Use Sun SPOTs as your build canary

Monitor the health of your continuous build process with Small Programmable Object Technology

Find out how to turn a new, open source wireless device — Sun's Small Programmable Object Technology (SPOT) — into a highly visible indicator of the health of a continuous integration build. Craig Caulfield introduces you to Sun SPOTs and the SPOT SDK, then shows how to use SPOTs as an early-warning system for CruiseControl builds.

Share:

Craig W. Caulfield (craig.caulfield@ajilon.com.au), Senior software engineer, Ajilon Consulting

Craig Caulfield is a senior software engineer with a software consulting company in Perth, Western Australia. He holds certifications in Java technology, RPG, WebSphere, DB2, and XML.



03 November 2009

Also available in Chinese

In the early days of underground coal mining, pit canaries often had short but valuable lives. Because they were so sensitive to deadly gases such as methane and carbon monoxide, a canary dropping off its perch was a highly visible signal to the miners that it was time to leave. After waiting a while, and with a fresh canary, the miners could safely reenter the mine.

Your software project might have its own kind of pit canary. If you're running a Continuous Integration tool such as CruiseControl, you're probably familiar with the e-mail messages sent to the team when a build fails. This is a signal that something in the project code needs to be fixed quickly. But these messages can sometimes get lost amongst all my other incoming e-mail. And then before I know it, I've updated my local working copy from a broken repository trunk or innocently gone home and left the rest of the team muttering.

What's needed is something highly visible, like a canary, that I can quickly glance at to see the state of my continuous build process. My canary is a new open source technology from Sun Microsystems called Sun Small Programmable Object Technology (SPOT). This article introduces you to SPOTs and shows how I constructed a build canary to monitor CruiseControl.

What are SPOTs?

SPOTs (see Figure 1) are small wireless devices that run Java™ programs. A SPOT carries a range of onboard sensors for taking readings from its environment, a set of coloured LEDs for communicating with the outside world, and two pushbuttons for providing basic feedback. The LEDs are what I use to display the state of my build. Some SPOTs can be tethered to workstations via a USB cable, which lets them act as base stations through which other SPOTs can access resources on the workstation such as databases or Web applications.

Figure 1. A Sun SPOT
A Sun SPOT

Getting SPOTs

If you want to get some SPOTs to make your own build canary, you can buy kits through Sun SPOT World (see Resources). In the kit, you get two SPOTs and a base station, some USB cables, and the SDK on CD. The kits sometimes go out of stock (and they're still relatively expensive) but don't let that put you off. The SDK comes with an emulator called Solarium, which means you can get started straight away on virtual SPOTs.

After you've installed the SDK, you can explore sample applications, a developer's guide, technical specifications, source code, and some project descriptions you might like to try, such as using a SPOT to control a Web camera.

SPOTs are made up of the following hardware elements:

  • The main processor is a 180MHz Atmel AT91RM9200 system-on-chip.
  • Each SPOT has 4MB Flash RAM and 512K pseudostatic RAM.
  • Power is supplied by an internal rechargeable battery (lithium-ion prismatic cell), by an external voltage, or through the USB host.
  • The battery has a charge life of about three hours with continuous use. It hibernates when nothing is happening, which can extend its life.
  • The demonstration daughterboard contains temperature and light sensors, a three-axis accelerometer, eight tricolour LEDs, and two pushbutton switches. Additional daughterboards can be added if necessary through five general-purpose I/O pins and four high-current output pins.
  • Wireless communication is through an IEEE 802.15.4 compliant transceiver that operates in the 2.4GHz-to-2.4835GHz unlicensed bands.

On this hardware, SPOTs run a small-footprint JVM called Squawk, which is written almost entirely in the Java language. Squawk is compliant with the Connected Limited Device Configuration (CLDC) 1.1 Java Micro Edition (Java ME) configuration. It runs without the need for an underlying operating system — something called "running on the bare metal" (see Resources).

Cyber-physical systems

SPOTs are an example of a cyber-physical system: a system in which embedded devices run software and communications protocols that can sense and react to their environment. See Resources for more information.

Applications for SPOTs are written according to the Java ME MIDlet programming model. This means that the JVM on each SPOT manages the MIDlet's life cycle (creation, start, pause, and exit) in a similar way to how servlets and Enterprise JavaBeans (EJBs) are managed under Java EE. But, given the constrained environment in which MIDlets run, the CLDC takes JDK 1.3.1 as its starting point and strips out everything that isn't needed. Therefore, SPOT programs don't have access to file streams; there's no reflection, no serialisation, no classloading, no native methods, no regular expressions, no Swing, and only limited data types. The only available collection data structures are vectors, stacks, enumerations, and hash tables. Some CLDC-specific connection classes are added to this subset, but otherwise your programming world is very tight.


Writing, building, and deploying code to a SPOT

Writing, building, and deploying a MIDlet to a SPOT is a simple process, and you can use the IDE of your choice. For example, to set up your development in Eclipse:

  1. Create a standard Java project and delete the default JRE.
  2. From the lib folder of the SPOT SDK, add the following JARs to your classpath:
    • transducer_device.jar
    • multihop_common.jar
    • spotlib_device.jar
    • spotlib_common.jar
    • squawk_device.jar
  3. Create a file called MANIFEST.MF in a directory called resources/META-INF. This file contains information the Squawk VM will use to run the application. For example, Listing 1 is the manifest file for my build canary:
    Listing 1. Contents of the resources/META-INF/MANIFEST.MF file
    MIDlet-Name: BuildCanary
    MIDlet-Version: 1.0.0
    MIDlet-Vendor: Craig Caulfield
    MIDlet-1: Build Canary, , speck.buildmonitor.BuildCanary
    MicroEdition-Profile: IMP-1.0
    MicroEdition-Configuration: CLDC-1.1
    BaseStationAddress: 0014.4F01.0000.3A3C
    PortNumber: 100

    The most important line in Listing 1 is:

    MIDlet-1: Build Canary, , speck.buildmonitor.BuildCanary

    The first parameter is the name of the application, and the third is the fully qualified class name of the application's main class.

    You can add your own properties to this file and read them back at run time, like this:

    String baseStationAddress = getAppProperty("BaseStationAddress");
  4. Create a class that extends javax.microedition.midlet.MIDlet and start developing your application.
  5. When you're ready to deploy your code, bundle it into a JAR and send it wirelessly to a SPOT:
    1. Connect a base station SPOT to your workstation using the USB cable.
    2. Start the base station by executing ant startbasestation from the installation directory of the SPOT SDK.
    3. Deploy the JAR by executing:
      ant -DremoteId=0014.4F01.0000.3A19 deploy

Eclipse projects for the build-canary application are available for download to get you started.


Application overview

The deployment diagram in Figure 2 shows how I have set up my build canary to monitor CruiseControl builds. (I use CruiseControl because it's easy to install and configure and has plenty of plug-ins that allow me to construct a build process that suits almost any need; see Resources for more details.)

Figure 2. Deployment diagram for the build monitor
The build deployment diagram

A CruiseControl build loop is running on a build server that has a SPOT base station connected via a USB cable. A simple Java SE application — CanaryHandler — is called on the build server whenever the current state of the build (SUCCESS, FAILED, or RUNNING) changes. A wireless message is then sent via the base-station SPOT to BuildCanary — a MIDlet running on a remote SPOT — to update that SPOT's LEDs to reflect the build's new state.

The CanaryHandler code

To get the CanaryHandler program off to a good start, I've used Apache Commons CLI to parse the command-line arguments (see Resources). CLI does the hard work of collecting and validating the arguments and also provides a handy help function. For example, if you enter java CanaryHandler --help, you'll see the output in Listing 2:

Listing 2. Help text for CanaryHandler
usage: java CanaryHandler (--running | --failed | --success) --spot
            "0014.4F01.xxxx.yyyy" --port 100 --serial COM4
This program connects with a remote Sun SPOT to set a colour to denote the
current state of the Continuous Integration build process.
 -a,--spot <spot>    The IEEE wireless address (like
                     0014.4F01.0000.30E0) of the SPOT (enclose in double quotes).
 -c,--serial <serial>The serial port (e.g. COM4) to which the SPOT base
                     station is attached.
 -f,--failed         The build has failed.
 -h,--help           Print this usage information.
 -p,--port <port>    The port address (range 32 to 255) to be used for
                        communicating with the SPOT.
 -r,--running        The building is running
 -s,--success        The building was successful.
For more instructions see the Javadoc in the docs/api directory.

You can see in Listing 2 that CanaryHandler accepts four parameters:

  • The current state of the build, which can be:
    • Running (the SPOT flashes its LEDs in a running blue stream).
    • Failed (the SPOT blinks its LEDs on and off in red).
    • Successful (the SPOT displays its LEDs as a constant green bar).
  • The address of a remote SPOT. Each SPOT is identified by a 64-bit IEEE wireless address starting with 0014.4F01, with the other two quartets uniquely identifying the SPOT.
  • A port number that uniquely identifies the connection between the base station and the remote SPOT.
  • A serial port that identifies the serial port on the build server to which the base station is connected.

After parsing the command-line arguments, CanaryHandler opens a radiostream connection to the remote SPOT, as shown in Listing 3:

Listing 3. The main() method from CanaryHandler
/**
 * Respond to the state a continuous build process by setting the LEDs on a
 * remote SPOT accordingly. This is done by writing a simple message to an
 * output stream, on the end of which is a SPOT waiting to read.
 *
 * @param args the command line arguments. See the printUsage
 * method further down for a full description of the parameters.
 */
public static void main(String[] args) throws IOException {

    createCommandLineParser();
    StreamConnection connection = null;
    DataOutputStream dos = null;
    DataInputStream dis = null;

    try {

        CommandLine commandLine = parser.parse(options, args);
        String spotAddress = commandLine.getOptionValue("spot");
        String port = commandLine.getOptionValue("port");
        String spotConnection = "radiostream://" + spotAddress + ':' + port;

        System.setProperty("SERIAL_PORT", commandLine.getOptionValue("serial"));

        log.info("Setting address to " + spotConnection);

        connection = (StreamConnection) Connector.open(spotConnection);
        dos = connection.openDataOutputStream();
        dis = connection.openDataInputStream();

        if (commandLine.hasOption("running")) {

            log.info("Setting build state to RUN.");
            dos.writeUTF("RUN");
            dos.flush();
            log.info("SPOT responded with: " + dis.readUTF());

        } else if (commandLine.hasOption("failed")) {

            log.info("Setting build state to FAIL.");
            dos.writeUTF("FAIL");
            dos.flush();
            log.info("SPOT responded with " + dis.readUTF());

        } else if (commandLine.hasOption("success")) {

            log.info("Setting build state to SUCCESS.");
            dos.writeUTF("SUCCESS");
            dos.flush();
            log.info("SPOT responded with " + dis.readUTF());

        } else {

            printUsage(options);
            System.exit(1);

        }

    } catch (ParseException e) {

        // This will be thrown if the command line arguments are malformed.
        printUsage(options);
        System.exit(1);

    } catch (NoRouteException nre) {

        log.severe("Couldn't get a connection to the remote SPOT.");
        nre.printStackTrace();
        System.exit(1);

    } finally {

        if (connection != null) {
            connection.close();
        }

        System.exit(0);
    }
}

The radiostream protocol is similar to a peer-to-peer socket connection and provides reliable, buffered stream-based I/O between the base station and the remote SPOT. Data input and output streams are also opened. Then, a simple message about the build is written to the output stream, which the remote SPOT reads; it then returns a short confirmation message. CanaryHandler then ends and waits to be invoked the next time the build status changes.

The BuildCanary code

When CanaryHandler opens a radiostream connection and sends a simple message about the build, on the other end of the connection is BuildCanary, a Java ME MIDlet deployed on the remote SPOT. MIDlets are similar to servlets and EJBs in that you implement a specialised interface, and the runtime environment is responsible for invoking certain methods at points during its life cycle.

For example, the typical entry point for a MIDlet is the startApp() method. In BuildCanary, startApp() delegates to another method, which in turn spawns a thread to listen for messages from CanaryHandler. Listing 4 shows the code for BuildCanary's entry points:

Listing 4. Entry points of BuildCanary
/**
 * MIDlet call to start our application.
 */
protected void startApp() throws MIDletStateChangeException {
    run();
}

/**
 * Main application run loop which spawns a thread to listen for updates
 * about the build status from the host.
 */
private void run() {

    // Spawn a thread to listen for messages from the host.
    new Thread() {

        public void run() {
            try {

                updateBuildStatus();

            } catch (IOException ex) {

                try {
                    if (connection != null) {
                        connection.close();
                    }

                    if (dos != null) {
                        dos.close();
                    }

                    if (dis != null) {
                        dis.close();
                    }

                    ex.printStackTrace();
                } catch (IOException ex1) {
                    // Can't do much now.
                    ex1.printStackTrace();
                }
            }
        }
    }.start();
}

The updateBuildStatus() method, shown in Listing 5, is where the main work of the MIDlet is done:

Listing 5. Method to listen for read messages sent by CanaryHandler
/**
 * Reflect the status of the continuous build process taking place on the
 * host PC by setting the colours of the SPOT's LEDs appropriately.
 */
private void updateBuildStatus() throws IOException {

    setColour(LEDColor.WHITE, "");

    String baseStationAddress = getAppProperty("BaseStationAddress");
    String portNumber = getAppProperty("PortNumber");
    String connectionString = "radiostream://" + baseStationAddress + ':' +
            portNumber;

    // Open a connection to the base station.
    connection = (RadiostreamConnection) Connector.open(connectionString);
    dis = connection.openDataInputStream();
    dos = connection.openDataOutputStream();

    while (true) {

        // dis will block here forever until it has something from the host
        // to read.
        String buildStatus = dis.readUTF();

        if (buildStatus.equals("RUN")) {

            setColour(LEDColor.BLUE, buildStatus);

        } else if (buildStatus.equals("SUCCESS")) {

            setColour(LEDColor.GREEN, buildStatus);

        } else if (buildStatus.equals("FAIL")) {

            setColour(LEDColor.RED, buildStatus);

        } else {
            throw new IllegalArgumentException("The build status of " +
                    buildStatus + " isn't recognised.");
        }

        dos.writeUTF("Build status updated to " + buildStatus);
        dos.flush();
    }
}

This method first turns all the LEDs white to show the SPOT is ready. Then, similarly to CanaryHandler, it opens a radiostream connection and data input and output streams. But in this case, it first tries to read a message from the input stream, and it will block until something is received.

When a message is received, the setColour() method, shown in Listing 6, spawns another thread to update the colour of the LEDs on the SPOT. BuildCanary then goes back to the top of its while loop and waits for the next message.

Listing 6. Spawning a new thread to set the colours
/**
 * Set a colour for the LEDs to display.
 *
 * @param colour an LEDColor value.
 * @param buildStatus the current status if the build.
 */
private void setColour(final LEDColor colour, final String buildStatus) {

    if (colourThread != null) {

        colourThread.interrupt();

    }

    setColour = new SetColour(colour, buildStatus);
    colourThread = new Thread(setColour);
    colourThread.start();
}

Meanwhile, the SetColour class, shown in Listing 7, is where the LEDs are actually manipulated:

Listing 7. An inner class that manipulates the LEDs
/**
 * An inner class to handle the colour and behaviour (flashing, solid,
 * running) of the LEDs of a SPOT.
 */
public class SetColour implements Runnable {

    /**
     * A reference to the SPOTs LEDs so they can be set according to the
     * state of the build.
     */
    private ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();

    /**
     * The RGB colour to set the LEDs to.
     */
    private LEDColor colour;

    /**
     * The current status of the build. This will be used to set the
     * behaviour of the LEDs (flashing, solid, running).
     */
    private String buildStatus;

    public SetColour(LEDColor colour, final String buildStatus) {

        this.colour = colour;
        this.buildStatus = buildStatus;
    }

    public void run() {

        try {

            if (buildStatus.equals("RUN")) {

          // Display running LEDs.
                while (true) {

                    for (int i = 0; i < 8; i++) {

                        leds[i].setColor(colour);
                        leds[i].setOn();
                        Thread.sleep(200);
                        leds[i].setOff();

                    }
                }

            } else if (buildStatus.equals("FAIL")) {

          // Flash the LEDs on and off.
                while (true) {

                    for (int i = 0; i < 8; i++ ) {
                        leds[i].setColor(colour);
                        leds[i].setOn();
                     }

                    Thread.sleep(250);

                     for (int i = 0; i < 8; i++ ) {
                        leds[i].setOff();

                     }

                    Thread.sleep(250);
                }

            } else {

          // Display the LEDs as a solid bar.
                for (int i = 0; i < 8; i++) {
                    leds[i].setColor(colour);
                    leds[i].setOn();
                }
            }

        } catch (InterruptedException ie) {
        // Do nothing. Just bail out so we can set the LEDs to another colour.
        }
    }
}

Listing 7 sets a colour for each of the SPOT's eight LEDs and switches them on or off, in accordance with the status of the build.

The next step is to attach the build canary to my continuous build process.

The CruiseControl configuration

When CruiseControl starts, it runs in a continuous loop, periodically checking a source-code repository such as Subversion, and then building and testing projects from scratch. CruiseControl can then publish the success or failure of the build to a Web site for all to see and send out a variety of messages.

The behaviour of the CruiseControl loop is controlled by an XML configuration file — config.xml, shown in Listing 8:

Listing 8. The CruiseControl configuration file
<cruisecontrol>

   <property environment="env"/>
   <property name="local.directory" value="C:\data\skills\development\builds"/>
   <property name="repository" value="http://kimba/svn"/>
   <property name="javaExecutable" value="C:\applications\java\jdk1.6.0_14\bin\
                       java.exe"/>
   <property name="workingDirectory" value="C:\data\skills\development\java\micro\
                                  netbeans\sunspots\CanaryHandler\dist"/>
   <property name="libraryPath" value="-Djava.library.path=C:\applications\Sun\
                       SunSPOT\sdk-red-090706\lib"/>
   <property name="commonArguments" value="${libraryPath} -jar
                       ${workingDirectory}\CanaryHandler.jar
                       --spot "0014.4F01.0000.3A19"
                       --port 100
                       --serial COM4"/>

   <plugin name="log" dir="${logdir}"/>
   <plugin name="svn" classname="net.sourceforge.cruisecontrol.sourcecontrols.SVN"
                     username="cruise"
                     password="catbert"/>

   <project name="developer-ci-build" buildafterfailed="false">

<!-- Defines where CruiseControl looks for changes to decide whether to run the build. -->
      <modificationset quietperiod="30">
         <svn localWorkingCopy="${local.directory}/checkout/SunDeveloper"
                   repositoryLocation="${repository}/DEVELOPER/trunk/SunDeveloper"/>
      </modificationset>

<!-- Check for modifications every 60 seconds -->
      <schedule interval="60">
         <composite>
         <exec command="${javaExecutable}" args="${commonArguments}
                              --running"/>
         <maven2 mvnhome="${env.MAVEN_HOME}"
                           pomfile="${local.directory}/checkout/SunDeveloper/pom.xml"
                           goal="clean | scm:update | compile"/>
         </composite>
      </schedule>

      <listeners>
         <currentbuildstatuslistener
                      file="${local.directory}/logs/${project.name}/buildstatus.txt"/>
      </listeners>

<!-- The publishers are run when a build completes one way or another. -->
      <publishers>
         <onsuccess>
            <execute command="${javaExecutable} ${commonArguments}
                              --success"/>
         </onsuccess>
         <onfailure>
            <execute command="${javaExecutable} ${commonArguments}
                              --failed"/>
         </onfailure>
      </publishers>
   </project>

</cruisecontrol>

In my loop, I have a single project, developer-ci-build — an Apache Maven project checked into a local Subversion repository. CruiseControl checks the repository for modifications every minute; if something has changed, it executes some Maven goals to build the project. But first it calls CanaryHandler with a parameter of --running to show that the build is in progress.

The build will either succeed or fail, at which time either the onsuccess or onfailure publisher is invoked. Within these publishers are more calls to CanaryHandler to set the appropriate state of the build.


Running the build

With all the pieces now in place, I'm ready to run the build and see how the remote spot reacts.

Figure 3 shows the SPOT in its starting state with all its LEDs set to white:

Figure 3. The build canary in its initial state (LEDs all white) sitting on the build server
The initial build canary state

I then commit some deliberately faulty code to my Subversion repository and wait for CruiseControl to detect the changes and initiate a build. When the build starts, the LEDs on the build canary SPOT change from all white to running blue, as shown in Figure 4:

Figure 4. The build canary showing a build in progress (running blue LEDs)
The build canary with a build underway

Shortly after, the build fails as expected and the LEDs change to urgent blinking red, as shown in Figure 5:

Figure 5. The build canary showing a build has failed (blinking red LEDs)
The build canary showing a failed build.

I then do another commit to Subversion that fixes the faulty code. CruiseControl initiates another build, which succeeds this time, and the LEDs change to a reassuring green bar, as shown in Figure 6:

Figure 6. The build canary showing a successful build
The build canary showing a successful build

Conclusion

If a build running on your Continuous Integration system fails, you can be fairly certain the cause lies in the most recent change. The sooner you're aware of the failure, the sooner you can react to it. The SPOT-based build canary I've described in this article is designed to provide a highly visible indicator that project code is in urgent need of repair.

Although SPOTs are essentially still an experimental technology, they are backed by an active community and have been used in many exciting and imaginative ways — from riding on rockets to demonstrate physics concepts to high school students, to sitting on trees monitoring for fires, to acting as game controllers. Follow the forums on Sun SPOT World and check out some of the demonstrations on YouTube to see a wide variety of SPOT applications (see Resources).


Download

DescriptionNameSize
CruiseControl file and Java source code1j-spots.zip22KB

Note

  1. This .zip file contains the CruiseControl configuration file as well as all the source code for CanaryHandler and BuildCanary in the form of Eclipse projects.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=442724
ArticleTitle=Use Sun SPOTs as your build canary
publish-date=11032009