Réseaux et télécommunications
Fermer ×

Arduino MKR connectée

Arduino MKR wifi et ENV

Présentation des cartes

Ces cartes font partie de la famille MKR déjà étudiée dans le chapitre Arduino.

MKR Wifi 1010

Image issue du site arduino.cc

Cette carte MKR possède en plus des interfaces Wifi et bluetooth.

MKR ENV

Image issue du site arduino.cc

Cette carte fille possède 3 capteurs : température+humidité, pression et UV accessible via le bus I2C et un capteur de luminosité connecté sur l'entrée analogique A2.

Notez que je décline toutes responsabilités quant aux conséquences que pourraient avoir l'application des méthodes et conseils suivants ainsi que l'utilisation des programmes présentés. Ceux-ci pourraient être erronés ou obsolètes.

Utilisation du Wifi

Le Wifi est géré par la librairie WININA. Les principales méthodes de cette classe sont :

Exemple de connexion Wifi

Voir le contenu du code de test de la connexion Wifi
#include <SPI.h>
#include <WiFiNINA.h>
#include "wifi_connexion.h"

int statut = WL_IDLE_STATUS ;

bool CheckWifi() {
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Problème module wifi");
    return false;
  }
  String fv = WiFi.firmwareVersion();
  if ( fv < "1.0.0" ) {
    Serial.println("Problème version wifi");
    return false;
  }
  return true;
}

void connexionWifi(char *ssid,char *passphrase) {
  statut = WL_IDLE_STATUS ;
  Serial.print("Connexion wifi ");
  statut = WiFi.begin(ssid,passphrase);
  while ( statut != WL_CONNECTED ) {
    Serial.print(".");
    statut = WiFi.status();
    delay(1000);
  }
  Serial.println("OK");
}

void printInfosConnexion() {
  byte adrmac[6];
  WiFi.macAddress(adrmac);
  Serial.print("MAC : ");
  for(int i=0;i<5;i+=1) {
    Serial.print(adrmac[i],HEX);
    Serial.print(":");
  }
  Serial.println(adrmac[5],HEX);
  IPAddress ip = WiFi.localIP(); 
  Serial.print("IP : "); 
  Serial.println(ip);
  IPAddress mask = WiFi.subnetMask();
  Serial.print("MASK : "); 
  Serial.println(mask); 
  IPAddress passerelle = WiFi.gatewayIP();
  Serial.print("Passerelle : "); 
  Serial.println(passerelle);  
}

void setup() {
  Serial.begin(115200);
  while (!Serial) ;
  if (!CheckWifi()) {
    Serial.println("Problème connexion wifi");
    while (true);  
  }
  Serial.println("Wifi disponible");
  connexionWifi(BORNE_SSID,BORNE_PASSPHRASE);  
  printInfosConnexion();
}

void loop() {}
					

Le fichier wifi_connexion.h contient les valeurs de BORNE_SSID et BORNE_PASSPHRASE.

Le code de test est composé de deux fonctions qui seront utilisées dans tous les programmes qui utilisent le Wifi.

  • CheckWifi() qui retourne vrai si le module Wifi est présent et si la version du firmware est compatible avec la librairie.
  • connexionWifi(ssid,motpasse) qui effectue la connexion Wifi avec le ssid et le motpasse transmis. Cette fonction boucle tant que la connexion n'est pas effective.

Après, si la connexion est réussie, le programme affiche l'adresse MAC de l'interface WIfi, L'adresse IP obtenue par DHCP, le masque de sous-réseau ainsi que l'adresse IP de la passerelle du réseau.

La ligne de code while (!Serial) ; attend la connexion du terminal série. Cela permet d'afficher les messages contenus dans la fonction setup(). Pour une utilisation sans moniteur, il faut commenter cette ligne, sinon le programme ne démarre jamais. Cela sera présent dans tous les codes.

La boucle loop() ne fait rien, c'est seulement un programme de test de la connexion Wifi.

Utilisation de la carte fille

La librairie MKRENV permet de gérer l'ensemble des capteurs de cette carte. Les principales méthodes de cette classe sont :

Exemple d'utilisation de la carte

Voir le contenu du code de test de la carte ENV avec la librairie Arduino_MKRENV
#include <Arduino_MKRENV.h>

typedef struct s_capteurs {
  float temperature;
  float humidite;
  float pression;
  float luminosite;
  unsigned int indiceuv;
} T_capteurs;

typedef T_capteurs *ptr_capteurs ;

T_capteurs capteurs;

void litcapteurs(ptr_capteurs pcapteurs) {
  pcapteurs->temperature = ENV.readTemperature();
  pcapteurs->humidite    = ENV.readHumidity();
  pcapteurs->pression    = ENV.readPressure(MILLIBAR);
  pcapteurs->luminosite = ENV.readIlluminance();
  pcapteurs->indiceuv     = ENV.readUVIndex();  
}

void printCapteurs(T_capteurs capteurs) {
  Serial.print("Temperature = ");
  Serial.print(capteurs.temperature,1);
  Serial.print(" °C");
  Serial.print(" , Humidite = ");
  Serial.print(capteurs.humidite,0);
  Serial.print(" %");
  Serial.print(" , Pression = ");
  Serial.print(capteurs.pression,0);
  Serial.print(" hPa");
  Serial.print(" , luminosite = ");
  Serial.print(capteurs.luminosite,0);
  Serial.print(" lx");
  Serial.print(" , Indice UV = ");
  Serial.println(capteurs.indiceuv);  
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  if (!ENV.begin()) {
    Serial.println("Erreur initialisation capteurs!");
    while (true);
  }  
}

void loop() {
  litcapteurs(&capteurs);
  printCapteurs(capteurs);
  delay(1000);
}
					

Afin de regrouper tous les capteurs on utilise une structure de type T_capteurs et afin de simplifier l'écriture on définit également un type pointeur vers cette structure ptr_capteurs.

La valeur de la température est en degré Celcius, l'humidité en %RH, la pression en hPa, la luminosité en lux.

Précision des valeurs des capteurs

Résultats obtenus

Comme cela a déjà été écrit dans les exemples I2C et SPI des systèmes embarqués, il faut bien évidement vérifier les valeurs obtenues. Une première évaluation rapide montre que la température est supérieure d'environ 2 degrés à la valeur réelle, l'humidité 10%RH en dessous, la pression 10hPa en dessous, la luminosité environ 50 lux en dessous de la valeur mesurée (≈ 500 lux) avec un smartphone. L'indice UV n'ayant pas pu être vérifié.

Capteur de Luminosité

Le capteur de luminosité est un circuit TEMT6000X01. La documentation nous donne les principales caractéristiques de ce composant, notamment le courant en fonction de la luminosité en lux. De cette courbe, on peut déduire une fonction de transfert approximative qui est L=2×I avec I en µA et L en Lux. Ensuite d'après le schéma de la carte MKR ENV, on déduit la tension à l'entrée analogique du microcontrôleur qui est V=10×I avec R en et I en µA. Le capteur est un phototransistor, qui, comme le transistor possède une tension de saturation (non documentée), ceci implique que l'on aura jamais la tension maximale de l'alimentation 3.3v. Le convertisseur analogique-numérique fonctionne sur 10 bits avec une tension de référence de 3.3V ce qui donne la fonction de transfert :

V=Vcc×NNmax
La fonction de transfert totale est donc :
L=2×Vcc×NR×Nmax
La valeur approchée donne : L ≈ 0.645×N
Afin de connaître un ordre de grandeur de l'erreur sur la valeur obtenue, on peut travailler en mode différentiel en prenant en compte la précision de la résistance, la précision de la tension d'alimentation ainsi que l'erreur sur la tension convertie, ce qui donne :
d L = | L V c c | d V c c + | L N | d N + | L R | d R = | 2 N R N m a x | d V c c + | 2 V c c R N m a x | d N + | - 2 V c c N R 2 N m a x | d R = 2 N R N m a x d V c c + 2 V c c R N m a x d N + 2 V c c N R 2 N m a x d R
Avec une valeur dR=1 kΩ (résistance à 10%), dN=1 bit, dVcc=3.3 mV (variation de la tension d'alimentation), Nmax=1024 (10 bits), R=10kΩ , on obtient dL ≈ 0.06445×N pour une valeur de L ≈ 0.645×N, soit une erreur d'environ 10%. On peut déduire que seule la résistance influe sur la précision.

Capteur de pression

Le circuit utilisé est un LPS22HB. La notice d'application AN4672 décrit l'implémentation de ce composant sur un circuit imprimé. La notice d'application AN4833 explique comment compenser le problème de décalage du à l'implémentation en modifiant le contenu d'un registre (RPDS). Il convient de vérifier que ce décalage est constant dans toute la plage de mesures.

Capteur de température et humidité

Le circuit utilisé est un HTS221. Comme pour le précédent la notice d'application AN4722 explique comment implémenter le composant sur un circuit imprimé.

Mais cette fois-ci il n'existe pas de registre pour corriger le décalage. Les registres de calcul de la température et de l'humidité sont intégrés dans le composant et accessibles en lecture seule. On propose de suivre cette discussion pour comprendre la difficulté de mise en oeuvre de ce composant. En conclusion, il faut recalculer la fonction de conversion et ne plus utiliser la fonction de conversion interne au capteur.

Conclusion

Avant d'utiliser les capteurs, il convient de vérifier les valeurs retournées et ainsi vérifier que les erreurs sont bien inférieures à celles imposées par le cahier des charges. Sinon il faut effectuer des mesures avec du matériel adapté afin de modifier ou ajouter les fonctions de transfert. Cela implique donc de modifier les codes sources des librairies ou bien de réécrire les librairies.

Utilisation d'un serveur web

Librairies utilisées

Pour effectuer le transfert des données au serveur web, on utilise une librairie ArduinoHttpClient qui, elle-même, utilise la librairie WiFiClient. Les principales méthodes utilisées sont :

Exemple de transmission des données

On transmet les données au serveur web de la raspberry pi qui utilise le script CGI sauvecapteurs.cgi

Voir le contenu du code de d'envoi des données de la carte MKR ENV au serveur web
#include <Arduino_MKRENV.h>
#include <WiFiNINA.h>
#include <ArduinoHttpClient.h>
#include "wifi_connexion.h"

#define PGMCGI "/cgi-bin/savecapteurs.cgi"

typedef struct s_capteurs {
  float temperature;
  float humidite;
  float pression;
  float luminosite;
  float indiceuv;
} T_capteurs;

typedef T_capteurs *ptr_capteurs ;

T_capteurs capteurs;

void litcapteurs(ptr_capteurs pcapteurs) {
  pcapteurs->temperature = ENV.readTemperature();
  pcapteurs->humidite    = ENV.readHumidity();
  pcapteurs->pression    = ENV.readPressure(MILLIBAR);
  pcapteurs->luminosite = ENV.readIlluminance();
  pcapteurs->indiceuv     = ENV.readUVIndex();  
}

void printCapteurs(T_capteurs capteurs) {
  Serial.print("Temperature = ");
  Serial.print(capteurs.temperature);
  Serial.println(" °C");
  Serial.print("Humidite    = ");
  Serial.print(capteurs.humidite);
  Serial.println(" %");
  Serial.print("Pressure    = ");
  Serial.print(capteurs.pression);
  Serial.println(" hPa");
  Serial.print("Illuminance = ");
  Serial.print(capteurs.luminosite);
  Serial.println(" lx");
  Serial.print("UV Index    = ");
  Serial.println(capteurs.indiceuv);  
}

int statut = WL_IDLE_STATUS;
WiFiClient client;
HttpClient http(client,HOSTNAME,80);

bool CheckWifi() {
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Problème module wifi");
    return false;
  }
  String fv = WiFi . firmwareVersion ();
  if ( fv < "1.0.0" ) {
    Serial.println("Problème version wifi");
    return false;
  }
  return true;
}

void connexionWifi(char *ssid,char *passphrase) {
  statut = WL_IDLE_STATUS ;
  Serial.print("connexion wifi ");
  statut = WiFi.begin(ssid,passphrase);
  while ( statut != WL_CONNECTED ) {
    Serial.print(".");
    statut = WiFi.status();
    delay(1000);
  }
  Serial.println("");
}

int ConnexionHTTPGET(String parametres) {
  int err = 0;
  String sReponse ;
  int coderetour;
  err = http.get(parametres.c_str());
  if (err == 0) {
    int statutcode = http.responseStatusCode();
    if (statutcode == 200) {
      err = http.skipResponseHeaders();
      if (err >= 0) {
        sReponse = "";
        while (http.connected() || http.available() ) {
            if (http.available()) {
              sReponse += http.read();
            }
        }
        if (sReponse.startsWith("code=")) {
          String scode = sReponse.substring(5);
          coderetour = scode.toInt();
        }
     }       
     else {
        Serial.println("erreur skip reponseheader");
        coderetour = err ;
     }
    } 
    else {
      Serial.println("erreur code retour ");
      coderetour = statutcode ; 
    } 
  } 
  else {
    Serial.println("erreur get");
    coderetour = err ;
  } 
  http.stop();
  return coderetour ;
}

int ConnexionHTTPPOST(const char *url,String parametres) {
  int err = 0;
  String sReponse ;
  int coderetour;
  String contentType = "application/x-www-form-urlencoded";
  err = http.post(url,contentType,parametres.c_str());
  if (err == 0) {
    int statutcode = http.responseStatusCode();
    if (statutcode == 200) {
      err = http.skipResponseHeaders();
      if (err >= 0) {
        sReponse = "";
        while (http.connected() || http.available() ) {
            if (http.available()) {
              sReponse += http.read();
            }
        }
        if (sReponse.startsWith("code=")) {
          String scode = sReponse.substring(5);
          coderetour = scode.toInt();
        }
     }       
     else {
        Serial.println("erreur skip reponseheader");
        coderetour = err ;
     }
    } 
    else {
      Serial.println("erreur code retour ");
      coderetour = statutcode ; 
    } 
  } 
  else {
    Serial.println("erreur get");
    coderetour = err ;
  } 
  http.stop();
  return coderetour ;
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  if (!ENV.begin()) {
    Serial.println("Problème avec la carte MKRenv");
    while (true);
  } 
  if (!CheckWifi()) {
    Serial.println("Problème connexion wifi");
    while (true);  
  }
  Serial.println("Wifi Ok");
  connexionWifi(BORNE_SSID,BORNE_PASSPHRASE);
  Serial.println("Wifi Connecte");
}

String chainevaleurs;
String requete ;

void loop() {
  litcapteurs(&capteurs);
  printCapteurs(capteurs);
  chainevaleurs = "temperature=" + String(capteurs.temperature);
  chainevaleurs += "&humidite=" + String(capteurs.humidite);
  chainevaleurs += "&pression=" + String(capteurs.pression); 
  chainevaleurs += "&luminosite=" + String(capteurs.luminosite);
  chainevaleurs +=  "&uv=" + String(capteurs.indiceuv) ;
  requete = String(PGMCGI) + "?" + chainevaleurs ;
  int coderetour = ConnexionHTTPGET(requete);
  if (coderetour == 0) {
    Serial.println("Transmission OK");
  }
  else {
    Serial.print("Transmission ERREUR ");
    Serial.print(coderetour);
  }
  delay(10000);
}
					

Le fichier wifi_connexion.h contient les valeurs de BORNE_SSID, BORNE_PASSPHRASE et HOSTNAME.

Ce code définit les deux fonctions d'envoi des données au serveur :

  • ConnexionHTTPGET() qui effectue la connexion HTTP, envoie les données avec la méthode GET, vérifie l'état de la transmission puis ferme la connexion HTTP.
    Cette fonction établit la connexion, teste la valeur de retour, si c'est OK, lit la réponse du serveur octet par octet pour construire la chaîne réponse retournée par le script CGI qui est de la forme code=coderetour si tout s'est bien passé. Elle extrait la valeur coderetour de la chaîne afin de le retourner.
  • ConnexionHTTPPOST() qui effectue la connexion HTTP, envoie les données avec la méthode POST, vérifie l'état de la transmission puis ferme la connexion HTTP. L'algorithme reste identique à celui de la méthode ConnexionHTTPGET().

La fonction loop construit la chaîne des paramètres à partir des valeurs des capteurs, puis appel la fonction ConnexionHTTPGET pour transmettre des données au serveur. Il est tout à fait possible de modifier ce programme pour utiliser la méthode POST.

Utilisation de Domoticz

Librairie HTTP

On utilise toujours la librairie HTTP, la différence réside dans le contenu de l'URL envoyée, à raison d'une connexion HTTP par capteur.

La connexion avec Domoticz nécessite une authentification avec utilisateur et mot de passse.

Librairie JSON

Le serveur domoticz retourne une réponse au format JSON, ce traitement nécessite la librairie ArduinoJson, les principales fonctions sont :

Exemple de transmission des données

On transmet les données au serveur domoticz de la raspberry pi

Voir le contenu du code de d'envoi des données de la carte MKR ENV au serveur domoticz
#include <Arduino_MKRENV.h>
#include <WiFiNINA.h>
#include <ArduinoHttpClient.h>
#include <ArduinoJson.h>
#include "wifi_connexion.h"

#define DOMOTICZ_URL "/json.htm?type=command¶m=udevice&idx"
#define DOMOTICZ_UTILISATEUR_64 "dXNlcgo="
#define DOMOTICZ_MOTPASSE_64 "YWNjZXNzCg=="

#define IDXTEMPERATURE 1
#define IDXHUMIDITE 2
#define IDXPRESSION 3
#define IDXLUMINOSITE 4
#define IDXUV 5

typedef struct s_capteurs {
  float temperature;
  float humidite;
  float pression;
  float luminosite;
  float indiceuv;
} T_capteurs;

typedef T_capteurs *ptr_capteurs ;

T_capteurs capteurs;
JsonDocument JsReponse;


void litcapteurs(ptr_capteurs pcapteurs) {
  pcapteurs->temperature = ENV.readTemperature();
  pcapteurs->humidite    = ENV.readHumidity();
  pcapteurs->pression    = ENV.readPressure(MILLIBAR);
  pcapteurs->luminosite = ENV.readIlluminance();
  pcapteurs->indiceuv     = ENV.readUVIndex();  
}

void printCapteurs(T_capteurs capteurs) {
  Serial.print("Temperature = ");
  Serial.print(capteurs.temperature);
  Serial.println(" °C");
  Serial.print("Humidite    = ");
  Serial.print(capteurs.humidite);
  Serial.println(" %");
  Serial.print("Pressure    = ");
  Serial.print(capteurs.pression);
  Serial.println(" hPa");
  Serial.print("Illuminance = ");
  Serial.print(capteurs.luminosite);
  Serial.println(" lx");
  Serial.print("UV Index    = ");
  Serial.println(capteurs.indiceuv);  
}

#define HUMIDITE_NORMAL 0
#define HUMIDITE_CONFORT 1
#define HUMIDITE_SEC 2
#define HUMIDITE_HUMIDE 3

int statutHumidite(float humidite) {
  int statut=0;
  if (humidite < 20) {
    statut = HUMIDITE_SEC;
  }
  else if (humidite < 50) {
    statut = HUMIDITE_NORMAL;
  }
  else if (humidite < 70) {
    statut = HUMIDITE_CONFORT;
  }
  else {
    statut = HUMIDITE_HUMIDE;
  }
  return statut;
}

#define PRESSION_STABLE 0
#define PRESSION_ENSOLEILLE 1
#define PRESSION_NUAGEUX 2
#define PRESSION_INSTABLE 3
#define PRESSION_ORAGEUX 4
#define PRESSION_INCONNU 5
#define PRESSION_PLUIE 6


int statutPression(float pression) {
  int statut=0;
  if (pression < 980) {
    statut =  PRESSION_INSTABLE;
  }
  if (pression < 1000) {
    statut = PRESSION_PLUIE;
  }
  else if (pression < 1010) {
    statut = PRESSION_NUAGEUX;
  }
  else if (pression < 1020) {
    statut = PRESSION_STABLE;
  }
  else if (pression < 1040) {
    statut = PRESSION_ENSOLEILLE;
  }
  else {
    statut = PRESSION_ORAGEUX;
  }
  return statut;  
}

int statut = WL_IDLE_STATUS;
WiFiClient client;
HttpClient http(client,HOSTNAME,DOMOTICZ_PORT);

bool CheckWifi() {
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Problème module wifi");
    return false;
  }
  String fv = WiFi . firmwareVersion ();
  if ( fv < "1.0.0" ) {
    Serial.println("Problème version wifi");
    return false;
  }
  return true;
}

void connexionWifi(char *ssid,char *passphrase) {
  statut = WL_IDLE_STATUS ;
  Serial.print("connexion wifi ");
  statut = WiFi.begin(ssid,passphrase);
  while ( statut != WL_CONNECTED ) {
    Serial.print(".");
    statut = WiFi.status();
    delay(1000);
  }
  Serial.println("");
}

int ConnexionHTTPGET(String parametres) {
  int err = 0;
  String sReponse ;
  int coderetour;
  int statutcode;
  http.beginRequest();
  http.get("/");
  http.sendBasicAuth(DOMOTICZ_UTILISATEUR_64, DOMOTICZ_MOTPASSE_64); 
  http.get(parametres.c_str());
  http.endRequest();
  statutcode = http.responseStatusCode();
  if (statutcode == 200) {
      err = http.skipResponseHeaders();
      if (err >= 0) {
        sReponse = "";
        while (http.connected() || http.available() ) {
         if (http.available()) {
            sReponse += (char)http.read();
         }
        }
        deserializeJson(JsReponse, sReponse.c_str());
        const char *statut = JsReponse["status"];
        Serial.print("statut : ");
        Serial.println(statut);
        coderetour = 0 ;
      }
      else {
        Serial.println("erreur skip reponseheader");
        coderetour = err ; 
      }
  } 
  else {
      int taille = http.contentLength();
      Serial.print(taille);
      Serial.println(" erreur code retour ");
      coderetour = statutcode ; 
  } 
  http.stop();
  return coderetour ;
}


void setup() {
  Serial.begin(115200);
  while (!Serial);
  if (!ENV.begin()) {
    Serial.println("Problème avec la carte MKRenv");
    while (true);
  } 
  if (!CheckWifi()) {
    Serial.println("Problème connexion wifi");
    while (true);  
  }
  Serial.println("Wifi Ok");
  connexionWifi(BORNE_SSID,BORNE_PASSPHRASE);
  Serial.println("Wifi Connecte");
}

String requete ;

void loop() {
  litcapteurs(&capteurs);
  printCapteurs(capteurs);
  int statuthumidite = statutHumidite(capteurs.humidite);
  int statutpression = statutPression(capteurs.pression);
  requete = String(DOMOTICZ_URL) + "=" + String(IDXTEMPERATURE) + "&nvalue=0&svalue=" + String(capteurs.temperature) ;
  ConnexionHTTPGET(requete);
  requete = String(DOMOTICZ_URL) + "=" + String(IDXHUMIDITE) + "&nvalue=" + String(capteurs.humidite) +  "&svalue=" + String(statuthumidite) ;
  ConnexionHTTPGET(requete);
  requete = String(DOMOTICZ_URL) + "=" + String(IDXPRESSION) + "&nvalue=0&svalue=" + String(capteurs.pression) + ";" + String(statutpression) ;
  ConnexionHTTPGET(requete); 
  requete = String(DOMOTICZ_URL) + "=" + String(IDXLUMINOSITE) + "&nvalue=0&svalue=" + String(capteurs.luminosite) + ";0" ;
  ConnexionHTTPGET(requete);
  requete = String(DOMOTICZ_URL) + "=" + String(IDXUV) + "&nvalue=0&svalue=" + String(capteurs.indiceuv) + ";0" ;
  ConnexionHTTPGET(requete);
  delay(1000);
}
					

Le codage du login et du mot de passe en base 64 a été effectué sur l'ordinateur de développement avec la commande base64 (linux)

Par rapport à l'exemple précédent, on a ajouté deux fonctions de calculs pour du statut l'humidité et la prévision barométrique avec la pression

  • statutHumidite(humidite) qui détermine le statut entre 0 et 3, le choix des seuils est totalement arbitraire et ne correspond à aucune réalité.
  • statutPression(pression) qui détermine la prévision entre 0 et 6, comme précédemment, le choix des seuils est totalement arbitraire et ne correspond à aucune réalité.

La fonction ConnexionHTTPGET a été modifiée pour effectuer une connexion authentifiée et traiter une réponse de type JSON.

La fonction loop , après avoir lu les valeurs des capteurs, envoie successivement les valeurs au serveur domoticz à raison d'une connexion par capteur.

Utilisation de MQTT et Domoticz

Librairie MQTT

La librairie MQTT utilisée est PubSubClient, les principales fonctions sont :

La syntaxe des données mqtt de domoticz est au format JSON :

{ "idx" : valeur , "nvalue" : valeur , "svalue" : "valeur" }

Gestion des capteurs

Voir le contenu du code de d'envoi des données de la carte MKR ENV au serveur MQTT
#include <Arduino_MKRENV.h>
#include <WiFiNINA.h>
#include <PubSubClient.h>
#include "wifi_connexion.h"

#define TOPIC "domoticz/in"

#define IDXTEMPERATURE 1
#define IDXHUMIDITE 2
#define IDXPRESSION 3
#define IDXLUMINOSITE 4
#define IDXUV 5

typedef struct s_capteurs {
  float temperature;
  float humidite;
  float pression;
  float luminosite;
  float indiceuv;
} T_capteurs;

typedef T_capteurs *ptr_capteurs ;

T_capteurs capteurs;

void litcapteurs(ptr_capteurs pcapteurs) {
  pcapteurs->temperature = ENV.readTemperature();
  pcapteurs->humidite    = ENV.readHumidity();
  pcapteurs->pression    = ENV.readPressure(MILLIBAR);
  pcapteurs->luminosite = ENV.readIlluminance();
  pcapteurs->indiceuv     = ENV.readUVIndex();  
}

void printCapteurs(T_capteurs capteurs) {
  Serial.print("Temperature = ");
  Serial.print(capteurs.temperature);
  Serial.println(" °C");
  Serial.print("Humidite    = ");
  Serial.print(capteurs.humidite);
  Serial.println(" %");
  Serial.print("Pressure    = ");
  Serial.print(capteurs.pression);
  Serial.println(" hPa");
  Serial.print("Illuminance = ");
  Serial.print(capteurs.luminosite);
  Serial.println(" lx");
  Serial.print("UV Index    = ");
  Serial.println(capteurs.indiceuv);  
}

#define HUMIDITE_NORMAL 0
#define HUMIDITE_CONFORT 1
#define HUMIDITE_SEC 2
#define HUMIDITE_HUMIDE 3

int statutHumidite(float humidite) {
  int statut=0;
  if (humidite < 20) {
    statut = HUMIDITE_SEC;
  }
  else if (humidite < 50) {
    statut = HUMIDITE_NORMAL;
  }
  else if (humidite < 70) {
    statut = HUMIDITE_CONFORT;
  }
  else {
    statut = HUMIDITE_HUMIDE;
  }
  return statut;
}

#define PRESSION_STABLE 0
#define PRESSION_ENSOLEILLE 1
#define PRESSION_NUAGEUX 2
#define PRESSION_INSTABLE 3
#define PRESSION_ORAGEUX 4
#define PRESSION_INCONNU 5
#define PRESSION_PLUIE 6


int statutPression(float pression) {
  int statut=0;
  if (pression < 980) {
    statut =  PRESSION_INSTABLE;
  }
  if (pression < 1000) {
    statut = PRESSION_PLUIE;
  }
  else if (pression < 1010) {
    statut = PRESSION_NUAGEUX;
  }
  else if (pression < 1020) {
    statut = PRESSION_STABLE;
  }
  else if (pression < 1040) {
    statut = PRESSION_ENSOLEILLE;
  }
  else {
    statut = PRESSION_ORAGEUX;
  }
  return statut;  
}

int statut = WL_IDLE_STATUS;
WiFiClient client;
PubSubClient mqtt(client);

bool CheckWifi() {
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Problème module wifi");
    return false;
  }
  String fv = WiFi . firmwareVersion ();
  if ( fv < "1.0.0" ) {
    Serial.println("Problème version wifi");
    return false;
  }
  return true;
}

void connexionWifi(char *ssid,char *passphrase) {
  statut = WL_IDLE_STATUS ;
  Serial.print("Connexion wifi ");
  statut = WiFi.begin(ssid,passphrase);
  while ( statut != WL_CONNECTED ) {
    Serial.print(".");
    statut = WiFi.status();
    delay(1000);
  }
  Serial.println("OK");
}

void reconnect() {
  while (!mqtt.connected()) {
    Serial.print("MQTT connexion... ");
    if (mqtt.connect(HOSTNAME)) {
      Serial.println("OK");
    } 
    else {
      Serial.print("ERREUR, statut=");
      Serial.println(mqtt.state());
      delay(5000);
    }
  }
}

void publiecapteur(const char *chainevaleurs) {
  boolean ok=mqtt.publish(TOPIC,chainevaleurs);
  if (ok) {
    Serial.println("publish OK");
  }
  else {
    Serial.println("publish ERREUR");
  } 
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  if (!ENV.begin()) {
    Serial.println("Problème avec la carte MKRenv");
    while (true);
  } 
  if (!CheckWifi()) {
    Serial.println("Problème connexion wifi");
    while (true);  
  }
  Serial.println("Wifi disponible");
  connexionWifi(BORNE_SSID,BORNE_PASSPHRASE);
  mqtt.setServer(HOSTNAME,MQTT_PORT);
}

String chainevaleurs;

void loop() {
  litcapteurs(&capteurs);
  printCapteurs(capteurs);
  int statuthumidite = statutHumidite(capteurs.humidite);
  int statutpression = statutPression(capteurs.pression);
  if (!mqtt.connected()) {
    reconnect();
  }
  chainevaleurs = "{ \"idx\" : " + String(IDXTEMPERATURE) + " , \"nvalue\" : 0 , \"svalue\" : \"" + String(capteurs.temperature)  + "\" }";
  publiecapteur(chainevaleurs.c_str());
  chainevaleurs = "{ \"idx\" : " + String(IDXHUMIDITE) + " , \"nvalue\" : " + String((int)capteurs.humidite) + " , \"svalue\" : \"" + String(statuthumidite)  + "\" }";
  publiecapteur(chainevaleurs.c_str());
  chainevaleurs = "{ \"idx\" : " + String(IDXPRESSION) + " , \"nvalue\" : 0 , \"svalue\" : \"" + String((int)capteurs.pression)  + ";" + String(statutpression) + "  \" }";
  publiecapteur(chainevaleurs.c_str());
  chainevaleurs = "{ \"idx\" : " + String(IDXLUMINOSITE) + " , \"nvalue\" : 0 , \"svalue\" : \"" + String((int)capteurs.luminosite)  + ";0\" }";
  publiecapteur(chainevaleurs.c_str());
  chainevaleurs = "{ \"idx\" : " + String(IDXUV) + " , \"nvalue\" : 0 , \"svalue\" : \"" + String(capteurs.indiceuv)  + ";0\" }";
  publiecapteur(chainevaleurs.c_str());
  delay(2000);
}
					

La fonction reconnect reconnecte le client au serveur en cas de déconnexion, la connexion est surveillée dans la boucle loop.

La fonction publiecapteur publie les valeurs d'un capteur en respectant la syntaxe JSON de Domoticz.

Gestion de la LED RGB de la carte MKR Wifi

La led RGB est connectée à la puce Wifi, il faut utiliser librairie Wifi pour commander cette LED.

Exemple de gestion de LED RGB avec un selecteur domoticz

Voir le contenu du code de l'abonnement au serveur MQTT
#include <WiFiNINA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "wifi_connexion.h"

#define TOPIC "domoticz/out/6"

# define RGB_GREEN 25
# define RGB_RED 26
# define RGB_BLUE 27

const uint8_t portRGB[3] = { RGB_RED , RGB_GREEN , RGB_BLUE };

int statut = WL_IDLE_STATUS;
WiFiClient client;
PubSubClient mqtt(client);
JsonDocument JsReponse;

bool CheckWifi() {
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Problème module wifi");
    return false;
  }
  String fv = WiFi . firmwareVersion ();
  if ( fv < "1.0.0" ) {
    Serial.println("Problème version wifi");
    return false;
  }
  return true;
}

void connexionWifi(char *ssid,char *passphrase) {
  statut = WL_IDLE_STATUS ;
  Serial.print("Connexion wifi ");
  statut = WiFi.begin(ssid,passphrase);
  while ( statut != WL_CONNECTED ) {
    Serial.print(".");
    statut = WiFi.status();
    delay(1000);
  }
  Serial.println("OK");
}

uint32_t jsontorvb(String sjson) {
  uint32_t rvb=0;
  deserializeJson(JsReponse, sjson.c_str());
  const char *sniveau = JsReponse["svalue1"];
  int niveau = strtoul(sniveau,NULL,10);
  Serial.println(niveau);
  switch (niveau) {
    case 0 :
            rvb = 0;
            break;
    case 10 :
              rvb = 0xFF0000;
              break;
    case 20 :
              rvb = 0x00FF00;
              break;
    case 30 :
              rvb = 0x0000FF;
              break;
    case 40 :
              rvb = 0xFFFFFF;
              break;
  }
  return rvb;
}

void setLEDRGB(uint32_t rgb) {
  WiFiDrv::analogWrite(RGB_RED,(rgb >> 16) & 0xff);
  WiFiDrv::analogWrite(RGB_GREEN, (rgb >> 8) & 0xff);
  WiFiDrv::analogWrite(RGB_BLUE,rgb & 0xff);
}

void callback(char* topic, byte* payload, unsigned int longueur) {
  Serial.print("Message recu [");
  Serial.print(topic);
  Serial.print("] : ");
  String sjson= "";
  for (int i=0,j=0;i<longueur;i++) {
    sjson += (char)payload[i];
  }
  uint32_t rvb = jsontorvb(sjson);
  Serial.print(sjson);
  setLEDRGB(rvb);
  Serial.println();
}

void reconnect() {
  while (!mqtt.connected()) {
    Serial.print("MQTT connexion... ");
    if (mqtt.connect(HOSTNAME)) {
      Serial.println("OK");
    } 
    else {
      Serial.print("ERREUR, statut=");
      Serial.println(mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  if (!CheckWifi()) {
    Serial.println("Problème connexion wifi");
    while (true);  
  }
  Serial.println("Wifi disponible");
  connexionWifi(BORNE_SSID,BORNE_PASSPHRASE);
  mqtt.setServer(HOSTNAME,MQTT_PORT);
  mqtt.setCallback(callback);
  mqtt.setBufferSize (512);
  setLEDRGB(0x00101010);
}

String chainevaleurs;

void loop() {
  if (!mqtt.connected()) {
    reconnect();
    mqtt.subscribe(TOPIC);
  }
  mqtt.loop();
}
					

le TOPIC correspond à l'interrupteur utilisé sous Domoticz.

Le message JSON est au format précisé sur le chapitre précédent, la valeur qui nous intéresse pour piloter la led RGB est celle qui correspond à la clé "svalue1".

La fonction callback stocke le message JSON reçu et le transmet à la fonction jsontorvb pour convertir la valeur reçue en RVB, valeur qui sera transmise à la fonction affichage de la LED setLEDRGB(rvb)

La fonction jsontorvb extrait du message la valeur de la clé "svalue1", puis code la valeur rvb en fonction du code reçu

  • 0 : off
  • 10 : rouge
  • 20 : vert
  • 30 : bleu
  • 40 : blanc (les trois couleurs)