Production teams around the world use status lights and visual displays to quickly monitor processes and detect production failures as they occur. Nothing beats a bright glowing red light for grabbing attention. Immediate notification allows workers to stop production if necessary and to begin repairing problems as soon as they occur so that one issue won't create additional problems. If breaks are fixed when they occur, the line can continue producing quality products without issues multiplying.

Nothing beats a bright glowing red light for grabbing attention.

Software teams also benefit just as much as other production teams from rapid failure notification. Development teams use Continuous Integration (CI) servers to rebuild and retest software systems as they are modified. Automated tests verify that the functionality implemented last week still functions as intended with today's changes. When new changes cause tests to fail, breaks can be fixed with less effort if they are repaired as they happen. This article explains how you can write the software to monitor your CI server using some simple lights and a few Z-Wave home automation devices.

Z-Wave technology powers a wide variety of home automation devices ranging from simple power switches to water control valves, door locks, and various types of sensors. Z-Wave devices, called nodes, are queried and controlled by a Z-Wave controller. The general term “node” refers to any Z-Wave device that is a member of a Z-Wave network.

The controller maintains the information required to address various nodes in the network. In turn, software systems interface with the controller to implement the logic of the network. For the examples given in this article, I'll use a switchable power outlet Z-Wave device. The Z-Wave controller can query the outlet to determine whether the power is on or off. The controller can also send a signal over the Z-Wave network to turn the outlet on or off. For more information on Z-Wave technology, take a look at the “Z-Wave Technology” sidebar.

Controlling Z-Wave Switches

The open source project OpenZWave exposes a .NET API for querying and controlling Z-Wave devices. Developers can download the OpenZWave project from Github at https://github.com/OpenZWave/open-zwave. OpenZWave is written in C++ and includes a .NET wrapper to make controlling Z-Wave devices from C# or VB.NET straightforward. You can also download all of the sample code provided here as part of the ZBuildLights project at https://github.com/Vector241-Eric/ZBuildLights. ZBuildLights packages this sample code in the OpenZWaveDemo project along with a small console application wrapper.

Z-Wave home automation networks start with a controller and a device. Controlling Z-Wave switches requires only a few lines of code. Relying on the OpenZWave project, these few lines of code change the setting for a light switch from off to on or vice versa:

private void ToggleSwitchValue(ZWManager manager, ZWValueID valueId)
{
    //Get the current switch value
    bool switchValue;
    manager.GetValueAsBool(valueId, out switchValue);
    
    //Invert the switch value
    manager.SetValue(valueId, !switchValue);
}

Before you can set the switch value, you need both a manager and a device value. The manager, which OpenZWave exposes as the type ZWManager, controls almost all of the interesting functionality in OpenZWave. To build the manager, you first need to set a few options.

private void SetOptions()
{
    var options = new ZWOptions();
    options.Create(
        @"C:\work\OpenZWavRelease\config",
        @"c:\OzwDemo\UserData",string.Empty);
    options.AddOptionInt("SaveLogLevel", (int) ZWLogLevel.None);

    //OpenZWave requires finalized options
    options.Lock();
}

The Create() method sets the configuration options for OpenZWave. The OpenZWave project ships with some XML configuration files containing detailed descriptions of known device manufacturers and their devices. The first parameter for Create() points within the OpenZWave release folder to the root directory for these configuration files. The second Create() parameter tells OpenZWave where to cache data specific to your particular Z-Wave network. OpenZWave preserves network configuration and state at this directory path. The third parameter allows command line options to be passed through to OpenZWave, and I've ignored that here. I've also set the “SaveLogLevel.” OpenZWave logs plenty of diagnostic information while running, but I've chosen to keep that quiet for now. If you want to see the log messages, you can try logging level Detail or Debug.

Using the SetOptions() helper function, you can create the ZWManager.

private ZWManager CreateOpenZWaveManager()
{
    SetOptions();
    var manager = new ZWManager();
    manager.Create();
    return manager;
}

Because the ZWManager allocates some system resources internally, it should be treated as a singleton for the life of your application. Also, make sure to clean up system resources using manager.Destroy()as your program terminates. Skipping this cleanup step can cause phantom problems, such as missing Z-Wave devices when the program executes the next time.

Now that you have a manager, you need to use it to find the devices, called nodes, on the Z-Wave network. Because Z-Wave devices can initiate events themselves, the ZWManager exposes an asynchronous notification model. You'll initialize the manager and use these event notifications to discover the IDs of the nodes on the network. Listing 1 shows a method to initialize the ZWManager and write all node IDs to the console. As you can see in the listing, you first attach the event handler to the manager, then the call to AddDriver() sets the COM port for the controller and starts the initialization routines for the ZWManager. After you wait for the manager to initialize, you can use it to change the switch value.

Listing 1: Writing All Z-Wave node IDs to the console

private void DumpZWaveNodes()
{
    var manager = CreateOpenZWaveManager();

    bool allNodesQueried = false;

    //Attach a notification handler to the manager
    manager.OnNotification += notification => {
        switch (notification.GetType()) {
            case ZWNotification.Type.AllNodesQueried:
            case ZWNotification.Type.AllNodesQueriedSomeDead:
                allNodesQueried = true;
                break;
            case ZWNotification.Type.NodeAdded:
                Console.WriteLine(
                    "Add node --> Home:{0}  Node:{1}",
                    notification.GetHomeId(),
                    notification.GetNodeId());
                break;
            case ZWNotification.Type.ValueAdded:
                Console.WriteLine("Add value --> " +
                    "Home:{0}  Node:{1}  Value:{2}  Label: {3}",
                    notification.GetHomeId(),
                    notification.GetNodeId(),
                    notification.GetValueID().GetId(),
                    manager.GetValueLabel(notification.GetValueID()));
                break;
        }
    };

    //Initialize the manager and wait for it to initialize
    Console.WriteLine("Initializing ZWave Manager");
    manager.AddDriver(@"\\.\COM3");
    while (!allNodesQueried) {
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
    Console.WriteLine("Initialization Complete");
    manager.Destroy();
}

The switch statement only handles the three event types that you need:

  • The AllNodesQueried or AllNodesQueriedSomeDead event fires after the manager completes initialization.
  • The NodeAdded event fires when the manager finds a node on the network.
  • The ValueAdded event fires when the manager discovers a value that can be queried or changed on a node.

Z-Wave devices and OpenZWave support many more event types. The Github OpenZWave source code for the Notification.h header file contains a complete listing of events as well as descriptions for each.

When a Z-Wave controller connects to the computer via the USB port, Windows allocates a COM port to the device. If you have PowerShell installed on your computer, finding the port numbers is simple:

Get-WmiObject Win32_SerialPort |
Select-Object Name,DeviceID

Or, if you prefer to use the mouse, you can find your COM ports using Windows Device Manager. Launch Device Manager by tapping the START button and typing Device Manager. COM devices are listed under “Ports (COM & LPT).” Right-click your device, then select Properties > Port Settings > Advanced. The dropdown at the bottom of the window shows you the current COM port number, and allows you to change which COM port this device uses. Regardless of which method you choose, be sure to set the correct COM port number in the example code before attempting to execute it.

Selecting Z-Wave Devices

Before any of this code can be executed, Z-Wave devices must be purchased, and the Z-Wave network must be set up. Z-Wave networks are built with a controller and one or more Z-Wave devices. Standalone systems use a controller hub for controlling devices in the network. These hubs are typically Internet-enabled and work in concert with Web and mobile applications that enable users to configure and control their Z-Wave devices. In addition to standalone hubs, Z-Wave devices can also be controlled by a controller attached to a PC. For this application, I selected an Aeon Labs DSA02203-ZWUS Z-Stick USB controller (http://amzn.to/1EHXjdHhttp://amzn.to/1EHXjdH).

When considering switches, I realized that I want a collection of at least three lights in a monitoring location: a green light to indicate successful status, a yellow light to indicate running tests, and a red light to alert the team to failure. Aeon Labs makes a handy power strip called the Aeotec Z-Wave Smart Energy Power Strip (http://amzn.to/1dj05tB). (Editor's note: item no longer available.) The Smart Energy Power Strip includes four Z-Wave controllable outlets. Each of the four switchable outlets can be turned on or off individually, or all switched outlets can be turned on or off at the same time. The power strip also provides two always-on outlets. The Smart Energy Power Strip provides a good value over purchasing three or four individual Z-Wave switches. Finally, this power strip provides energy consumption monitoring for each outlet.

Any electrical device that plugs into a standard power outlet will work.

The power strip allows flexibility and creativity in choosing lights and devices. Any electrical device that plugs into a standard power outlet will work. My team works in a typical cubicle environment with plenty of wiring conduits that rise to the ceiling. Therefore, I chose various colors of rope lighting to wrap around the conduits. I used green to indicate success and red to indicate that a job is broken. Because my current client uses blue for an official brand color I used blue for the work in progress light. With the flexibility to use any device that can be switched on or off at the power socket, you can get creative. How about a fake flame that lights up when something is broken? (http://amzn.to/1zn2UUa).

Establishing the Z-Wave Network

Z-Wave devices must join the controller's network in a process called “pairing” or “inclusion.” The Aeon Labs Z-Stick makes pairing with Z-Wave devices simple. Z-Wave pairing operations use a small handshake signal, and therefore require a minimal distance between the device and controller. Fortunately, the Z-Stick contains a small battery so that the user can remove it from the computer and carry it to the device for pairing. To pair a Z-Wave device with the Z-Stick, first make sure that the new device has power. Remove the controller from the USB port and carry it near the device. Press the button on the Z-Stick to put it into inclusion mode and then press the button on the Z-Wave device. The Z-Stick LED blinks quickly while the controller communicates with the device, then the LED returns to blinking slowly to indicate that more devices can be paired. Once all devices have been added, another press of the Z-Stick button stops it from searching for more devices.

The Aeon Labs Z-Stick makes pairing with Z-Wave devices simple.

After the Z-Stick has been plugged back into the USB port, OpenZWave has access to the newly included devices. Z-Wave nodes can expose multiple values. For instance, an energy monitoring power outlet has a switch value for turning the switch on or off, and it also has values for reading energy usage. In the section, “Running the Sample Code,” (next) you can learn more about reading and writing values to query and control Z-Wave devices.

Running the Sample Code

At this point, you have Z-Wave devices that are paired with the Z-Wave controller. You have plugged the controller into a USB port and determined its COM port address. The sample code uses COM3 for its COM port address. With the devices ready to go, you can now dig into implementation specifics for the code. If you'd like to download the full sample code, you can download the ZBuildLights project from Github at https://github.com/Vector241-Eric/ZBuildLights. All of the code shown in this article is in the Program.cs file located in the OpenZWaveDemo project.

If you prefer to start with an empty application, open Visual Studio and create a new console application. Then, add the sample code from Listing 1 as well as the functions CreateOpenZWaveManager() and SetOptions() already listed in this article. You'll also need to download and build the OpenZWave library and add a reference to OpenZWave from your new console project. You can find instructions for building OpenZWave on the ZBuildLights Github page.

Whether you start with the sample code or you type everything in yourself, be sure to configure the COM port given in the call to manager.AddDriver() and the file paths in the SetOptions() method. Once everything is configured, running the DumpZWaveNodes() method should produce some output like this:

Added node --> Home:25479126  Node:1
Added value --> Home:25479126  Node:1
  Value:72057594055229441  Label: Basic
Added node --> Home:25479126  Node:2
.
.
.
Added value --> Home:25479126  Node:2
  Value:72057594076282880  Label: Switch
.
.
.

In this case, the controller itself shows up as Node 1 and a switchable lamp socket shows up as Node 2. Be sure to take note of the value ID for any “Switch” values listed. In the example, value 72085894076282880 uniquely identifies the switch value used for turning the lamp off or on. In order to use the ZWManager to change the switch value, the ZWValueID should be captured when the value is added and the ValueAdded notification is fired.

ZWValueID valueId = null;
manager.OnNotification += notification =>
{
    switch (notification.GetType())
    {
        case ZWNotification.Type.ValueAdded:
            if (notification.GetValueID()
                .GetId().ToString()
                .Equals(@"72085894076282880"))
                    {
                        valueId = notification.GetValueID();
                    }
                    break;
    }
};

In this switch statement, you save notification.GetValueID() into a local ZWaveValueID variable when the ValueAdded event fires for the specific value you're searching for. Passing this value along with the ZWManager to ToggleSwitchValue() causes the switch's value to toggle. To see all of the code pulled together, refer to Listing 2.

Listing 2: Toggling a swtich with OpenZWave

private void ToggleSwitch(string switchId)
{
    var manager = CreateOpenZWaveManager();

    //Capture the valueID for the switch
    ZWValueID valueId = null;
    bool allNodesQueried = false;
    manager.OnNotification += notification => {
        switch (notification.GetType()) {
            case ZWNotification.Type.AllNodesQueried:
            case ZWNotification.Type.AllNodesQueriedSomeDead:
                allNodesQueried = true;
                break;
            case ZWNotification.Type.ValueAdded:
                if (notification.GetValueID().GetId().ToString()
                    .Equals(switchId)) {
                    valueId = notification.GetValueID();
                }
                break;
        }
    };
    Console.WriteLine("Initializing ZWave Manager");
    manager.AddDriver(@"\\.\COM3");
    while (!allNodesQueried) {
        Thread.Sleep(TimeSpan.FromSeconds(1));
    }
    Console.WriteLine("Initialization Complete");

    if (valueId != null) {
        ToggleSwitchValue(manager, valueId);
    }

    manager.Destroy();
}

Reacting to CI Server Status Changes

In order to display build status lights with Z-Wave devices, you also need the build status. Fortunately, most major CI servers produce the same status XML format. The CruiseControl build server has always produced XML to be interpreted by the monitoring application CCTray. CI servers call this XML format CCTray XML. TeamCity, Jenkins, CruiseControl (Java, Ruby, and .NET versions), Hudson, Travis CI, and Go all produce CCTray XML. The examples listed here use TeamCity's URL format, so check the documentation for your CI server to determine how to query its CCTray XML.

Reading Status from the CI Server

Retrieving the CCTray XML from a TeamCity server requires a simple GET HTTP request to the TeamCity server. This example shows the request using guest authentication:

http://<server>:<port>/guestAuth/app/rest/cctray/projects.xml

If the TeamCity server requires authentication as a specific user, then this format authenticates the request for a specific user:

http://<username>:<password>@<server>:<port>/
httpAuth/app/rest/cctray/projects.xml

If an application stores the CCTray XML URL and the CI server requires HTTP authentication, be sure that the credentials used have the least amount of privilege required to view job status. To test that your URL is correctly formatted, paste it into your browser address bar, and you should receive an XML response like this example:

<Projects>
    <Project activity="Sleeping"
        name="Unit Tests"
        lastBuildStatus="Success" .../>

    <Project activity="Building"
        name="Integration Tests"
        lastBuildStatus="Failure" .../>
</Projects>

To simplify this example, I removed some attributes from the Project elements. Here, you can see that the Unit Tests project is not currently building and the most recent build was successful. The status of the Integration Tests project shows that the previous build failed and the job is currently running. For this example, treat any build status other than “Success” as a failure.

Parsing CCTray XML

To interpret status from CCTray XML, an application must either parse the XML directly or convert the XML into an object model. The easiest method uses the XmlSerializer to deserialize the XML into a matching object model. Visual Studio provides xsd.exe to help generate object models from XML. ZBuildLights already contains a generated object model in the file ZBuildLights.Core.Models.CruiseControl.XmlObjectModel.cs. The classes contained in that file will likely work for any CI server producing CCTray XML. If not, you can follow these steps to generate an object model for your CCTray XML.

  1. Use your browser to read the status XML and save the results in projects.xml.
  2. Start the Developer Command Prompt for your version of Visual Studio.
  3. Change directory to where you saved projects.xml.
  4. Execute the command, xsd projects.xml. This command generates the .xsd file.
  5. Execute the command xsd projects.xsd /classes to generate projects.cs. Note the .xsd extension in this command.

This series of commands generates an object structure with a root class called Projects. With the help of the System.Net, System.IO, and System.Xml.Serialization namespaces, requesting the latest status from the CI server and converting the response into C# objects requires just a few lines of code:

public static Projects GetProjects(string url)
{
    var request = WebRequest.Create(url);

    using (var response = request.GetResponse())
    using (var stream = response.GetResponseStream())
    using (var reader = new StreamReader(stream))
    {
        var serializer = new XmlSerializer(typeof(Projects));
        var projects = (Projects)serializer.Deserialize(reader);
        return projects;
    }
}

A bit of LINQ to Objects helps reading the last build status for the Unit Tests project:

string unitTestStatus = projects
    .Items
    .Single(x => x.name.Equals("Unit Tests"))
    .lastBuildStatus;

bool unitTestsArePassing = unitTestStatus.Equals("Success"));

Updating Lights to Show Build Status

To keep this example simple, consider a single Z-Wave switch connected to an artificial flame such as the one referenced in “Selecting Z-Wave Devices.” Because the flame is powered with an AC power source, it makes a perfect candidate for control via a Z-Wave switch. If the flame is activated, the team should investigate a build failure on the CI server. If the flame is off, everything is fine.

Using all of the code concepts shown so far, a simple method can be constructed to monitor a build and set the flame switch on or off accordingly. You only need a few pieces of information for some configuration: the ID of the switch value, the URL for reading the CCTray XML from the CI server, and the name of the project to monitor. Take a look at Listing 3 to see everything pulled together into a concise method for monitoring a single project.

Listing 3: A Simple Build Status Monitor

public void Monitor(string url, string projectName, string switchId)
{
    ZWManager manager = null;
    try {
        manager = CreateOpenZWaveManager();
        ZWValueID valueId = null;
        bool allNodesQueried = false;
        manager.OnNotification += notification =>  {
            switch (notification.GetType()) {
                case ZWNotification.Type.AllNodesQueried:
                case ZWNotification.Type.AllNodesQueriedSomeDead:
                    allNodesQueried = true;
                break;
                case ZWNotification.Type.ValueAdded:
                    if (notification.GetValueID().GetId().ToString()
                        .Equals(switchId)) {
                        valueId = notification.GetValueID();
                    }
                    break;
            }
        };
        Console.WriteLine("Initializing ZWave Manager");
        manager.AddDriver(@"\\.\COM3");
        while (!allNodesQueried) {
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
        Console.WriteLine("Initialization Complete");

        while (true) {
            var projects = GetProjects(url);
            var status = projects.Items
                .Single(x => x.name.Equals(projectName)).lastBuildStatus;
            var failureLightOn = status != "Success";
            manager.SetValue(valueId, failureLightOn);
            Thread.Sleep(TimeSpan.FromSeconds(15));
        }
    }
    finally {
        if (manager != null)
        manager.Destroy();
    }
}

OpenZWave: An Open Source Solution for a Proprietary Protocol

The official Z-Wave SDK costs approximately $3,000 and requires developers to execute an NDA to prevent disclosure of the information in the development kit. Because Z-Wave is a proprietary technology that requires a significant investment in the SDK, there are no official low-cost SDK options available to developers. So, some developers created the OpenZWave project and reverse engineered the Z-Wave protocol to provide a free open source alternative. OpenZWave is not an official Z-Wave SDK, but many developers have found it both useful and reliable.

The OpenZWave community stays active with many developers making contributions and participating in the mailing list. To connect with the community, check out the OpenZWave project at http://openzwave.com/. The OpenZWave project code lives at Github: https://github.com/OpenZWave.

More Complex Monitoring

The code and information in this article provide someone familiar with C# and Visual Studio with the necessary information required to build a simple Z-Wave powered monitoring system. When considering more complex scenarios, such as multiple teams sharing the same Z-Wave network, multiple build servers, and multiple monitoring stations, the software solution to configure and control everything gets much more complex. Managing the relationships between multiple sets of devices and multiple builds on multiple servers requires more than a few lines of code as well as a developed user interface to reduce errors when maintaining configuration.

ZBuildLights (https://github.com/Vector241-Eric/ZBuildLights) exists to handle such a complex environment. The ZBuildLights project tackles the issues faced by a medium to large organization sharing the same Z-Wave network. The results of multiple CI projects from multiple CI servers can be aggregated into a single status display. If a single build in the group fails, the status display shows failure. Additionally, multiple status displays can easily be set up in multiple locations to display the same project status to different areas of a large physical workspace.

ZBuildLights includes an ASP.NET MVC project with a small Windows service to trigger periodic static updates. The Github pages include installation and configuration instructions. If you see something that needs improvement or would like to add additional features, submit an issue. Better yet, send a pull request.