Utiliser MQTT pour la domotique à la maison

Cela fait plusieurs fois que je retombe sur MQTT et me dis que ca serait bien de jouer avec pour la domotique à la maison. Et comme on utilise un service de message au boulot, je me suis laissé tenter ! Cela va permetttre de distribuer l’infrastructure et de ne pas faire reposer tout le poids de la donnée à mon serveur Domoticz.

Qu’est ce que MQTT ?

MQTT (Message Queuing Telemetry Transport) est un service de messagerie TCP/IP où chacun pourra soit publier des messages, soit souscrire à un message, message de n’importe quelle nature que ce soit. Un broker reçoit et émet les messages. On voit facilement que nos sondes ESP8266 vont envoyer des données au broker MQTT et que Domoticz (entre autre) sera consommateur des données.

https://www.myelectronicslab.com/iot/esp8266-mqtt-iot-device-control-rete-iot-dashboard-tutorial/

Le service est organisé en topic. Par exemple, si je souscris à /domotique/temperature/salon, je recevrais les messages de ce topic à intervalles réguliers. Ma sonde sera elle chargée d’écrire à /domotique/temperature/salon.

On peut évidement choisir qui souscrit au topic, qui émet, et acéder à un sous ensemble de topic via le symbole #. Tout est détaillé sur le site de MQTT.

Il existe un QoS (qualité de service) pour les messages émis :

  • niveau 0 (At most once) : émis une seule fois. Il n’est pas stocké et nous n’avons pas de garantie de bonne réception par le broker.
  • niveau 1 (At least once) : livré au moins une fois, le message est ré-émis en cas d’échec.
  • niveau 2 (exactly once) : sauvegardé par l’emetteur et transmis tant que le recepteur ne valide pas la réception.

Installation du broker

Je vais utiliser un Raspberry pi zero W pour les tests, je prendrais surement un Raspberry pi 3+ pour la production. Sous Linux, le serveur de prédilection est Mosquitto. Nous allons donc l’installer.

On va d’abord mettre à jour son système d’exploitation

sudo apt-get update
sudo apt-get dist-upgrade

Puis lançons l’installation du broker mosquitto.

sudo apt-get install mosquitto mosquitto-clients

Sur un deuxième raspberry, installer le client mosquitto

apt-get install mosquitto-clients

Sur le premier raspberry, lancer le serveur

pi@raspberrypi:~ $ sudo /etc/init.d/mosquitto start
[ ok ] Starting mosquitto (via systemctl): mosquitto.service.

Puis on va se mettre en mode écoute sur le topic domotique

sudo mosquitto_sub -d -t domotique/#

Sur le deuxième raspberry, on va lancer un message sur le topic domotique

sudo mosquitto_pub -h 192.168.1.30 -d -t domotique -m "Hello from RPI2"

Et sur le premier on reçoit

Client mosqsub/32396-raspberry sending PINGREQ
Client mosqsub/32396-raspberry received PINGRESP
Client mosqsub/32396-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique', ... (15 bytes))
Hello from RPI2

MQTT sur NodeMCU

J’ai testé pas mal de librairies dans le gestionnaire de librairie d’Arduino mais aucune ne fonctionnait correctement. J’ai donc fait pas mal de tests et je suis tombé sur cette librairie : pubsubclient.

Je vous joins la librairie au cas où le site disparait.

J’ai donc mixé le code vu précédemment sur NodeMCU et BME280 et celui de pubsubclient envoyer les données via MQTT sur le topic domotique.

On commence par inclure la librairie

#include <PubSubClient.h>

Puis on rajoute les variables nécessaires au fonctionnement du programme. Le port par défaut est 1883. Vu que le BME nous sort 3 valeurs, et que Domoticz (qui recevra l’info) en veut 5, on va générer les valeurs humstat (sec, humide, normal) et barostat (prévision barométrique que l’on mettra à zéro).

const char* mqtt_server =         "192.168.1.30";
const int   mqtt_port =           1883;
const char* topic_temp =          "domotique/veranda/temp";
const char* topic_hum =           "domotique/veranda/hum";
const char* topic_hum_stat=       "domotique/veranda/humstat";
const char* topic_baro =          "domotique/veranda/baro";
const char* topic_baro_stat =     "domotique/veranda/barostat";

On déclare ensuite le client MQTT, associé à la connexion wifi.

WiFiClient espClient;
PubSubClient client(espClient);

Pour initier la connexion, on va utiliser le code ci dessous

client.setServer(mqtt_server, mqtt_port);
  
  while (!client.connected()) {
      Serial.print("Attempting MQTT connection...");
      // Create a random client ID
      String clientId = "ESP8266Client-";
      clientId += String(random(0xffff), HEX);
      // Attempt to connect
      if (client.connect(clientId.c_str())) {
        Serial.println("connected");       
...

On va travailler les valeurs du BME pour les formater en tableau afin de les publier sur les topics. Le %0.0f veut dire arrondi à 0 chiffre après la virgule. Si on veut 2 chiffres, il faut mettre %0.2f

char bufTemp[10];
sprintf(bufTemp, "%0.1f", temp);
char bufHum[10];
sprintf(bufHum, "%0.0f", hum);
char bufHumstat[10];
sprintf(bufHumstat, "%i", humstat);
char bufPres[10];
sprintf(bufPres, "%0.0f", pres);

Enfin, on publie sur MQTT. On laisse un délai de 100ms entre chaque sans quoi les messages ne sont pas envoyés.

client.publish(topic_temp, bufTemp);
delay(100);
client.publish(topic_hum, bufHum);
delay(100);
client.publish(topic_hum_stat, bufHumstat);
delay(100);
client.publish(topic_baro, bufPres);
delay(100);
client.publish(topic_baro_stat, "0");
delay(100);

Sur notre Raspberry, on lance l’abonnement au topic domotique/# (le # inclut tous les sous topics).

sudo mosquitto_sub -d -t domotique/#

On voit ensuite apparaitre le résultat

Client mosqsub/18190-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/temp', ... (4 bytes))
23.4
Client mosqsub/18190-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/hum', ... (2 bytes))
40
Client mosqsub/18190-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/humstat', ... (1 bytes))
0
Client mosqsub/18190-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/baro', ... (4 bytes))
1017
Client mosqsub/18190-raspberry received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/barostat', ... (1 bytes))
0

Voici maintenant le code complet du programme à charger sur le nodemcu

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <BME280_t.h>

// Update these with values suitable for your network.

const char* ssid =                "mon_ssid";
const char* password =            "xxxxx";
const char* mqtt_server =         "192.168.1.30";
const int   mqtt_port =           1883;
const char* topic_temp =          "domotique/veranda/temp";
const char* topic_hum =           "domotique/veranda/hum";
const char* topic_hum_stat=       "domotique/veranda/humstat";
const char* topic_baro =          "domotique/veranda/baro";
const char* topic_baro_stat =     "domotique/veranda/barostat";
const int MYALTITUDE =            350;

BME280<> BMESensor;

WiFiClient espClient;
PubSubClient client(espClient);

//fonction qui corrige la pression en fonction de l'altitude
double getP(double Pact, double temp, double altitude) {
  return Pact * pow((1 - ((0.0065 * altitude) / (temp + 0.0065 * altitude + 273.15))), -5.257);
}

void setup() {
  Serial.begin(115200);
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  client.setServer(mqtt_server, mqtt_port);
  
  while (!client.connected()) {
      Serial.print("Attempting MQTT connection...");
      // Create a random client ID
      String clientId = "ESP8266Client-";
      clientId += String(random(0xffff), HEX);
      // Attempt to connect
      if (client.connect(clientId.c_str())) {
        Serial.println("connected");       
        //initialisation bme280
        Wire.begin(0,2); 
        BMESensor.begin();
        BMESensor.refresh();
        
        float temp = BMESensor.temperature;
        float hum = BMESensor.humidity;
        float pres = getP((BMESensor.pressure / 100.0F), BMESensor.temperature, MYALTITUDE);
        int humstat = 0;
        if(hum < 30) humstat = 2;
        if (hum >=30 && hum < 45) humstat = 0;
        if (hum >=45 && hum < 70) humstat = 1;
        if(hum >= 70) humstat = 3;
        
        char bufTemp[10];
        sprintf(bufTemp, "%0.1f", temp);
        char bufHum[10];
        sprintf(bufHum, "%0.0f", hum);
        char bufHumstat[10];
        sprintf(bufHumstat, "%i", humstat);
        char bufPres[10];
        sprintf(bufPres, "%0.0f", pres);
        
       
        client.publish(topic_temp, bufTemp);
        delay(100);
        client.publish(topic_hum, bufHum);
        delay(100);
        client.publish(topic_hum_stat, bufHumstat);
        delay(100);
        client.publish(topic_baro, bufPres);
        delay(100);
        client.publish(topic_baro_stat, "0");
        delay(100);
        
      } else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 5 seconds");
        // Wait 5 seconds before retrying
        delay(5000);
      }
    }
    Serial.println("Going into deep sleep for 120 seconds");
    ESP.deepSleep(120e6); // 120e6 is 120 seconds
}

void loop() {
}

Pour aller plus loin

Pour sécuriser notre serveur, on va lui faire un fichier de configuration particulier et lui exiger un mot de passe pour émettre sur le broker.

Commencer par créer un fichier de configuration vide.

sudo nano /etc/mosquitto/conf.d/mosquitto.conf

Puis ajouter le contenu suivant, dans lequel on interdit les connexions anonymes et on limite le nombre de message dans la queue.

# Config file for mosquitto
#
# See mosquitto.conf(5) for more information.
# https://mosquitto.org/man/mosquitto-conf-5.html

user mosquitto
max_queued_messages 200
message_size_limit 0
allow_zero_length_clientid true
allow_duplicate_messages false

listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd

On va ensuite créer un utilisateur mot de passe. Renseigner 2 fois le mot de passe choisi.

sudo mosquitto_passwd -c /etc/mosquitto/passwd mosquitto

Puis on redémarre le broker

sudo systemctl restart mosquitto

Pour tester, on peut lancer la commande suivante

sudo mosquitto_sub -h 192.168.1.30 -d -u mosquitto -P mon_password -t domotique/#

Il ne reste plus qu’à mettre à jour le NodeMCU en conséquence

On rajoute en entête les identifiants

const char* mqtt_user =           "mosquitto";
const char* mqtt_password =       "mon_password";

Puis dans la ligne de connexion

if (client.connect(clientId.c_str(),mqtt_user,mqtt_password)) {
...

On voit le résultat

Client mosqsub/6466-raspberryp received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/temp', ... (4 bytes))
22.3
Client mosqsub/6466-raspberryp received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/hum', ... (2 bytes))
43
Client mosqsub/6466-raspberryp received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/humstat', ... (1 bytes))
0
Client mosqsub/6466-raspberryp received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/baro', ... (4 bytes))
1016
Client mosqsub/6466-raspberryp received PUBLISH (d0, q0, r0, m0, 'domotique/veranda/barostat', ... (1 bytes))
0
Client mosqsub/6466-raspberryp sending PINGREQ
Client mosqsub/6466-raspberryp received PINGRESP

Conclusion

On touche au but. Notre objet connecté sait maintenant émettre en MQTT ses messages, on va donc pouvoir le gérer au sein de notre domotique.

One Comment

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.