Z-Wave – Définition des trames – partie 2

Cet article fait suite aux articles :

Dans l’article précédent, nous avons vu comment initialiser un contrôleur et comment connaître les esclaves qui lui sont associés. Nous allons maintenant voir comment communiquer avec un esclave.

1 – Equipement

Pour communiquer avec un esclave, il nous faut un esclave. Dans l’exemple que nous allons voir, nous allons contrôler un bouton ON / OFF. J’utilise un FGS-211 de chez Fibaro. C’est du bon matériel bien qu’un peu cher. Il faut compter 60 €.

ZWAVE

2 – Format d’une trame d’échange de données

Lorsque le contrôleur échange des données avec les esclaves, Z-Wave utilise une trame assez standardisée. Le format est le suivant.

Capture

Nous retrouvons les éléments suivants :

  • 1 – Header qui vaut « 01 » pour un début de trame
  • 2 – La taille de trame à partir de l’octet 2 (cela revient à N-1)
  • 3 – Il s’agit d’une requête dont la valeur est « 00 »
  • 4 – Nous voulons envoyer des données, la valeur à utiliser est « 13 »
  • 5 – Identifiant de l’esclave récupéré via la trame de récupération des ids des nodes (voir articles précédent)
  • 6 – Taille des paramètres à envoyer vers l’esclave (cela correspond à N – 1 – 7)
  • 7..N-2 – paramètres à envoyer à l’esclave
  • N-1 – Le type de transmission qui vaut « 01 | 04 | 20 ». Il existe plusieurs types, dans l’ordre, nous demandons un accusé de réception | de transférer le message vers une autre node si besoin | pour le 20, je ne sais pas exactement, son nom est « EXPLORE ».
  • N : le checksum de vérification

 

3 – Obtenir l’état d’un interrupteur

La première étape à l’initialisation d’un logiciel de domotique est de connaitre l’état d’un esclave. Pour savoir si notre bouton est sur un ON ou OFF, il faut envoyer une trame ayant le format suivant :

Début de la trame Taille de la trame – 2 C’est une requête Commande d’envoi de données Id de la node Taille des paramètres Cible de l’action Action Transmission Checksum
0x01 0x08 0x00 0x13 xx 0x02 0x25 0x02 01 | 04 | 20 = 0x31 0xF2

Ici, nous avons deux paramètres :

  • L’élément cible de l’action qui vaut « 25 » pour un bouton
  • L’action « 02 » demande la récupération de la valeur du bouton

La trame de réponse est de la forme suivante :

Début de la trame Taille de la trame – 2 C’est une réponse Commande d’envoi de données Paramètres Checksum
0x01 0x04 0x01 0x13 0x01 0xE8

Cette trame ne contient qu’un seul paramètre qui signifie simplement que le contrôleur a bien envoyé la demande vers l’esclave. Cependant, nous ne savons pas si notre bouton est sur ON ou OFF …

En fait, l’esclave va envoyer vers le contrôleur une requête de mise à jour de l’état du bouton. Nous allons donc la réceptionner pour la traiter. Elle a la forme suivante :

Début de la trame Taille de la trame – 2 C’est une requête Commande d’envoi de la valeur d’un esclave Id de la node Taille des paramètres Cible de l’action Action Valeur Checksum
0x01 0x09 0x00 0x04 xx 0x03 0x25 0x03 0x00 0xD5

 

Cette requête envoyée par le bouton permet de récupérer son état. Les éléments importants sont :

  • Position 4 : « 04 » est le code indiquant qu’il s’agit d’une commande de mise à jour de l’état de l’esclave
  • Position 8 : nous avons que l’action « 02 » sert à demander l’état du bouton. L’action « 03 » sert à indiquer que le paramètre reçu est l’état du bouton.
  • Position 9 : la valeur du bouton : « 00 » pour éteint et « FF » pour allumé

Nous avons donc vu les échanges nécessaires à la récupération de l’état d’un interrupteur. La plupart des modules simples fonctionnent sur le même principe en terme de récupération de leur état. Par exemple, un interrupteur à plusieurs niveaux (réglage d’intensité lumineuse par exemple) renverra une valeur entre « 00 » et « FF ».

 

4 – Changer l’état de l’interrupteur

Une action intéressante est de pouvoir modifier l’état de notre interrupteur à distance (sinon la domotique n’a pas beaucoup d’intérêt …). La trame à envoyer est la forme suivante :

Début de la trame Taille de la trame – 2 C’est une requête Commande d’envoi de données Id de la node Taille des paramètres Cible de l’action Action Valeur Transmission Checksum
0x01 0x09 0x00 0x13 xx 0x03 0x25 0x01 xx 01 | 04 | 20 = 0x31 0xF2

Ici, nous avons deux paramètres importants :

  • Position 8 : « 01 » est l’action pour modifier l’état du bouton
  • Position 9 : la valeur de l’état du bouton : « 00 » pour éteint et « FF » pour allumer

La réponse est une simple trame validant l’envoi vers l’esclave :

 

Début de la trame Taille de la trame – 2 C’est une réponse Commande d’envoi de données Paramètres Checksum
0x01 0x04 0x01 0x13 0x01 0xE8

 

5 – Conclusion

Dans cet article nous pouvons retenir :

  • Les commandes
    • La commande d’envoi de données est « 13 »
    • La commande de réception d’une demande de mise à jour de l’état est « 04 »
  • Les cibles
    • Un interrupteur binaire à l’identifiant « 25 »
  • Les actions d’un interrupteur
    • La mise à jour de l’état est « 01 »
    • La demande de récupération de l’état est « 02 »
    • La réception de l’état est « 03 »

Dans un prochain article, nous verrons comment configurer les paramètres d’un interrupteur. Pour lui indiquer de s’éteindre automatiquement au bout de 30 secondes par exemple.

Z-Wave – Définition des trames – partie 1

Cet article fait suite à la présentation du protocole Z-Wave et celui sur le début d’implémentation en C#.

Nous allons voir une partie des trames Z-Wave permettant de communiquer avec les contrôleur et les différentes nodes.
Il y a beaucoup de commandes possibles, nous allons voir certaines des principales.

1 – Rappel du format d’une trame

La trame de base à la forme suivante.

Capture

Les acquittements

Les acquittements sont échangés avec les contrôleurs et les nodes.

Un acquittement doit être reçu / envoyé à chaque fois qu’une trame de données est envoyée / reçue. Il peut être de deux types :

  • Positif : la trame contient alors un unique octet 06.
  • Négatif : la trame contient alors un unique octet 15

Toutes les autres trames commencent par l’octet 01 annonçant une trame de données.

 

2 – Trames d’initialisation

Les trames présentées sont nécessaires à l’initialisation d’un système complexe.

2.1 – Récupération du « Home Id » et de l’identifiant de la node

Le « Home Id » est l’identifiant unique du contrôleur. Il permettra à une application de différencier les différents contrôleur branchés sur la machine. Le format de la trame a envoyer est le suivant :

Début de la trame Taille de la trame – 2 C’est une requête Commande pour obtenir le Home Id Checksum
01 03 00 20 DC

La réponse est de la forme :

Début de la trame Taille de la trame – 2 C’est une réponse Commande pour obtenir le Home Id Home id sur 4 octets Id de la node dans le réseau Checksum
01 08 01 20 xx xx xx xx 00 00

Via cette trame, nous récupérons donc deux informations essentielles :

  • L’identifiant unique du contrôleur dans tous les réseaux, le Home Id
  • L’identifiant unique du contrôleur dans son réseau, l’identifiant de la node

 

2.2 – Obtenir la version Z-Wave gérée par le contrôleur

Cette donnée peut être utile lorsqu’on utilise des fonction avancée. La trame a envoyer est la suivante :

ébut de la trame Taille de la trame – 2 C’est une requête Commande pour obtenir la version Checksum
01 03 00 15 E9

La réponse est de la forme :

Début de la trame Taille de la trame – 2 C’est une réponse Commande pour obtenir la version Version Z-Wave jusqu’à l’octet 0 Type du contrôleur Checksum
01 10 01 15 xx xx xx 00 00 00

La trame nous fournie donc les informations suivantes :

  • La version du protocole Z-Wave au format string
  • Le type du contrôleur (01 pour un stick USB) que nous retrouvons dans la trame de détermination du protocole d’un matériel.

 

2.3 – Obtenir la liste des nodes liées au contrôleur

Cette trame est la seconde plus importante après celle permettant la récupération du Home Id puisqu’elle nous donne la liste des identifiants des nodes du réseau du contrôleur.

Début de la trame Taille de la trame – 2 C’est une requête Commande pour obtenir les nodes Checksum
01 03 00 02 DC

La réponse est de la forme :

Début de la trame Taille de la trame – 2 C’est une réponse Commande pour obtenir la version Inconnu Inconnu Taille des données (29) Identifiants des nodes Inconnu Inconnu Checksum
01 25 01 02 05 00 1D 27 octets 04 01 xx

Même si toutes les informations ne sont pas connues et donc non utilisées, les données principales sont bien là : la liste des identifiants des nodes.

La récupération des identifiants des nodes nécessite un peu de réflexion car ils sont intégrés dans les bits des octets. L’algorithme de récupération est le suivant :

  • Pour chaque octet i de 1 à 29
    • Pour chaque bit j de 0 à 7
      • Si l’octet ET 2^j = 2^j alors l’identifiant de la node est (i-1) * 8 + j + 1
    • Fin pour
  • Fin pour

Concrètement, si les 2 premiers octets récupérés sont représentés en bit : 00000011 01001000

  • i = 1; j = 0 : 00000011 ET 00000001 (1) = 00000001 (1) : le résultat est vrai, donc la node (1-1)*8 + 0 + 1 = 1 existe
  • i = 1; j = 1 : 00000011 ET 00000010 (2) = 00000010 (2) : le résultat est vrai, donc la node (1-1)*8 + 1 + 1 = 2 existe
  • i = 1; j = 2 : 00000011 ET 00000100 (4) = 00000000 (0) : le résultat est faux, donc la node (1-1)*8 + 2 + 1 = 3 n’existe pas
  • i = 2; j = 0 : 01001000 ET 00000001 (4) = 00000000 (0) : le résultat est faux, donc la node (2-1)*8 + 0 + 1 = 9 n’existe pas
  • i = 2; j = 3 : 01001000 ET 00001000 (8) = 00001000 (8) : résultat est vrai, donc la node (2-1)*8 + 3 + 1 = 12 existe

La dernière node correspond au numéro 255. Il n’est donc pas possible d’avoir plus de 255 nodes associées à un même contrôleur.

 

2.4 – Déterminer le protocole des matériels

Cette trame permet d’obtenir le type d’un matériel.

Début de la trame Taille de la trame – 2 C’est une requête Commande pour obtenir le protocole Identifiant de la node Mode de transmission Checksum
01 05 00 41 xx 31 (ACK | Explore | NoRoute) xx

La réponse est de la forme:

Début de la trame Taille de la trame – 2 C’est une réponse Commande pour obtenir le protocole Inconnu Inconnu Inconnu Classe de matériel Type générique de matériel Inconnu Checksum
01 09 01 41 D3 xx xx 27 octets 04 01 xx

Les deux informations importantes sont la classe du matériel et son type générique.
Les valeurs possibles sont les suivantes.

  • Classe de matériel :
    • Inconnu = 00,
    • Contrôleur statique = 01,
    • Contrôleur classique = 02,
    • Esclave amélioré = 03,
    • Esclave classique= 04,
    • Installeur = 05,
    • Esclave de routage = 06,
    • Contrôleur par pont = 07,
    • Matériel de test = 08

Concrètement, votre stick USB sera reconnu comme un contrôleur statique. Et les nodes (un interrupteur par exemple) seront des esclaves classiques.

  • Type générique de matériel :
    • Autre  = 00,
    • Contrôleur= 02,
    • Point de contrôle AV = 03,
    • Affichage = 04,
    • Thermostat = 08,
    • Interrupteur binaire = 10,
    • Interrupteur variable = 11,
    • Interrupteur distant = 12,
    • Switch ON / OFF = 13,
    • Ventilation = 16,
    • Détecteur binaire = 20,
    • Détecteur à plusieurs niveaux = 21,
    • Compteur d’impulsions = 30,
    • Metre = 31,
    • EentyControl ? = 40,
    • Alarme = a1

Chaque élément est ainsi rangé dans un type générique.

3 – Bilan

Nous avons vu une série de trame permettant d’initialiser un système complexe :

  • Récupérer le Home ID du contrôleur
  • Récupérer la version Z-Wave supportée par le contrôleur (optionnel)
  • Récupérer la liste des matériels connus par le contrôleur
  • Pour chaque matériel, récupérer leur type

Nous avons donc un système dont nous connaissons les contrôleurs et les nodes qui lui sont liés.

Nous verrons dans un prochain article comment exécuter des actions sur les matériels et les configrer.

Z-Wave – Débuter avec le contrôleur en C#

Cet article a pour but de présenter une solution .Net écrite en C# permettant de communiquer avec un contrôleur Z-Wave.
Cet article fait directement suite à celui présentant le protocole Z-Wave.

Z-wave-speak-logo

Le code de l’application est disponible en dernière partie de l’article.
Je l’ai délibérément simplifié au maximum pour comprendre le fonctionnement des échanges. La réalisation d’une application réelle nécessiterait une meilleure architecture (découpage en couche notamment) et une gestion des exceptions.

Rappels sur le fonctionnement du protocole Z-Wave

La communication avec les modules Z-Wave se fait via l’échange de trame au format hexadécimal. Pour cela, il est nécessaire de passer pour un module se branchant sur le PC qui sera vu comme un port série par Windows.

Le format d’une trame est le suivant :

Capture

Le matériel

J’ai bien sûr un PC (Windows 10) avec Visual Studio 2015 d’installé.
Pour le Z-Wave, j’ai acquis un stick USB Z-Wave de la marque Vision Security pour de chez Vision Security pour 35 €.
C’est moins connu que les grosses marques, mais c’est 2 fois moins cher et il marche très bien.

L’installation est assez simple puisqu’il suffit de brancher le stick sur un port UBS pour voir apparaître un nouveau port COM dans le gestionnaire de périphériques.

Capture

Besoin

Nous allons écrire un simple client C# pour communiquer avec notre stick USB.
Les exigences seront les suivantes :

  • Pourvoir émettre des données au format hexadécimal vers le contrôleur via une IHM
  • Pouvoir lire les données envoyées par le contrôleur au format hexadécimal

API de communication avec le port série

Le Framework .Net fournit depuis ses débuts une API pour communiquer avec le port série. A noter qu’aucune API n’est disponible dans le Framework .Net Core pour communiquer avec le port COM. Il faut obligatoirement utiliser le Frameowk Full, je serais en version 4.6.

La documentation de la classe « System.IO.Ports.SerialPort » nous donne les informations nécessaires.

  • Constructeur
    • SerialPort(string name) : instancier un objet pour un port série nommé (« COM1 », « COM2 » …)
  •  Méthodes
    • static string[] GetPortNames() : obtenir la liste des noms des ports séries disponibles
    • void Open() : ouvre la connexion avec le port série
    • void Write(byte[] message, int start, int end) : écrit une plage d’octet d’un tableau
    • void Close() : fermer la connexion avec le port série
  • Evènement
    • DataReceived : lancé lorsque des données sont dans le buffer de lecture. Dans une solution utilisable réellement, je préfère appeler la méthode « void Read(byte[] buffer, int start, int end) de manière récursive. En effet, j’ai remarqué de l’événement n’est pas forcément levé ou met du temps. Ce n’est pas très performant.
  •   Propriétés
    • bool IsOpen : déterminer si le port est ouvert ou non
    • int BytesToRead : obtenir le nombre d’octets qui peuvent être lus

Réalisation

Nous allons créer une application Windows en XAML / C# pour communiquer simplement avec le contrôleur Z-Wave.

Partie IHM

Dans Visual Studio, nous pouvons créer une nouvelle application WPF nommée « ZWaveGUI ».
La fenêtre principale contient les éléments minimums pour répondre à notre besoin.

CaptureElle comporte :

  • Une liste qui contiendra les ports séries disponibles
  • Un champ texte permettant d’écrire notre message avec un bouton envoyer
  • Un champ texte en lecture seule détaillant les échanges

Je ne décris pas plus la création de la fenêtre qui est disponible dans le code source, ce n’est pas l’objet de cet article.

Partie code

Liste des ports séries disponibles

Nous commençons par afficher la liste des ports séries disponibles dans la liste en complétant le constructeur de la classe « MainWindow ».
J’appelle simplement la méthode statique « GetPortNames » qui renvoie la liste des noms des ports séries disponibles. Puis, je sélectionne le premier élément de cette liste.

public MainWindow()
{
   InitializeComponent();

   // Display serial port list in "cbComPorts"
   cbComPorts.ItemsSource = SerialPort.GetPortNames().ToList();
   if (cbComPorts.Items.Count > 0) cbComPorts.SelectedIndex = 0;
}
Connexion et déconnexion au port série

Nous pouvons ensuite établir la connexion avec le port série qui sera sélectionné dans la liste des ports séries disponibles.
La connexion au port série se fait en liant l’évènement « SelectionChanged » de la liste avec une méthode de connexion.

  • Elle ferme la connexion au port série si elle est ouverte
  • Elle récupère le nom du port sélectionné dans la liste générant l’évènement
  • Elle connecte le port série
  • Elle attache l’événement de réception des octets qui est décrit plus bas dans l’article

En plus, nous ajoutons une autre méthode liée à l’événement de fermeture de la fenêtre.

  • Elle ferme la connexion au port série si elle est ouverte
private SerialPort Port { get; set; }

private void cbComPorts_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
   // Close serial port connexion if necessary
   if (Port != null && Port.IsOpen)
   {
      Port.Close();
   }

   var list = (ComboBox) sender;
   if (list.SelectedValue != null)
   {
     // Get port name
     var portName = (string)list.SelectedValue;
     if (!string.IsNullOrEmpty(portName))
     {
       // Create port and open connexion
       Port = new SerialPort(portName);
       Port.DataReceived += Port_DataReceived;
       Port.Open();
     }
   }
}

private void Window_Closing(object sender, CancelEventArgs e)
{
   // Close serial port connexion if necessary
   if (Port != null && Port.IsOpen)
   {
     Port.Close();
   }
}
Envoi des messages

L’envoi des messages se fait via un champ texte limité aux caractères hexadécimaux.
Cette limite est crée en ajoutant une méthode pour l’événement « PreviewTextInput » de notre champ texte.

  • Elle valide que le texte écrit à un format hexadécimal
private void tbMessage_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
   int hexNumber;
   e.Handled = !int.TryParse(e.Text, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out hexNumber);
}

Il nous reste à lier l’événement « Click » du bouton à notre méthode d’envoi.

  • Elle vérifie que le port est bien ouvert
  • Elle transforme les données saisies en tableau d’octet (les données sont séparées par des espaces)
  • Elle ajoute l’octet du checksum (XOR entre les octets 2 à N et appliquer un NOT sur le résultat)
  • Elle écrit le tableau d’octet dans le port série
  • Elle trace l’envoi dans le champ texte des échanges
private static byte GenerateChecksum(List<byte> data)
{
   var result = data[1];
   for (var i = 2; i < data.Count; i++)
   {
     result ^= data[i];
   }
   result = (byte)(~result);
   return result;
}

private void btnSend_Click(object sender, RoutedEventArgs e)
{
   // Check that serial port is ready
   if (Port != null && Port.IsOpen)
   {
      // Convert to text to byte array
      var values = tbMessage.Text.Split(' ');
      var message = new List<byte>();
      foreach (var hexStr in values)
      {
         int hexInt;
         if (int.TryParse(hexStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out hexInt))
         {
            message.Add((byte) hexInt);
         }
      }

      // Add checksum end byte for data frames
      if (message.Count > 0 && message[0] == 0x01) message.Add(GenerateChecksum(message));

      // Write array and log
      Port.Write(message.ToArray(), 0, message.Count);
      tbExchanges.Text += "Message envoyé : ";
      message.ForEach(x => tbExchanges.Text += x.ToString("X") + " ");
      tbExchanges.Text += "\r\n";
   }
}
Réception des messages

La lecture des données reçues se fait via la méthode « Read » du port série. Le mieux est de l’appeler de manière régulière dès qu’un message est envoyé.
Pour notre exemple, je vais utiliser l’événement « DataReceived » du port série qui est levé quand des octets sont en attente de lecture. Cet événement a été attaché à la connexion du port série.

Plusieurs méthodes sont nécessaires à la réception du message. En effet, les octets lus dans le port série ne représente pas un message Z-Wave complet mais seulement une fraction. Il est donc nécessaire de compléter le message au fur et à mesure des réception.

Pour cela, nous avons 2 informations importantes :

  • Le premier octet du message est le type de message (0x01 : frame de données, 0x06 : acquittement positif et 0x015 : acquittement négatif)
  • Le second octet est la taille totale de la frame – 2

Tout d’abord, nous devons déclarer 2 propriétés.

  • Une représentant le buffer des octets lus, j’utilise un type « Queue » qui est de type First In First Out (FIFO) pour gérer l’ordre des octets
  • Une représentant le message en cours
private Queue<byte> ReadBuffer { get; } = new Queue<byte>();
private List<byte> CurrentMessage { get; set; } = new List<byte>();

Ensuite, nous pouvons écrire une méthode qui lit les octets dans le port série.

 private byte[] ReadBytes(SerialPort port)
 {
   byte[] result;
   if (port.IsOpen)
   {
      // create the buffer with the number of bytes to read
      result = new byte[port.BytesToRead];
      if (result.Length > 0)
      {
         port.Read(result, 0, result.Length);
      }
   }
   else
   {
      result = new byte[0];
   }
   return result;
}

Puis, nous pouvons écrire la méthode qui va servir à créer un message Z-Wave complet.
Cet méthode fonctionne comme suit :

  • Si le message est nouveau alors il est vide
    • Nous récupérons le type de la frame
  • Si le message est une frame de données (0x01), alors
    • Si le message comporte 1 octet, nous récupérons la taille du message
    • Ensuite, nous récupérons les octets depuis le buffer tant que la taille du message n’a pas été atteint ou que le buffer soit vidé
 private List<byte> BuildMessage(Queue<byte> readBufer, List<byte> currentMessage)
 {
     // if current message is empty : add frame type
     if (currentMessage.Count == 0)
     {
        currentMessage.Add(readBufer.Dequeue());
     }

     // If the frame is a data frame
     if (currentMessage.First() == 0x01)
     {
        // Get the frame size
        if (currentMessage.Count == 1 && readBufer.Count >= 1)
        {
           currentMessage.Add(readBufer.Dequeue());
        }

        // Compute bytes while frame size is not good
        for (var i = 0; i < readBufer.Count && currentMessage.Count != currentMessage[1] + 2; i++)
        {
           currentMessage.Add(readBufer.Dequeue());
        }
     }
     return currentMessage;
}

Enfin, pour terminer, il nous reste à écrire les méthodes gérant l’événement émis par le port série.

  • Elle lit les octets depuis le port série
  • Elle construit le message Z-Wave
  • Si le message est complet (frame data avec taille vérifiée ou acquittement), alors il est affiché dans le champ des échanges.
  • La méthode gérant l’événement de réception des octets boucle tant que le buffer de lecture n’est pas vide
private void ProcessReceivedBytes()
{
   // Build the current message
   if (ReadBuffer.Count > 0) CurrentMessage = BuildMessage(ReadBuffer, CurrentMessage);

   // Message is complete if size + 2 equals message size
   byte[] result;
   if (CurrentMessage.Count > 1 && CurrentMessage.Count == CurrentMessage[1] + 2)
   {
      result = CurrentMessage.ToArray();
   }
   // If message size is 1 and byte is 0x06 or 0x15, then message is acknowlegment.
   else if (CurrentMessage.Count == 1 && (CurrentMessage[0] == 0x06 || CurrentMessage[0] == 0x15))
   {
      result = CurrentMessage.ToArray();
   }
   else
   {
      result = new byte[0];
   }

   // if message is valid, display it
   if (result.Length != 0)
   {
     CurrentMessage = new List<byte>(); // reset current message
     Action action = delegate
     {
        if (result[0] == 0x15) tbExchanges.Text += "Acquittement négatif reçu : ";
        else if (result[0] == 0x15) tbExchanges.Text += "Actquitement posifitf reçu : ";
        else tbExchanges.Text += "Trame reçue : ";
        result.ToList().ForEach(x => tbExchanges.Text += x.ToString("X") + " ");
        tbExchanges.Text += "\r\n";
     };
     Application.Current.Dispatcher.Invoke(action);
   }
}

private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
   // Read bytes from serial port
   ReadBytes((SerialPort)sender).ToList().ForEach(x => ReadBuffer.Enqueue(x));

   // process while bytes are in buffer
   while (ReadBuffer.Count != 0)
   {
      ProcessReceivedBytes();
   }
}

Notre code est maintenant fini.
Si vous avez des problèmes à le faire fonctionner, vous pouvez télécharger le code source que j’ai utilisé.

Tests de l’application

Nous allons pouvoir envoyer notre première trame Z-Wave !

Capture

Saisissez la trame : 1 3 0 20

  • Le header 1 signifie qu’il s’agit d’une trame de données
  • La taille de la trame est 3 +2 = 5 (il n’y a que 4 octets dans la trame que je vous ai donné, mais le checksum est calculé par l’application et rajouté au message)
  • Le type de requête est 0 pour un « get »
  • La commande 20 demande l’identifiant unique (appelé « home id ») du contrôleur
  • Le checksum qui sera calculé par l’application est DC

Un premier résultat 6 est reçu. Cela signifie que le contrôleur a validé notre message.

Le second résultat est notre retour de demande de home id, pour mon contrôleur j’ai : 1 8 1 20 DC 8C 2B 32 1 9E

  • Le header 1 signifie qu’il s’agit d’une trame de données
  • La taille de la frame de données est de 8 octets
  • Le type de requête 1 est le résultat du « get »
  • La commande est bien 20
  • La valeur de l’identifiant de mon contrôleur est « DC 8C 2B 32 » car il est sur 4 octets (ce n’est pas indiqué dans la trame, il faut le savoir)
  • L’avant dernière valeur 1 est l’identifiant du module dans le réseau Z-Wave
  • La dernière valeur est le checksum

Pour terminer, vous pouvez envoyer une trame « 6 » pour indiquer au contrôleur que vous avez bien reçu une trame correcte.

Conclusion

Vous savez maintenant comment communiquer avec un contrôleur.

Dans les prochains articles, nous verrons les différentes commandes intéressantes du contrôleur, obtenir la liste des modules du réseau notamment. Puis, nous pourrons voir comment interagir avec ces modules.

 

Z-Wave – Présentation du protocole domotique

z-wave_logo

 

Z-Wave est un protocole domotique qui a le vent en poupe car il offre un panel de fonctionnalités et de matériels importants. Il s’agit d’un protocole propriétaire développé par la société Sigma Designs.

C’est le protocole que j’ai choisi d’utiliser pour développer ma propre box domotique.

Ses avantages

  • Les modules domotiques Z-Wave sont très répandus et il en existe un large panel : interrupteur, contrôle de volet, thermostat pour radiateur, compteur d’eau, de gaz …
  • Plus de 100 constructeurs proposent des produits Z-Wave
  • Le réseau peut être autonome et décentralisé sans gestion centrale
  • Les modules peuvent servir de relais*

Ses inconvénients

  • Le protocole est mono-bande et utilise les ondes radios faibles puissances, un simple brouilleur coupe toutes les connexions
  • Le protocole est fermé, la documentation est très réduite voir inexistante
  • Le matériel est assez onéreux (50 / 60 € le module)

La concurrence

  • Il existe plusieurs autres normes qui émergent dont ZigBee qui est très promoteur car ouvert et est un consortium de société, mais reste encore peu répandue et très cher.
    J’espère que Z-Wave sera faire les bons choix quand a son ouverture.

Programmation en .Net

Il n’existe pas d’API officielle écrite .Net permettant de commander les modules Z-Wave. De plus, obtenir la documentation de l’API coûte environ 3000 $. Heureusement, il y a eu plusieurs fuites qui permettent d’implémenter une API soi même.

Il suffit de disposer d’une clé USB gérant le Z-Wave et d’avoir un module domotique. Les échanges se font en série via des trames avec un système d’accusé de réception.

Capture

Le séquencement des échanges comporte systématique l’envoi d’un accusé de réception.

Capture

L’utilisation du Framework .Net Core permettra de pouvoir utiliser une API en C# sur un système Windows et Linux.

Trame échangée

Le plus compliqué est de déterminer le format des traces à échanger. En effet, il existe une quantité assez importe de commandes. De plus, chaque module comporte ses propres paramètres. Il existe un pattern général décrivant les trames échangées.

Capture

Les informations sont envoyées au format hexadécimal.

  • 1 – Header
    • 0x01 pour une frame contenant des données;
    • 0x06 pour un acquittement positif, la frame n’a alors pas d’autre valeur;
    • 0x15 pour acquittement négatif, la frame n’a alors pas d’autre valeur.
  • 2 – Taille de la sous-frame
    • Nombre d’élément valant N-2
  • 3 – Type de requête
    • 0x00 : requête à un module
    • 0x01 : réponse d’un module
  • 4 – Commande
    • Il existe énormément de commandes. Elles pourront faire l’objet d’articles particulier.
      Par exemple, la commande 0x13 décrit l’envoi d’informations vers un module.
  • 5..N-1 – Paramètres
    • Il s’agit de l’ensemble des paramètres de la commande.
  • N – Checksym
    • Le dernier élément correspond à une somme de contrôle.
      Son calcul est le suivant :

      • Faire un XOR sur tous les octets de la frame hormis le premier
      • Appliquer un NOT sur le résultat pour obtenir le checksum