[NODE-RED] Contrôle réception signal capteur zigbee

Je vous partage un flow Node-Red pour mesurer le temps de réception du signal d’un capteur zigbee.
Si le temps entre chaque réception du signal est supérieur à 24H (paramètrable) alors une alerte avec Telegram est envoyée.

Je réalisé cette fonction car l’indication de batterie des capteurs zigbee n’est pas fiable et plusieurs fois je me suis fait avoir sur une température alors que le capteur ne fonctionnait plus. Ce qui peux être embêtant quand on a besoin de cette valeur dans une scène.
Maintenant je sais si un de mes capteurs n’envoi plus de données au bout de 24H. Et cela en boucle toutes les 24H.
Testé avec capteur d’ouverture, température/humidité, switch, capteur vibration, capteur de mouvement.

La demande de fonctionnalité dans Gladys

Pour l’installation de NODE-RED, vous connaissez déjà tous le tuto vidéo de @pierre-gilles. https://www.youtube.com/watch?v=bpmHzR8_S5g .

Pré-requis:

  • configurer le broker mqtt pour se connecter au broker zigbee2mqtt


    Le mot de passe est généré par Gladys, vous pouvez le retrouver dans la db.
    image

  • installer dans Node-Red : « node-red–contrib-telegrambot »

  • configurer un bot telegram pour Node-Red

  • récuperer un numéro ChatId pour pouvoir envoyer des messages avec Télégram dans Node-Red (j’ajouterais par la suite comment récupérer ce numéro ci besoin)
    Cette partie Telegram est optionnel, vous pouvez aussi renvoyer un état dans un fake device mqtt à Gladys. Et ensuite faire l’alerte Telegram avec Gladys.

  • configuration de Node-Red pour ecrire/lire des fichiers:
    Editer le fichier /var/lib/node-red/settings.js (voir la vidéo de @pierre-gilles pour l’installation de Node-Red il explique comment éditer ce fichier)
    Ajouter les lignes ci dessous:

contextStorage: {
   default: "memoryOnly",
   memoryOnly: { module: 'memory' },
   file: { module: 'localfilesystem' }
},

Voici un aperçu du flow dans Node-red d’un de mes capteurs:

Voici le flow à importer dans Node-Red: j’ai modifié un de mes flows pour qu’il soit générique et que mes données de connexion soit effacées

[
    {
        "id": "d5482843ef207659",
        "type": "function",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "send msg to telegram",
        "func": "msg.payload={};\nmsg.payload.chatId =\"******\";\nmsg.payload.content = \"[ALERTE] Perte de la communication avec le thermomètre! Vérifier la pile de ce device!\";\nmsg.payload.type = \"message\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1180,
        "y": 120,
        "wires": [
            [
                "f57a85488266a794"
            ]
        ]
    },
    {
        "id": "2c780ff8e1b28a9e",
        "type": "mqtt in",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "Thermomètre",
        "topic": "zigbee2mqtt/Thermomètre",
        "qos": "2",
        "datatype": "auto",
        "broker": "2a159ac23236c7e2",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 150,
        "y": 120,
        "wires": [
            [
                "47ee6c1dd9132535"
            ]
        ]
    },
    {
        "id": "47ee6c1dd9132535",
        "type": "change",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "sensor",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 490,
        "y": 120,
        "wires": [
            [
                "147c4e952052bad4"
            ]
        ]
    },
    {
        "id": "cfd94c8353c2c6f9",
        "type": "inject",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "Task 1000",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "60",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "task",
        "payload": "",
        "payloadType": "date",
        "x": 490,
        "y": 80,
        "wires": [
            [
                "147c4e952052bad4"
            ]
        ]
    },
    {
        "id": "147c4e952052bad4",
        "type": "function",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "TimeOut",
        "func": "/*\n * Time-out capteur\n */\n\n\nconst TIME_OUT = 1440; /* 24 heures */\n\n/*\n * Sortie time out\n */\nvar msg_TimeOut = null;\n\n/*\n * delai avant time out capteur entre 2 réceptions\n */\nvar delayTimeOut = context.get(\"delayTimeOut\", \"file\") || TIME_OUT;  // initialize variables\n\n\nif (msg.topic === \"sensor\")  // initialisation compteur a chaque réception capteur\n{\n    delayTimeOut = TIME_OUT;\n    context.set(\"delayTimeOut\", delayTimeOut, \"file\");  // save last value in local context\n\n    node.status({ fill: \"green\", shape: \"ring\", text: \"Remaining: \" + delayTimeOut + \" minutes\" });\n\n    //msg_TimeOut = { topic: \"temp\", payload: delayTimeOut };\n}\n\nif (msg.topic === \"task\")   // mise à jour toutes les minutes\n{\n    if (delayTimeOut > 0)\n    {\n        delayTimeOut--;\n        context.set(\"delayTimeOut\", delayTimeOut, \"file\");  // save last value in local context\n        \n        node.status({ fill: \"green\", shape: \"ring\", text: \"Remaining: \" + delayTimeOut + \" minutes\" });\n\n    }\n\n    if (delayTimeOut === 0)\n    {\n        delayTimeOut = 0;    \n        context.set(\"delayTimeOut\", delayTimeOut, \"file\");  // save last value in local context\n\n        node.status({ fill: \"red\", shape: \"ring\", text: \"TIME OUT\" });\n\n        msg_TimeOut = { topic: \"timeout\", payload: \"TIME OUT\" };\n    }\n    \n} \n\nreturn msg_TimeOut;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 900,
        "y": 120,
        "wires": [
            [
                "d5482843ef207659"
            ]
        ],
        "outputLabels": [
            "TimeOut"
        ]
    },
    {
        "id": "f57a85488266a794",
        "type": "telegram sender",
        "z": "d76f072c1559266b",
        "g": "87c2f560ca6c5a98",
        "name": "",
        "bot": "e1f028778559de40",
        "haserroroutput": false,
        "outputs": 1,
        "x": 1510,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "2a159ac23236c7e2",
        "type": "mqtt-broker",
        "name": "zigbee2mqtt2",
        "broker": "192.168.*.*",
        "port": "1884",
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": "",
        "credentials": {}
    },
    {
        "id": "e1f028778559de40",
        "type": "telegram bot",
        "botname": "******",
        "usernames": "",
        "chatids": "",
        "baseapiurl": "",
        "updatemode": "polling",
        "pollinterval": "300",
        "usesocks": false,
        "sockshost": "",
        "socksprotocol": "socks5",
        "socksport": "6667",
        "socksusername": "anonymous",
        "sockspassword": "",
        "bothost": "",
        "botpath": "",
        "localbotport": "8443",
        "publicbotport": "8443",
        "privatekey": "",
        "certificate": "",
        "useselfsignedcertificate": false,
        "sslterminated": false,
        "verboselogging": false
    }
]

1. mqtt in : Configuration capteur zigbee
image

Mettre un objet mqtt in
image

Indiquez dans serveur votre broker zigbee2mqtt
Indiquez dans Topic le topic de votre capteur (vous pouvez vous aider d’une application comme MQTT Explorer pour explorer votre réseau et retrouver le topic). En général le topic est zigbee2mqtt/nomdevotrecapteur

image

2. change: transformation topic
image

Mettre un objet change pour modifier le topic et ainsi éviter de modifier le topic pour chaque capteur ensuite dans la fonction qui viens après.
image

image

3. function : calcul du temps entre 2 envois du signal zigbee du capteur
image

Mettre un objet function
image

Dans le code ci dessous vous pouvez modifier la constante TIME_OUT (en minutes). Moi j’ai mis 1440 minutes (24H)

/*
 * Time-out capteur
 */


const TIME_OUT = 1440; /* 24 heures */

/*
 * Sortie time out
 */
var msg_TimeOut = null;

/*
 * delai avant time out capteur entre 2 réceptions
 */
var delayTimeOut = context.get("delayTimeOut", "file") || TIME_OUT;  // initialize variables


if (msg.topic === "sensor")  // initialisation compteur a chaque réception capteur
{
    delayTimeOut = TIME_OUT;
    context.set("delayTimeOut", delayTimeOut, "file");  // save last value in local context

    node.status({ fill: "green", shape: "ring", text: "Remaining: " + delayTimeOut + " minutes" });

    //msg_TimeOut = { topic: "temp", payload: delayTimeOut };
}

if (msg.topic === "task")   // mise à jour toutes les minutes
{
    if (delayTimeOut > 0)
    {
        delayTimeOut--;
        context.set("delayTimeOut", delayTimeOut, "file");  // save last value in local context
        
        node.status({ fill: "green", shape: "ring", text: "Remaining: " + delayTimeOut + " minutes" });

    }

    if (delayTimeOut === 0)
    {
        delayTimeOut = 0;    
        context.set("delayTimeOut", delayTimeOut, "file");  // save last value in local context

        node.status({ fill: "red", shape: "ring", text: "TIME OUT" });

        msg_TimeOut = { topic: "timeout", payload: "TIME OUT" };
    }
    
} 

return msg_TimeOut;

J’ai ajouter le temps restant dans la fonction avant que l’alerte soit envoyée
image

4. inject: envoie d’un payload toutes les minutes pour recalculer le temps dans la fonction
image

Mettre un objet inject
image

Mettre en topic task. Ce topic est utilisé dans la fonction. Si vous le modifier il faudra modifier le code de la fonction.
Indiquez ensuite un interval chaque minute

5. function: création du texte pour telegram
image

Mettre un objet function
image

Il faudra insérer votre numéro chatId et vous pouvez personnaliser votre message

msg.payload={};
msg.payload.chatId ="******";
msg.payload.content = "[ALERTE] Perte de la communication avec le thermomètre! Vérifier la pile de ce device!";
msg.payload.type = "message";
return msg;

6. sender telegram: envoie du message
image

Ajouter un objet telegram sender
image

Indiquez votre bot telegram pour nodered
image

6 « J'aime »

Super ça !!! Je vais mettre ça en place rapidement chez moi :wink: Merci beaucoup !

1 « J'aime »

Pour la petit info, lorsque j’ai ajouté cette partie là, je l’ai fait en toute fin de fichier…

Et là Node-red s’est mis à rebooter en boucle.
Au final j’ai édité la partie déjà présente dans le fichier en la dé-commentant. Ça donne donc cette syntaxe de mon coté, avec une virgule tout à la fin :

contextStorage: {
   default: "memoryOnly",
   memoryOnly: { module: 'memory' },
   file: { module: 'localfilesystem' }
},

Ok je vérifierai mais c’est pas exclu un oubli de la virgule en copiant le code.
Je corrigerai, désolé…

Tiens moi au courant quand tu la mis en place…et pour tester tu peux changer la constante TIME_OUT et mettre seulement quelques minutes et en enlevant la pile d’un de tes capteurs.

1 « J'aime »

Tu as raison j’ai bien oublié la virgule en recopiant!
image

J’ai corrigé dans mon tuto.

1 « J'aime »

Et c’est un succès !

J’ai remplacé le premier bloc par un de ceux que j’ai l’habitude d’utiliser :

Merci beaucoup @_Will_71 :+1: :wink:

2 « J'aime »

Je ne vois pas le décompte sous la fonction TimeOut par contre!
As-tu essayé de déconnecter ton capteur en diminuant le TIME_OUT?

Oui oui c’est parce que je l 'avais mis à 1min pour les tests :wink:
Le voilà maintenant :
image

1 « J'aime »

Par contre petite question, je viens de faire ça :

Et après coup je me suis dit que je pourrais peut-être regrouper tous les Telegram Sender en un seul non ?

1 « J'aime »

Ok parfait!

Oui je l’ai regroupé aussi en utilisant les link
image

2 « J'aime »

Ah cool j’ai jamais compris à quoi servaient les LINK merci !

Après pour ce cas est ce que cela change quelque chose d’utiliser un link ou une box Telegram je ne sais pas.

En général j’utilise un link quand je veux réutiliser le résultat d’une autre fonction dans un autre flow.

Par exemple j’ai un flow avec une requête http pour récupérer la température extérieur (il faut que j’investisse dans un capteur extérieur) et j’utilise ensuite cette température dans mes calculs de puissance de chauffe de mes radiateurs. Dans ce cas je fais un link pour ne pas faire la requête http pour tous mes radiateurs. Ce n’est pas utile que je fasse plusieurs requêtes http dans ce cas le link est utile.

1 « J'aime »

Je reviens ici car au final mes premiers teste me semblaient concluants… mais il n’en est rien.

J’ai mis un timeout de 12h et 2x par jour je reçois des messages Telegram comme quoi mes appareils sont hors ligne… :thinking:

Je ne comprends pas vraiment d’où vient le problème.
Par ailleurs, comme je vois @_Will_71 que tu es balèze en Nodered, est-ce qu’il ne serait pas plus facile de checker l’état de la disponibilité En Ligne et Hors Ligne de z2m ?

Ou au pire faire un calcul avec la donnée last_seen ?

Et du coup les appareils ou tu reçoit l’alerte telegram ils sont hors lignes ou pas?
Car sur tes captures tu as 2 capteurs hors ligne avec des temps last seen superieur a 12h. Donc le comportement est normal.

Non non j’ai fait une capture au pif, j’ai quelques appareils hors ligne.
Mais les capteurs que je cherche à checker sont tous en ligne et ont des last_seen de maximum 1h

Pour ma part tout fonctionne bien mais tu n’as pas utilisé tous les mêmes éléments que moi
Est ce que l’objet zigbee2mqtt remonte les infos comme l’objet mqtt que j’utilise je ne sais pas?
Dans mon cas je me connecte directement au broker mqtt et a chaque envoie du capteur je lis la valeur. Peut-être que dans ton cas si la valeur ne change pas alors zigbee2mqtt ne renvoie pas la donnée. A mon avis il faut explorer cette piste.

Ah ok… alors je vais essayer d’utiliser ton bloc MQTT et voir ce que ça donne, merci du conseil !!

Après c’est qu’une suggestion car je n’ai jamais utilisé ton bloc.
J’essaierai de faire un essai avec ce bloc zigbee2mqtt pour voir le résultat que j’obtiens

1 « J'aime »

Super idée ! il est vrai qu’un capteur zigbee inop n’est pas signalé en défaut, ce qui est tout de même problématique !
Je viens d’essayer le flow de @_Will_71 et j’ai constaté la même chose que @guim31.

J’ai remplacé msg.topic par msg.payload dans le bloc ‹ change › et dans le script de la fonction ‹ Timeout › (lignes 19 et 29)

Ca a l’air de fonctionner correctement chez moi après cette modification.

1 « J'aime »

Je ne suis pas d’accord avec toi.

Pourquoi déjà ai-je choisi le topic et non le payload? Car le payload j’en ai pas besoin car je ne cherche pas à utiliser les données du capteur.

Donc j’ai utilisé un bloc change pour uniformiser le topic du capteur pour ne pas avoir à le modifier dans le script de ma fonction TimeOut. Après cette étape peut-être optionnel mais dans ce cas il faudrait adapter le topic dans le fonction.

Ton capteur émet toujours sur un topic dédié un payload avec ses données. (voir image ci dessous)
image

Ensuite tu as modifié la ligne 19 et 29 de ma fonction. Ok pour la ligne 19 si tu utilises le payload mais pourquoi la ligne 29?
La ligne 29 est un test pour récuperer le topic du bloc inject que j’envoie toute les minutes.

Après pourquoi cela ne fonctionne pas chez toi je sais pas peut-être tu as mal recopié ou pas utilisé les mêmes bloc que moi je sais pas!!