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