i-think Twenty-Two

Now with more coherency.

Build notification light using Redis and a Delcom Visual Indicator

| Comments

A while back I ordered a Visual Signal Indicator from Delcom. I had great plans to make it flash when I received a call on Lync, and to generally help me keep track of the health of the project I was working on. I looked at the C# example code provided by Delcom and initially was hesitant to get started. I was already planning out how I would need to write a library that would simplify the madness of unravelling the various settings the example provided. All the commands were wrapped to Click events in a Windows Form app which led me to not get a start on the project as quickly as I had hoped.

Recently I have just started working on a new project and as we were blessed with a build server and a mostly working build I was reminded of this project that had been put on the back burner. This time I approached it with a far more pragmatic mindset and just wanted to get something working. I wasn’t looking to win any UI awards and I didn’t want to architect it any further than what I needed to get the job done.

This is what the light can do:

  • Turn on any of three colours at various power levels
  • Flash lights on a timing interval
  • Make beeping sounds
  • Accept input (the light itself is a button)

I still haven’t quite worked out exactly how the button works on the light, but I was able to easily set it so it would beep if I pushed the button thanks to a built in setting. This however wasn’t something that I especially needed anyway.

My needs however were simple. I had three colours to choose from, red, green and yellow. I also wanted to be able to make the light flash while a build was running and continue to show the colour of the previous build result.

A Redis interface to the light

Another technology I have been looking into recently is Redis. It’s a key value store which lets you hold some simple data structures. It also has an excellent publish/subscribe model which allows messages to be broadcast to interested clients.

After using this to store some configuration information for some tests to great success I thought about using Redis to provide an interface into the inner workings of my build light. By holding the desired state of the light in Redis all I would need is to signal that a change had been made and all would be well.

Rather than starting from scratch I chose to extend the existing example code as it was already working well. The work I needed to do involved automating the interaction with the UI, a task I was already familiar with and to do it within the application itself, a breeze. Working this way also provided full visibility into what settings were being sent to the light.

The changes I needed to make were:

  • Opening a connection to the light on startup
  • Ensuring that the light’s state on startup matched the state stored in Redis
  • Applying styling to the On/Off/Flash lights to better indicate the current state
  • Listening to a channel on Redis for changes to the light state
  • Updating the light state as applicable

I decided to leave the buzzer and switch control as an exercise for another day as these were not essential to my plans (and the buzzer would likely annoy me).

I decided to use a Redis hash (a Dictionary) to store information about the desired state of the light. In Redis, hashes can have individual fields changed making it fairly easy to work with. I chose to set up the following fields for each colour light:

  • state with 0 representing off, 1 on, and 2 flashing
  • power representing the power level or intensity of the light
  • onduty specifying a time interval for the light to be on when flashing
  • offduty specifying a time interval for the light to be off when flashing
  • offset specifying an offset when flashing so that the flashing of lights could be synchronised (e.g. flashing between red and green)

These all corresponded to appropriate controls in the example application.

The code for the solution can be found in my HIDVIWINCS repository on GitHub.

Getting the build status

Of course, this was only part of the battle. Fortunately obtaining the status of the latest build was reasonably straightforward. First, I set up an Enum to keep track of the light’s current colour.

LightColour.cs
1
2
3
4
5
6
7
8
9
10
namespace ConsoleBuildMonitor
{
  public enum LightColour
  {
    Off,
    Red,
    Yellow, // On my light it is yellow, on others, blue
    Green
  }
}

With this simple task out of the way I could set up individual methods to update the appropriate hashes in Redis. The library I used in the build monitor was Booksleeve, a slightly older version than the client I used in HIDVIWINCS. Setting the colour of the light was as simple as folows:

Program.cs (excerpt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void SetLight(LightColour colour)
{
  var db = int.Parse(ConfigurationManager.AppSettings["RedisDatabase"]);
  using (
      var conn = new RedisConnection(ConfigurationManager.AppSettings["RedisHost"],
          int.Parse(ConfigurationManager.AppSettings["RedisPort"])))
  {
    conn.Open();
    conn.Hashes.Set(db, "red", "state", new[] {color == LightColour.Red ? (byte) 1 : (byte) 0 });
    conn.Hashes.Set(db, "blueyellow", "state", new[] {color == LightColour.Yellow ? (byte) 1 : (byte) 0 });
    conn.Hashes.Set(db, "green", "state", new[] {color == LightColour.Green ? (byte) 1 : (byte) 0 });
    conn.Publish(ConfigurationManager.AppSettings["RedisUpdateChannel"], "1");
  }

  this.lightColour = colour;
}

Then to make the light flash I simply needed to make sure that the state was set appropriate for the current light.

Program.cs (excerpt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void Flash()
{
  var db = int.Parse(ConfigurationManager.AppSettings["RedisDatabase"]);
  using (
      var conn = new RedisConnection(ConfigurationManager.AppSettings["RedisHost"],
          int.Parse(ConfigurationManager.AppSettings["RedisPort"])))
  {
    conn.Open();
    conn.Hashes.Set(db,
        this.lightColour == LightColour.Red ? "red" : this.lightColor == LightColour.Green ? "green" : "blueyellow",
        "state", new[] {(byte) 2});
    conn.Publish(ConfigurationManager.AppSettings["RedisUpdateChannel"], "1");
  }
}

It is worth noting that the message sent on the update channel doesn’t actually matter at this point in time as it is simple the receipt of the message that triggers an update to the light’s status.

Finally the code required to check the build status is reasonably straightforward:

Program.cs (excerpt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private static void ExecuteLoop()
{
  var buildStatus = new Dictionary<string, BuildStatus>();
  using (var collection = new TfsTeamProjectCollection(new Uri(ConfigurationManager.AppSettings["TfsProjectCollectionUrl"])))
  {
    collection.Authenticate();
    var bs = collection.GetService<IBuildServer>();
    var re = new Regex(ConfigurationManager.AppSettings["BuildPattern"]);
    var ls =
        bs.QueryBuildDefinitions(ConfigurationManager.AppSettings["TfsProject"])
            .Where(d => re.IsMatch(d.Name)).ToList();
    do
    {
      foreach (var b in ls)
      {
        var latestBuild = bs.QueryBuilds(b).OrderBy(x => x.StartTime).LastOrDefault();
        if (latestBuild == null) continue;
        if (!buildStatus.ContainsKey(b.Name) || buildStatus[b.Name] != latestBuild.Status)
        {
          switch (latestBuild.Status)
          {
            case BuildStatus.Failed:
              SetLight(LightColour.Red);
              Log.Error("Build {Build} Failed ({RequestedFor}) - {BuildId}", b.Name,
                latestBuild.RequestedFor, latestBuild.BuildNumber);
              break;
            case BuildStatus.PartiallySucceeded:
              SetLight(LightColour.Yellow);
              Log.Warning("Build {Build} Partially Succeeded ({RequestedFor}) - {BuildId}", b.Name,
                latestBuild.RequestedFor, latestBuild.BuildNumber);
              break;
            case BuildStatus.InProgress:
              Flash();
              Log.Information("Build {Build} In Progress ({RequestedFor}) - {BuildId}", b.Name,
                latestBuild.RequestedFor, latestBuild.BuildNumber);
              break;
            case BuildStatus.Succeeded:
              SetLight(LightColour.Green);
              Log.Information("Build {Build} {Status} ({RequestedFor}) - {BuildId}", b.Name,
                latestBuild.Status, latestBuild.RequestedFor, latestBuild.BuildNumber);
              break;
            default:
              Log.Information("Build {Build} {Status} ({RequestedFor}) - {BuildId}", b.Name,
                latestBuild.Status, latestBuild.RequestedFor, latestBuild.BuildNumber);
              break;
          }

          buildStatus[b.Name] = latestBuild.Status;
        }
      }

      Task.Delay(5000, _cancellationToken).Wait(_cancellationToken);
    } while (!_cancellationToken.IsCancellationRequested);
  }
}

The code required to integrate the light to the build monitor was actually the simplest bit of code in the whole thing. Indeed I haven’t shown the most complex part of the code because it still needs work, but it handles restarting the loop if it fails and shutting the whole thing down cleanly.

Importantly it means that the monitoring tools are well separated from the actual control of the light. They no longer need to even exist on the same computer. Indeed during my initial testing I had Redis hosted on one computer, the light on another and I was using a Redis client on a third.

Photos and videos of the build light in action!

Build Succeeded by i-think Twenty-Two

Both the build and the tests have passed. This makes me happy. :-D

Partially Succeeded by i-think Twenty-Two

This is what the build light looks like when tests fail.

Build Failure by i-think Twenty-Two

Here the code doesn’t even compile. This makes me sad. :-(

Building… by i-think Twenty-Two

While the build server is building the latest check-in and running tests the build light flashes.

HDIWINCS by i-think Twenty-Two

The super snazzy UI

Comments