Z-Wave protokollen i C# – Del 2

20. december, 2009 af Henrik Leidecker Jørgensen Skriv en kommentar »

I denne artikel ændres koden, så klienten ikke længere selv håndterer, hvornår den næste besked kan sendes. Der oprettes ligeledes en klasse, der repræsenterer den Z-Wave enhed, der kommunikeres med.

Funktionaliteten er den samme, som i sidste artikel. Først slukkes og derefter tændes den samme Z-Wave enhed.

Switch s = new Switch(nodeId, zwavePort)
s.Off();
s.On();

Det er vigtigt at have forstået de tidligere artikler for at få det fulde udbytte af eksemplet.

Til eksemplet anvendes de samme enheder og konfiguration, som blev brugt i artiklen ‘Z-Wave protokollen i C#‘.

Opbygningen af beskeder til at tænde og slukke for en enhed samt håndtering af de tilhørende beskeder fra Z-Wave controlleren samles i klassen Switch.

class Switch : ZWaveNode 
{ 
    public void On()
    { 
        ...
    }
 
    public void Off()
    {
        ...
    }
 
    public void MessageHandler(byte[] message) 
    { 
        ... 
    } 
}

Som der fremgår af koden, så er Switch klassen nedarvet fra klassen ZWaveNode.

abstract class ZWaveNode     
{
    public byte NodeId { get; private set; }
    private ZWavePort zp;
    ...
}

ZWaveNode klassen definerer en række egenskaber, der er fælles for alle Z-Wave enheder. Den indholder også en reference til ZWavePort objektet, så det er muligt at sende/modtage beskeder til/fra Z-Wave controlleren.

Et af målene for denne artikel er, at klienten ikke længere skal holde styr på, hvornår den næste besked kan sendes – undgå implementeringen af en pause.

Thread.Sleep(5000); // Wait for 5 seconds

Switch objektet,  starter beskedsekvensen, så det er naturligt også at lade den afgøre, om beskedsekvensen er afsluttet. I ZWavePort klassen, er der indført en variabel til at holde styr på, om der er en beskedsekvens igang.

private Boolean messagingLock;

Er variablen sat til truebetyder det, at en beskedsekvens er igang. Der er indført en funktion SetMessagingLock til at sikre, at der kun er en tråd ad gangen, der tilgår denne variabel.

private Boolean SetMessagingLock(Boolean state) 
{
    lock (messagingLocker)
    {
        if (state)
        {
            if (this.messagingLock)
            {
                return false;
            }
            else
            {
                this.messagingLock = true;
                return true;
            }
        }
        else
        {
            this.messagingLock = false;
            return true;
        }
    }
}

Funktionen bliver kaldt i SendMessagei ZWavePort klassen. Dette gøres for at sikre, at der ikke er nogen beskedsekvenser igang før en ny besked sendes.

public void SendMessage(byte[] message)
{
    if (sp.IsOpen == true)
   {
        if (message != MSG_ACKNOWLEDGE) 
        {
            if (!SetMessagingLock(true)) return false; 
            sendACK = false; 
            message[message.Length - 1] = GenerateChecksum(message); // Insert checksum
        }
        System.Console.WriteLine("Message sent: " + ByteArrayToString(message)); 
        sp.Write(message, 0, message.Length); 
        return true; 
}

Er der en beskedsekvens igang, så returnerer SendMessage funktion false og der afsendes ingen besked. ACK beskeder afsendes uafhængigt af dette system.
Hvis det lykkes SendMessage at sætte variablen til true, kan en ny beskedsekvens ikke startes før variablen igen er sat til false . Variablen sættes senere til false af Switch objektet.
For at sætte variablen til falseskal Switch objektet vide, når der kommer et response på en tænd eller sluk request. Det er muligt at blive orienteret om beskeder fra Z-Wave controlleren ved at kalde SubscribeToMessagesfunktionen på ZWavePort klassen. I ZWaveNode klassens constructor kaldes SubscribeToMessages, hvilket betyder, at Switch objektets MessageHandler funktion kaldes, hver gang en besked modtages.

public ZWaveNode(byte nodeId, ZWavePort zp)
{
    this.NodeId = nodeId;
    this.zp = zp;
    ZWavePort.MessageHandler messageHandler = new ZWavePort.MessageHandler(MessageHandler);
    zp.SubscribeToMessages(messageHandler);
}

Der kommer kun to typer af beskeder fra Z-Wave controlleren i dette eksempel

  • ACK – beskedsekvensen er ikke afsluttet, da dette er en kvittering for den, af C# klienten, afsendte request
  • Response – beskedsekvensen er afsluttet, da dette er svaret på den, af C# klienten, afsendte request (ACK på responset sendes automatisk før MessageHandler funktionen kaldes)

MessageHandlerfunktionen i Switch objektet kalder funktionen MessagingCompleted, hvis det ikke er en ACK besked.

public override void MessageHandler(byte[] message)
{
    if (message != MSG_ACKNOWLEDGE)
    {
        MessagingCompleted();
    }
}

Funktionen MessagingCompleted sætter messagingLockvariablen i ZWavePort objektet til false – så en ny beskedsekvens kan initieres.

For at sikre, at en besked sendes, selvom  den initielt afvises af SendMessagei ZWavePort objektet, er følgende SendMessagefunktion indført i ZWaveNode klassen.

protected Boolean SendMessage(byte[] message) 
{
    while (!zp.SendMessage(message)) Thread.Sleep(100);
    return true;
}

Afvises beskeden af SendMessagefunktionen i ZWavePort objektet, så ventes 100 ms og beskeden forsøges sendt igen. Dette gentager sig indtil beskeden er sendt.

Med eksemplet i denne artikel er det nu muligt at sende flere kommandoer efter hinanden uden at indføre en pause i klienten mellem de to beskeder.

Switch s = new Switch(nodeId, zwavePort)
s.Off();
s.On();

I næste artikel tilføjes en Dimmer til deviceklasserne og der rettes op på en svaghed i det eksisterende design – deviceklasserne ser ikke på, om de response-beskeder de modtager, er svar på en request sendt fra dem.

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

Annoncer

3 comments

  1. René siger:

    Hej Henrik

    Jeg følger interesseret med i denne artikkelserie.
    Ville næste skridt ikke være at lægge afsendelse og modtagelse af beskeder over i sin egen tråd. (producer/consumer pattern) så slipper du for locking problematikken da der så kun er én tråd der arbejder på selve porten.

  2. Henrik Leidecker Jørgensen siger:

    Et producer/consumer pattern er oplagt i forbindelse med håndteringen af beskeder og jeg har da også tidligere benyttet mig af dette i forbindelse med en Z-Wave implementation.
    Det løser dog ikke problematikken omkring det, at en beskedsekvens skal være afsluttet før en ny kan påbegyndes – det kan ikke være ZWavePort klassens ansvar at afgøre dette, da det afhænger af indholdet i den første request. I nogle tilfælde efterfølges en request fra den ene part af et response og derefter en request fra den anden part. I sådan et tilfælde, skal et device objektet beholde kontrollen over, hvornår en ny beskedsekvens kan påbegyndes.
    Når implementationen skal til at fungere med rigtige brugere, så er det selvfølgelig nødvendigt at indføre beskedkøer, da brugeren i praksis kan aktivere en knap på f.eks. en IPod langt hurtigere end Z-Wave kommunikationen over seriel porten kan afvikles. Når først køen er implementeret, bliver det hurtigt klart, at det skal være en prioriteret kø – nogle beskeder skal sendes før andre f.eks. ACK.
    Jeg har forsøgt at holde implementationen så simpel som det er muligt og jeg vil meget gerne høre fra dig, hvis du har et forslag til at opnå det samme men med en mere simpel/intuitiv implementation.
    Så mit svare er ja, det kunne være næste skridt, men samtidigt siger jeg også nej, da jeg forsøger at holde implementationen simpel og prøver at tilgodese, at der kommer så meget Z-Wave specifik implementation med som det er muligt.

  3. René siger:

    Hej Henrik

    Jeg er helt med på at eksempel implementeringer skal være så simple som muligt. Personligt vil jeg også helst se så meget ZWave specifiks om muligt :o )

    /René