Identifikation af Z-Wave enheder i C#

13. januar, 2010 af Henrik Leidecker Jørgensen Skriv en kommentar »

I denne artikel behandles C# implementeringen af protokollen, der blev gennemgået i ‘Identifikation af Z-Wave enheder‘..
De tekniske ændringer i forbindelse med de nye requests begrænser sig til en reimplementering af callback id. Ikke alle responses understøtter callback id, hvilket påvirker initieringen/afslutningen af beskedsekvenser.
Den reelle udfordring ligger i, at indholdet af de responses, som Z-Wave controlleren sender til C# klienten ikke længere udelukkende anvendes til at afslutte beskedsekvensen – selve indholdet er interessant, da det fortæller, hvilke enheder, der findes i Z-Wave netværket.

Identifikationen af Z-Wave enheder består basalt set af to ny requests.

  • Spørg efter en oversigt over node id’er i Z-Wave netværket (discovery)
  • Spørg, for hver node id, hvilken type denne node er

Der tilføjes en Controller til deviceklasserne.

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

Artiklen er én ud af en række artikler om Z-Wave protokollen og hvordan denne kan implementeres i C#. Det anbefales, at have læst de foregående artikler.

Det er deviceklassen Controller, der får ansvaret for at sende de nødvendige requests i forbindelse med identifikationen af Z-Wave enheder. Samtidigt er det også den, der skal kunne forstå indeholdet af de beskeder, der sendes tilbage fra Z-Wave controlleren. Derfor indføres der to funktioner ParseNodesFromBitMask og 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];
    }
 
    ....
 
}

I den forrige artikel blev det klart, at callback id ikke kan anvendes for alle request/response par. SendMessage funktionen i ZWaveNode deviceklassen ændres derfor, så de nedarvede deviceklasser selv bestemmer, om der skal anvendes 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;
    }
 
    ....
 
}

Implementeringen fra MessageHandler funktionen fjernes, da baggrunden for at signalere MessagingCompleted ikke længere er den samme på tværs af alle Z-Wave enheder. Tidligere var det nok at se på callback id, men dette understøttes ikke for de beskeder, der sendes ved identifikation af enheder i Z-Wave netværket.
MessageHandler funktionen i deviceklassen Controller sender MessagingCompleted, hvis der modtages et ‘discovery’ eller et ‘node type’ request fra Z-Wave controlleren.

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;
                }
            }
        }
    }
 
    ....
 
}

Nu er programmet istand til at

  • Sende de relevante requests.
  • Afslutte beskedsekvenserne korrekt.
  • Parse beskeder fra Z-Wave controlleren via en funktion i deviceklassen Controller.

Det, der mangler, er at kalde parserfunktionerne og udnytte informationen. En oplagt mulighed er at lade deviceklassen Controller parse ‘discovery’ informationen og derefter spørge alle enheder om, hvilken type de er. Her viser det sig dog, at den nuværende implementering ikke er fleksibel nok.

Programmet baserer sig på to tråde. En til modtagelse og en anden til afsendelse af beskeder.
Strategien for afsendelse af beskeder er, at forsøge indtil det lykkes. Tråden lægges til at ’sove, hvis det ikke lykkes. Når tråden ‘vågner’ forsøges igen.
MessageHandler
funktionen bliver kaldt af ‘modtager’ tråden. Problemet, med at lade deveiceklassen Controller spørge alle enheder om, hvilken type de er, når der modtages ‘discovery’ information, består i, at det pludselig er ‘modtager’ tråden, der sender beskeder.
Resultatet er, at ‘modtager’ tråden blokerer sig selv. Den sender en besked, der afvises, hvorefter tråden lægges til at sove. Det der kan tillade en besked at blive sendt er, at der modtages en besked. Tråden kommer dog aldrig til at modtage beskeder, da den hele tiden først vil forsøge at sende en besked.

Løsningen er relativ enkel. Ethvert C# objekt kan abonnere på beskeder fra Z-Wave controlleren. Derfor oprettes der et objekt i Main funktionen, som indholder en MessageHandler funktion.

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

Det eneste formål MessageHandler funktionen tjener er at gemme ‘discovery’ og ‘node type’ beskederne fra Z-Wave controlleren.

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;
        }
    }
}

Modtagelse af beskeder fra Z-Wave controlleren er det sidste, der mangler for at gøre Main funktionen færdig.

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();
}

Strategien i Main funktionen er at sende et request og så vente på, at der kommer svar. Der gøres brug af  funktion CreateDevice i deviceklassen Controller, som tager et node id og en type som input og returnere et objekt af typen ZWaveNode.

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 });
}

I visse tilfælde vil ReceiveMessage funktionen i ZWavePort objektet modtage et byte array, som består af flere beskeder. Funktionen ændres så det tilfældet håndteres, hvor ACK og response fra Z-Wave controlleren kombineres.

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

Sker det, så splittes den oprindelige besked i to, som herefter sendes ud hver for sig.

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);
        }
    }
}

Det er nu muligt automatisk at identificere, hvilke enheder der findes i Z-Wave netværket. Løsningen med at håndtere beskederne fra Z-Wave controlleren via Main funktionen er dog ikke særlig elegant.
I næste artikel indføres der et producer/consumer pattern for afsendelse af beskeder. Det betyder at funktionaliteten kan samles i deviceklassen Controller.

Har du nogen spørgsmål, idéer eller tanker omkring protokollen og programmering i forbindelse med Z-Wave, så findes der nu et forum til dette på digiWave.dk.

Kildekoden kan hentes via dette link. Der er ingen begrænsninger på anvendelsen af kildekoden.

Annoncer

7 comments

  1. René siger:

    Hej Henrik

    Jeg har hentet din kode og kigget lidt på den. Jeg er ikke selv den store C# fan, og påtænker derfor at portere til Java.

    .. jeg kan dog ikke finde nogen Copyright/Licens nogle steder. Jeg vil derfor spørge direkte.

    Jeg prøver selv at holde min egen kode i meget åbne licenser gerne baseret på PHK’s beerware-license (som har sin egen wikipedia-side her: http://en.wikipedia.org/wiki/Beerware) og ville håbe at Java udgaven kunne få denne licens.

    /René

  2. Henrik Leidecker Jørgensen siger:

    Hej René,

    Jeg undersøger sagen, men umiddelbart har jeg ingen intentioner om at begrænse brugen af koden på nogen måde. Jeg vender tilbage med et svar.

    Mvh,
    Henrik

  3. Hej Henrik
    Jeg er igen med at lave et class bibliotek, hvor jeg bruger din kode som grundlag. Jeg har oprettet nogle class med konstanter, og brugt dem i koden. Jeg planlægge at lave et projekt på CodePlax, så flere kan være med til at lav code mm.
    Koden er under http://www.gnu.org/copyleft/gpl.html og vil tilhører digiwave, hvis det er OK.
    Hvad siger du til det og tak for et godt arbejde.
    /Flemming.
    PS det er velkommen til at skriv til mig på min Email, og du kan også få mit solution.

  4. Henrik Leidecker Jørgensen siger:

    Hej Flemming,
    Det lyder spændende. Jeg vil foreslå dig, at skrive et indlæg i ‘Z-Wave -> Projects’ sektionen på digiWave.dk’s forum (http://www.digiwave.dk/forum), så andre bliver klar over, at du er i gang med et projekt og ønsker at samarbejde. Jeg har fået henvendelse fra andre omkring Z-Wave projekter og har bedt dem om at gøre det samme.
    Det kunne være spændende, hvis der blev stablet et Z-Wave projekt på benene med flere deltagere.
    Som jeg skrev tidligere til René, så er jeg i gang med at undersøge sagen omkring licens. Jeg er åben for forslag og har som sagt ingen intentioner om at lægge begrænsning på den kode, der allerede er lagt på digiWave.dk.
    /Henrik

  5. Hej Henrik
    Jeg tænkte lige på, om det ikke ville være bedre hvis
    jeg lagt min code på digiWaves.dk, hvis det er mulig.

    Hvad siger du til den idé.

    Det er nogle artikeler du skriver.

    /Flemming

  6. Henrik Leidecker Jørgensen siger:

    Hej Flemming,
    Jeg synes at idéen med at lægge kildekoden i et etableret repository lyder bedre – der får du automatisk en lang række muligheder, som du ikke vil have på digiWave.dk f.eks. issue tracking, versionsstyring …
    Vi kan så lave en ’sticky’ post på forummet med links til de forskellige projekter.
    /Henrik.

  7. Henrik Leidecker Jørgensen siger:

    Relateret til tidligere spørgsmål omkring licens på kildekoden. Jeg har nu opdateret alle artikler med en kommentar om, at der er ingen begrænsninger er på anvendelsen af kildekoden.