Z-Wave device discovery in C#

January 14th, 2010 by Henrik Leidecker Jørgensen | Edit this entry Leave a reply »

This article will cover the C# implementation of the protocol discussed in ‘Device discovery in Z-Wave‘.
The actual changes caused by the new requests are limited to a reimplementation of the callback id. Not all responses support the callback id and this affects the completion of the message sequences.
The real challenge is connected to the fact that the messages send from Z-Wave controller to the C# client are used for more than completing the message sequences – the content itself is interesting as it reveals information about the devices connected to the Z-Wave network.

The identification of Z-Wave devices basically consists of two new requests.

  • Ask for the node ids in the Z-Wave network (discovery)
  • Ask for the type of each node

A Controller is added to the device classes.

class Controller : ZWaveNode
{
    ....
 
    public void Discovery()
    {
        ...
    }
 
    public void GetNodeTypes(byte nodeId)
    {
        ...
    }
 
    ....
 
}

This article is just one out of a series of articles about the Z-Wave protocol and how it can be implemented in C#. Reading the previous articles before continuing with this one will provide a clear advantage.

The new device class Controller will be responsible for sending the needed requests related to discovery of Z-Wave devices. Two additional methods are implemented in the class to enable parsing of the messages being sent from the Z-Wave controller to the C# client – ParseNodesFromBitMask and ParseNodeType.

class Controller : ZWaveNode
{
    ....
 
    public List ParseNodesFromBitMask(byte[] message)
    {
        List nodeList = new List();
        // Decode the nodes in the bitmask (byte 9 - 37)
        byte k = 1;
        for (int i = 7; i < 36; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                if ((message[i] & ((byte)Math.Pow(2, j))) == ((byte)Math.Pow(2, j)))
                {
                    nodeList.Add(k);
                }
                k++;
            }
        }
        return nodeList;
    }
 
    public byte ParseNodeType(byte[] message)
    {
        return message[8];
    }
 
    ....
 
}

In the previous article it became clear that the callback id can’t be used for all request/response pairs. The SendMessage method in the ZWaveNode device class is modified in order to allow the inherited classes to make the decision whether or not to utilize a callback id.

class ZWaveNode
{
    ....
 
    protected Boolean SendMessage(byte[] message, byte callbackId)
    {
        callbackIds.Add(callbackId); // Add callback id to the list
        message[message.Length - 2] = callbackId; // Insert the callback id into the message
        return SendMessage(message);
    }
 
    protected Boolean SendMessage(byte[] message)
    {
        while (!zp.SendMessage(message)) Thread.Sleep(100);
        return true;
    }
 
    ....
 
}

The actual implementation of the MessageHandler method is removed as the signaling of MessagingCompleted no longer is the same across the different Z-Wave devices.
The MessageHandler method in the device class Control signals MessagingCompleted, if a ‘discovery’ or a ‘node type’ request is received from the Z-Wave controller.

class Controller : ZWaveNode
{
    ....
 
    public override void MessageHandler(byte[] message)
    {
        int length = message.Length;
        if (length > 3)
        {
            if (message[2] == 0x01) // Is it a request
            {
                switch (message[3])
                {
                    case 0x02: // Response to a discovery request
                        MessagingCompleted();
                        break;
                    case 0x41: // Response to a node type request
                        MessagingCompleted();
                        break;
                }
            }
        }
    }
 
    ....
 
}

The application is now capable of

  • Sending the relevant requests.
  • Terminate the message sequences properly.
  • Parse the messages from the Z-Wave controller via a method in the device class Controller.

What is missing is to call the parser methods and utilize the returned information. It is obvious to let the device class Controller parse the ‘discovery’ information and then iterative over the node ids asking for the device type. The implementation unfortunately turns out to be too inflexible to support this.

The program consists of two threads. One for receiving messages and one for sending messages.
The strategy for sending messages is to try until it succeeds. The thread will be put to sleep if the attempt is unsuccessful. When the thread wakes up it will retry to send the message. The
MessageHandler
method is called by the receiver thread. The receiver thread will in principle start to send messages if the device class Controller starts to request the node type when the discovery information is received.
The result is that the receiver thread is blocking its own execution. It tries to send a message, but the sending is denied and the thread is put to sleep. The only way that the sending can be allowed is if a message is received and MessagingCompleted is signaled. But a message is never received as the receiver threads tries to send messages and then goes to sleep again and again.

The solution is simple. Any C# object can subscribe to incoming messages from the Z-Wave controller. An object is created in the Main method. The object contains a MessageHandler method which is called when messages arrive from the Z-Wave controller.

static void Main(string[] args)
{
    Program p = new Program();
    ZWavePort zp = new ZWavePort();
    ZWavePort.MessageHandler messageHandler = new ZWavePort.MessageHandler(p.MessageHandler);
    zp.SubscribeToMessages(messageHandler);
 
    ...
}

The purpose of the MessageHandler method is to store either the ‘discovery’ or the ‘node type’ messages from the Z-Wave controller.

public void MessageHandler(byte[] message)
{
    int length = message.Length;
    if (length > 3)
    {
        switch (message[3])
        {
            case 0x02:
                discoveryResponse = message;
                break;
            case 0x41:
                nodeTypeResponse = message;
                break;
        }
    }
}

The Main method can finally be completed with the receiving of the messages from the Z-Wave controller in place.

static void Main(string[] args)
{
    ...
 
    zp.Open();
 
    Controller c = new Controller(zp);
    c.Discovery(); // Send discovery request to the controller
 
    // Wait for the discovery message to arrive
    while (p.discoveryResponse == null) Thread.Sleep(100);
 
    List nodeIds = c.ParseNodesFromBitMask(p.discoveryResponse);
 
    // Loop through the node list and ask for node type
    foreach (byte nodeId in  nodeIds)
    {
        p.nodeId = nodeId;
        c.GetNodeTypes(nodeId);
 
        // Wait for the node type message to arrive
        while (p.nodeTypeResponse == null) Thread.Sleep(100);
 
        // Create the device and add it to the device list
        ZWaveNode z = c.CreateDevice(nodeId, c.ParseNodeType(p.nodeTypeResponse));
        p.devices.Add(nodeId, z);
        System.Console.WriteLine("'" + z.GetType().Name + "' added to the device list.");
        p.nodeTypeResponse = null;
    }
 
    // Discovery completed
    System.Console.WriteLine("Discovery completed");
 
    Dimmer d = (Dimmer) p.devices[0x06];
    Switch s = (Switch) p.devices[0x2B];
 
    // Generate random number
    Random r = new Random();
    byte level = (byte)r.Next(99);
 
    s.Off();       // Switch off node '6'
    d.Off();       // Switch off node '43'
    s.On();        // Switch on node '6'
    d.Dim(level);  // Dim node '43' to random level
 
    System.Console.ReadLine(); // Wait for the user to terminate the program
 
    zp.Close();
}

The strategy applied in the Main method is to send a request and then wait for the messages to arrive. When the results arrive the CreateDevice method in the device class Controller is used to create a corresponding device object.

public ZWaveNode CreateDevice(byte nodeId, byte type)
{
    String className = "ZWave.Devices.";
    switch (type)
    {
        case 0x02:
            className += "Controller";
            this.NodeId = nodeId;
            return this;
        case 0x10:
            className += "Switch";
            break;
        case 0x11:
            className += "Dimmer";
            break;
    }
    return (ZWaveNode)Activator.CreateInstance(Type.GetType(className), new object[] { nodeId, zp });
}

The ReceiveMessage method in the ZWavePort object will in some cases receive a byte array consisting of several messages. The method is modified to handle the case where an ACK message is combined with a response from the Z-Wave controller.

/*
    0x06, 0x01, 0x09, 0x01, 0x41, 0xD1, 0x8C, 0x00, 0x04, 0x10, 0x01, 0xFE
*/

The message is split into two and dispatched accordingly if this type of message is received.

private void ReceiveMessage()
{
    while (sp.IsOpen)
    {
        int bytesToRead = sp.BytesToRead;
        if ((bytesToRead != 0) &amp; (sp.IsOpen == true))
        {
            byte[] message = new byte[bytesToRead];
            sp.Read(message, 0, bytesToRead);
            System.Console.WriteLine("Message received: " + ByteArrayToString(message));
            if (sendACK) // Does the incoming message require an ACK?
            {
                SendACKMessage();
            }
            sendACK = true;
            byte[] msg = message;
            int length = msg.Length;
            if (messageHandler != null)
            {
                if ((msg[0] == 0x06) &amp;&amp; (msg.Length &gt; 1))
                {
                    messageHandler(MSG_ACKNOWLEDGE);
                    byte[] tmp = new byte[length - 1];
                    System.Array.Copy(msg, 1, tmp, 0, length - 1);
                    msg = tmp;
                }
            }
            messageHandler(msg);
        }
    }
}

It is now possible to automatically identify the devices connected to the Z-Wave network but the implementation of a message handler in the ‘client’ code is not the most elegant.

A producer/consumer pattern is introduce for the message sending in the next article. The entire message handling concerning the discovery of the Z-Wave devices can then be moved to the device class Controller.

A forum is now available at digiWave.dk if you have any question, thoughts or ideas concerning the protocol and programming related to Z-Wave.

The source code can be downloaded via this link. You are free to use the code in any manner you like.

Advertisement

Comments are closed.