Stall Status: Know before you go

Description

 icon Stall Status is a Silverlight-based Vista Sidebar Gadget that uses the Z-Wave wireless protocol and door sensors to notify you of the occupied/available state of the bathroom stalls.
Clarity Consulting, Inc

Difficulty: Easy
Time Required: 1-3 hours
Cost: $50-$100
Software: Visual C# Express Edition
Hardware: ControlThink Z-Wave SDK Hawking Technologies Z-Wave HRDS1 door sensor
Download: Download

Stall Status

Every now and then someone throws out an idea for a completely off-the-wall application that just has to be built. When Jon Rauschenberger, Clarity’s CTO, came into my office with his “brilliant idea” for a monitoring system to track the “occupied/available” state of our washroom stalls, I immediately recognized it as one of those moments. I had been doing some research on the Z-Wave wireless protocol for a client project earlier that week, and I thought “Stall Status” would be a fun way to get some hands on experience using Z-Wave.

In this article I will walk you through how to write managed code that interacts with Z-Wave devices. I’ll also show how to use Silverlight 2.0’s access policies to write “connected clients”: client applications that establish a persistent TCP connection to a remote server and thereby avoid having to poll the server for status changes. Finally, I’ll walk through the steps necessary to deploy a Silverlight 2.0 application as a Windows Vista Sidebar Gadget.

What is Z-Wave?

Z-Wave is a wireless communication protocol that works over radio frequency and is specifically designed for low-power, low-bandwidth usage scenarios. Typical usage scenarios for Z-Wave devices are monitoring and control applications – such as home automation. Available devices include door sensors, switches, and motors. Z-Wave devices are interoperable - devices labeled as Z-Wave compliant should all work together.

A Z-Wave network consists of one or more devices and a centralized controller. A controller can be a standalone (often hand held) hardware unit or a software-based kit. Devices on a Z-Wave network handle the routing of messages automatically without the need to set up pre-defined routes. This means that if you have several devices in your network that need to communicate together (e.g. a door sensor and an alarm), the devices can route messages through each other. This “mesh” communication capability allows you to extend the range of your network much farther than you would be able to if the devices all required direct connections to each other.  For example, if you have three devices, A, B and C, device A can communicate with device C, even if C is outside of A's range, by routing its messages through device B.  This all works automatically.

My simple Z-Wave network for Stall Status consists of door sensors to notify me when a stall door is opened and closed, and a software based controller. I used Hawking Technologies’ HRDS1 battery powered door sensors for each stall door in the washroom. The HRDS1 is considered an “occasionally on” device – it quietly sits in sleep mode until it detects an open/close event from the door. This is the same type of device you would use, for example, when building an alarm system for monitoring windows and doors.  For the Z-Wave controller I used an excellent software-based controller and SDK from ControlThink, which you can purchase for $70. The controller consists of a USB dongle and a set of APIs for connecting to and communicating with devices. ControlThink includes a number of .Net sample applications, but be warned that the sample applications are not device specific, and the available devices usually assume you are integrating them into an existing hardware-controlled environment, so the documentation surrounding their usage tends to be sparse.

Using the ControlThink SDK is very straight forward. You simply plug in the USB dongle, reference their DLL from your .Net application, and connect to the controller with a single line of code. Once connected, you can manage your network by adding devices, removing devices, iterating the list of devices, etc.

I ended up writing three applications to support Stall Status:

  1. An administrative application for adding/removing/listing the Z-Wave devices on my network.  This is called StallStatusAdmin.
  2. A Windows Service to listen for open/close events from the door sensors and notify any connected client.  This is called StallStatusController.
  3. A Silverlight 2.0 client application (the end-user interface) that connects to the Windows Service to receive event notifications.  This is called StallStatusSilverlight.

Setting up the Z-Wave network

The administration application is a simple Windows Forms EXE that allows me to manage the network by adding, removing and listing the devices. This is simple to do using the ControlThink SDK – in most cases involving just a couple lines of code. Each Z-Wave device has some mechanism to put the device into “programming mode” . The HRDS1 has a button on the back that when pressed, puts the device into programming mode and allows the device to accept programming codes from the controller.

Listing 1 shows how to add a device to the network. Adding a device consists of registering the device with the controller, and then creating a relationship between the controller and the device's association group to enable the device to route messages to the controller. Once a device is registered, the controller assigns a unique NodeID that we will use to differentiate the three sensors from each other.

C#

ZWaveController controller = Connect(); 
MessageBox.Show("After pressing OK, press the \"programming\" button on your device.");
ZWaveDevice device = controller.AddDevice();
            
device.Groups[1].Clear();
device.Groups[1].Add(controller.Devices.GetByNodeID(controller.NodeID));
VB.Net
Dim controller As ZWaveController = Connect()
MessageBox.Show("After pressing OK, press the ""programming"" button on your device.")
Dim device As ZWaveDevice = controller.AddDevice()

device.Groups(1).Clear()
device.Groups(1).Add(controller.Devices.GetByNodeID(controller.NodeID))

Listing 1 – Adding a device to the network.

 

Getting a list of the registered devices on the network is as easy a iterating through the Devices collection on the controller.

C#

string result = "";
ZWaveController controller = Connect();

if (controller.Devices.Count > 0) {
   foreach (ZWaveDevice d in controller.Devices)    {
    result += d.GetType().ToString() + " as Node #" + d.NodeID + " PollEnabled = " + d.PollEnabled + "\r\n";
   }
}
else {
   result = "No devices found on the network";                
}
MessageBox.Show(result);
VB.Net
Dim result As String = ""
Dim controller As ZWaveController = Connect()

If controller.Devices.Count > 0 Then
   For Each d As ZWaveDevice In controller.Devices
    result &= d.GetType().ToString() & " as Node #" & d.NodeID & " PollEnabled = " & d.PollEnabled & Constants.vbCrLf
   Next d
Else
   result = "No devices found on the network"
End If
MessageBox.Show(result)

Listing 2 – Listing the devices in the network.

Receiving Z-Wave Events

Once the devices are registered on the network, they will send event notifications back to the controller each time the sensor is triggered. The next steps for us are to write code to receive the events and forward them to any client applications that are connected. To do this we’ll write a Windows Service that listens for events from the sensors.

All of the work for listening for events from the Z-Wave devices is handled for us by the ControlThink SDK. We simply connect to the controller and sink the LevelChanged event. We do this in our Run method, which is called from within our service’s OnStart function.

C#
public void Run() {
   try  {
       _controller = new ZWaveController();
       try  {
          _controller.Connect();
       }
       catch (Exception ex) {
          throw new Exception("Unable to connect.\r\n" + ex.ToString());
       }

       _controller.LevelChanged += new ZWaveController.LevelChangedEventHandler(_controller_LevelChanged);

       _policyListener = new PolicyListener();
       _listener = new SocketListener();
       _listener.Port = 4530;
             
        new System.Threading.Thread(new System.Threading.ThreadStart(_listener.StartSocketServer)).Start();
        new System.Threading.Thread(new System.Threading.ThreadStart(_policyListener.StartSocketServer)).Start();
        new System.Threading.Thread(new System.Threading.ThreadStart(ClearRecent)).Start();
   }
   catch (Exception ex) {
       Logger.WriteException(ex);
   }
}

VB.Net

Public Sub Run()
   Try
       _controller = New ZWaveController()
       Try
          _controller.Connect()
       Catch ex As Exception
          Throw New Exception("Unable to connect." & Constants.vbCrLf & ex.ToString())
       End Try

       AddHandler _controller.LevelChanged, AddressOf _controller_LevelChanged

       _policyListener = New PolicyListener()
       _listener = New SocketListener()
       _listener.Port = 4530

        CType(New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf _listener.StartSocketServer)), System.Threading.Thread).Start()
        CType(New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf _policyListener.StartSocketServer)), System.Threading.Thread).Start()
        CType(New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf ClearRecent)), System.Threading.Thread).Start()
   Catch ex As Exception
       Logger.WriteException(ex)
   End Try
End Sub

Listing 3 – Listening for Z-Wave events when our service starts up.

 

In addition to listening for events from the ZWave Controller, our Run method also sets up socket listeners for talking to the Silverlight client application, which we will talk about later. An important detail to note, however, is the use of threading in the Run method. Remember that this is called directly from our service’s OnStart code which we should not block - if you block OnStart, you’ll see the service stuck at “Starting...” in the Service Control Manager. Anything that will block or that is long running (such as a socket listener) needs to run on a separate thread.

When the controller detects that one of the sensors has changed its state, it throws the LevelChanged event to our service. Our service handles LevelChanged in the _controller_LevelChanged method. The signature for the LevelChanged event looks like this:

void _controller_LevelChanged(object sender, ZWaveController.LevelChangedEventArgs e)

The LevelChangeEventArgs contains the original device that raised the event. We have three door sensors connected to our network. When any of them are triggered, LevelChanged is raised and we can inspect the LevelChangedEventArgs to get the NodeID of the unique identifier of the device. That’s all there is to interacting with the Z-Wave network.

Silverlight 2.0

Now that we’ve got the server infrastructure set up it is time to build a client application so we can actually start using Stall Status. I built the client application in Silverlight as an exercise to show how to use Silverlight 2.0’s access policy to make cross domain network calls from a Silverlight client application. I also knew that it is simple to deploy a Silverlight application as a Windows Vista Gadget, and the Sidebar was to be my preferred deployment model for the application.

StallStatus2

Figure 1 – Stall Status interface

The main UI for the application is pretty simple – three ellipses, one for each stall, colored green if the stall is available and red if the stall is occupied. Most of the challenge with building the client piece has to do with connecting to the service and listening for updates. To do this, we create a persistent TCP connection and wait for data to be sent from the service over the socket. This is much better than a polling approach for an application like this because of the way the Z-Wave door sensors work. They are in sleep mode most of the time, and are only using power when they transmit a signal due to the door opening or closing. If we polled the devices frequently, it would kill the batteries in the device. Also, most of the time there is no change to the state of the doors, so polling would really be a waste.

Silverlight contains certain security mechanisms around network connectivity, especially TCP sockets. This is due to the fact that a Silverlight application runs in the context of the user’s browser, so special controls need to be in place to limit the type of connectivity that Silverlight applications can initiate. For socket-based connections, Silverlight 2.0 requires an access policy to be in place on the target server. This access policy essentially is a challenge/response mechanism that prevents a Silverlight application from connecting to an arbitrary server, potentially masquerading as the end user.

Access Policy

An Access Policy must be in place on the server for in order to enable a Silverlight 2.0 client to connect using sockets. The Silverlight framework handles all of the validation for the presence of the access policy, so your client application does not need to do anything. However, if the proper policy is not in place on the target server, Silverlight will throw an Access Denied exception, so your client application should be prepared to handle this situation. This is all very similar to how network access in Flash applications work. In fact, Silverlight can consume Flash policy files directly, as well as Silverlight policy files. In the Stall Status application, we’re going to be using Silverlight policy.

When a Silverlight application attempts to open a socket to a target server, Silverlight will automatically look for an access policy on the server by establishing a TCP connection over port 943, and sending a policy request. The policy request is simply an XML-style string: <policy-file-request/>. It is up to the author of the service that is receiving the request to ensure that they properly respond to this request, or else Silverlight will raise an exception on the client. All of this is described in detail in the MSDN documentation on the new network security restrictions in Silverlight 2.0.

In order to respond to a request for an access policy on our server, we need to implement a network listener on port 943 that responds to Silverlight’s policy file request. Dan Wahlin wrote a multi-part blog post showing exactly how to do this, and the listener I used for Stall Status, plus much of the other socket code, is copied from his example. I recommend reading his post - it is a great primer on writing TCP socket clients in Silverlight 2.0.

Our listener class run in our windows service and simply sets up a listener on port 943. When it receives a policy file request, it returns the appropriate response, as shown below.

<?xml version="1.0" encoding ="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from>
        <domain uri="*" />
      </allow-from>
      <grant-to>
        <socket-resource port="4530" protocol="tcp" />
      </grant-to>
    </policy>
  </cross-domain-access>
 </access-policy>

This response tells Silverlight to allow incoming connections to port 4530, which is the port that our service is listening on to accept connections from our Silverlight client.

Why is this important? Well, without access policy in place, there is nothing that prevents a malicious Silverlight application from connecting to a server without permission. For example:

  • Suppose you have an active session with your online bank. A malicious Silverlight application served up from Site A could attempt to connect to your bank using your existing login token (which may be stored as a cookie). With access policy in place, your bank would need to have a policy stating that it allowed Silverlight applications originating from Site A to connect to your bank.
  • Suppose you connect to Site A and download a malicious Silverlight application. The application can potentially connect to your corporate intranet and because you are on a corporate domain, the Silverlight app impersonates you. With the access policy rules in place, this would be prevented.

Persistent client connections

Now that we’ve got our access policy in place, we’re ready to code up our Silverlight client to connect to our server and wait for updates. Silverlight 2.0 can only connect using sockets over the port range 4502-4534. Our Windows Service uses 4530. It creates a listener on this port and then sits and waits for connections from our Silverlight clients.

C#

_listener = new TcpListener(IPAddress.Any, Port);
_listener.Start();
while (true)
{
   _waitEvent.Reset();
   _listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
   _waitEvent.WaitOne();
}

VB.Net

_listener = New TcpListener(IPAddress.Any, Port)
_listener.Start()
Do
   _waitEvent.Reset()
   _listener.BeginAcceptTcpClient(New AsyncCallback(AddressOf OnBeginAccept), Nothing)
   _waitEvent.WaitOne()
Loop

Listing 4 – Our main listener for client connections

 

We’re using asynchronous sockets, so we’ve set up a delegate – OnBeginAccept – to accept and process our connections. The code to process the connects is listed below.

C#

_waitEvent.Set();
TcpListener listener = _listener;
TcpClient client = listener.EndAcceptTcpClient(ar);

if (client.Connected)
{
   Logger.WriteLine("Accepted a connection from " + client.Client.RemoteEndPoint.ToString());

   StreamWriter writer = new StreamWriter(client.GetStream());
   writer.AutoFlush = true;
   _clients.Add(writer);
   writer.WriteLine("Connected.");

   if (ClientConnected != null)
   {
     ClientConnected(this, null);
   }
}

VB.Net

_waitEvent.Set()
Dim listener As TcpListener = _listener
Dim client As TcpClient = listener.EndAcceptTcpClient(ar)

If client.Connected Then
   Logger.WriteLine("Accepted a connection from " & client.Client.RemoteEndPoint.ToString())

   Dim writer As New StreamWriter(client.GetStream())
   writer.AutoFlush = True
   _clients.Add(writer)
   writer.WriteLine("Connected.")

   If ClientConnected IsNot Nothing Then
     ClientConnected(Me, Nothing)
   End If
End If

Listing 5 – Accepting a new client connection

 

Here we are simply adding a StreamWriter for each connected client to a List. We do this so when we receive events from the door sensors, we can loop through this List and notify all of the clients. We notify them by writing a tokenized string to the StreamWriter. We also have code to remove any clients from the list if we fail to write to their stream. This way we can catch the situation where the client disconnected.

C#
for (int i=_clients.Count-1;i>=0;i--)
{
   try
   {
      _clients[i].WriteLine(eventText);
   }
   catch (Exception ex)
   {
      Logger.WriteException(ex);
      Logger.WriteLine("Removing client # " + i + " because we failed to send them data.");
      _clients.Remove(_clients[i]);
   }
}

VB.Net

For i As Integer = _clients.Count-1 To 0 Step -1
   Try
      _clients(i).WriteLine(eventText)
   Catch ex As Exception
      Logger.WriteException(ex)
      Logger.WriteLine("Removing client # " & i & " because we failed to send them data.")
      _clients.Remove(_clients(i))
   End Try
Next i

Listing 6 – Sending an update to all clients

 

The text we pass to each client (represented in the code above by the eventText variable) is simply the NodeID of the sensor that triggered the event and the state (open/closed) on the sensor. The Silverlight client receives this text, parses it out, and updates the color of the ellipse accordingly.

C#
private void UpdateText(string text)
{
    string[] parts = text.Split(' ');

    if (parts.Length != 2) {
        return;
    }

    int stallNumber = Convert.ToInt32(parts[0]);
    string status = parts[1].ToLower();

    StatusIndicator thisStall = null;
    switch (stallNumber) {
        case 1:
            thisStall = this.Stall1;
            break;
        case 2:
            thisStall = this.Stall2;
            break;
        default:
            thisStall = this.Stall3;
            break;
    }

    if (status.Trim() == "open") {
        thisStall.Status = StallStatus.Open;
    }
    else if (status.Trim() == "closed") {
        thisStall.Status = StallStatus.Closed;
    }
    else if (status.Trim() == "recent") {
        thisStall.Status = StallStatus.Recent;
    }
    else {
        thisStall.Status = StallStatus.Unknown;
    }
}
VB.Net
Private Sub UpdateText(ByVal text As String)
    Dim parts() As String = text.Split(" "c)

    If parts.Length <> 2 Then
        Return
    End If

    Dim stallNumber As Integer = Convert.ToInt32(parts(0))
    Dim status As String = parts(1).ToLower()

    Dim thisStall As StatusIndicator = Nothing
    Select Case stallNumber
        Case 1
            thisStall = Me.Stall1
        Case 2
            thisStall = Me.Stall2
        Case Else
            thisStall = Me.Stall3
    End Select

    If status.Trim() = "open" Then
        thisStall.Status = StallStatus.Open
    ElseIf status.Trim() = "closed" Then
        thisStall.Status = StallStatus.Closed
    ElseIf status.Trim() = "recent" Then
        thisStall.Status = StallStatus.Recent
    Else
        thisStall.Status = StallStatus.Unknown
    End If
End Sub

Listing 7 – Receiving the update on the client from the server

 

The only other thing the application contains is the concept of "recently occupied". Once a stall has been vacated, we track it as recently occupied. This shows up on the client application as a yellow ellipse. To do this we keep track of a timestamp whenever the door opens.  Our Service contains a dedicated thread that checks for doors opened more than 2 minutes ago. If it finds any, it changes the door's status from “Recent” to a status of “Opened”.

C#
private void ClearRecent()
{
  while (!_wait.WaitOne(10000,false))
  {
    var q = from stall in _stalls
      where stall.Timestamp.AddMinutes(2) < DateTime.Now && stall.State.Equals(DoorState.Recent)
      select stall;

    foreach (Stall stall in q)
    {
      if (stall.State.Equals(DoorState.Recent)) {
        stall.State = DoorState.Open;
        Broadcast(stall.NodeID, DoorState.Open);
      }
    }
  }
}
VB.Net
Private Sub ClearRecent()
  Do While Not _wait.WaitOne(10000,False)
    Dim q = From stall In _stalls _
            Where stall.Timestamp.AddMinutes(2) < DateTime.Now AndAlso stall.State.Equals(DoorState.Recent) _
            Select stall

    For Each stall As Stall In q
      If stall.State.Equals(DoorState.Recent) Then
        stall.State = DoorState.Open
        Broadcast(stall.NodeID, DoorState.Open)
      End If
    Next stall
  Loop
End Sub

Listing 8 – Clearing the “recently opened” flag

Vista Sidebar Gadget

Once I had the Service and Silverlight client apps written, it was time to deploy the client as a Vista Sidebar Gadget. This is actually pretty simple to do with a Silverlight application, but there are a couple things to work through for 64-bit versions of Vista.

Packaging a Silverlight application to run as a Gadget is not that different than running it locally or via a web page. You simple collect all of the files for your application, add a manifest file, zip them up, and rename the ZIP file so it has a .gadget file extension.

One easy way to create the manifest file is to start from an existing file from one of your existing gadgets. You can find your installed gadgets in the folder \Users\<user name>\AppData\Local\Microsoft\Windows Sidebar\Gadgets. Note that by default the AppData folder is hidden. Check the folders of any of your gadgets and you’ll see that they all contain an gadget.xml file. Copy one of these, open it, and make any changes that you need to. Most of the elements in the file are self explanatory, but for a full explanation, check out this article.

Once you’ve got your files and manifest packaged up, you can distribute your gadget. Users can run .gadget files directly in Vista and Vista automatically installs them into the sidebar.

StallStatusGadget2

Figure 2 – Stall Status deployed as a Vista Sidebar gadget

One note with Silverlight-based gadgets and the 64-bit version of Windows Vista: Since Silverlight is a 32-bit application, you cannot directly host Silverlight-based gadgets in the 64-bit version of Sidebar.exe (which is the running by default on 64-bit Vista). As a work around, you can run the 32-bit version of Sidebar.exe, which is located in the \Program Files (x86)\Windows Sidebar\ folder.

As we’ve seen in this article it is easy to use .Net to control a Z-Wave network, and there are tons of creative uses for the technology. Writing network-enabled Silverlight applications has become much more powerful in Silverlight 2.0. And deploying Silverlight-based applications as Vista Sidebar Gadgets is pretty simple.

The Discussion

  • User profile image
    Frank

    Nice App.  The only thing I would change is the time delay after a stall is no longer occupied.  Smiley

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.