[NODE-RED] Monitoring Zigbee sensor signal reception

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 Likes

Awesome!!! I’ll set this up quickly at home :wink: Thank you very much!

1 Like

Just FYI, when I added that part, I did it at the very end of the file…

And then Node-RED started rebooting in a loop.
In the end I edited the part already present in the file by uncommenting it. So it results in this syntax on my end, with a comma at the very end:

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

Ok, I’ll check, but I can’t rule out that a comma was missed when copying the code.
I’ll fix it, sorry…

Let me know when you’ve set it up…and to test you can change the TIME_OUT constant to just a few

1 Like

You’re right, I did forget the comma when copying it over!
image

I’ve corrected it in my tutorial.

1 Like

And it’s a success!

I replaced the first block with one of the ones I usually use:

Thank you very much @_Will_71 :+1: :wink:

2 Likes

I don’t see the countdown under the TimeOut function, though!
Have you tried disconnecting your sensor by decreasing the TIME_OUT?

Yes yes it’s because I had set it to 1min for testing :wink:
Here it is now:

1 Like

Quick question though, I just did this:

And afterwards I thought maybe I could group all the Telegram Senders into a single one, right?

1 Like

Ok perfect!

Yes I grouped it too using the links

2 Likes

Ah cool, I never understood what LINKs were for, thanks!

As for this case, I don’t know whether using a link or a Telegram box makes any difference.

In general I use a link when I want to reuse the result of another function in another flow.

For example I have a flow with an HTTP request to retrieve the outside temperature (I need to invest in an outdoor sensor) and I then use that temperature in my radiators’ heating power calculations. In this case I create a link so as not to make the HTTP request for each of my radiators. It’s not useful for me to make multiple HTTP requests; in this case the link is useful.

1 Like

I’m coming back here because in the end my first tests seemed conclusive… but that’s not the case.

I set a 12-hour timeout and twice a day I receive Telegram messages saying my devices are offline… :thinking:

I don’t really understand where the problem is coming from.
Also, since I see @_Will_71 that you’re great at Node-RED, wouldn’t it be easier to check z2m’s availability status Online and Offline?

Or at worst do a calculation with the last_seen data?

So, the devices where you receive the Telegram alert — are they offline or not?
Because in your screenshots you have 2 sensors offline with last seen times greater than 12h. So the behavior is normal.

No no, I took a random screenshot, I have a few devices offline.
But the sensors I’m trying to check are all online and have last_seen of at most 1h

For my part everything works fine but you didn’t use exactly the same elements as I did
I don’t know if the zigbee2mqtt object reports the info the same way as the mqtt object I use?
In my case I connect directly to the mqtt broker and with each sensor send I read the value. Maybe in your case, if the value doesn’t change, zigbee2mqtt doesn’t resend the data. In my opinion you should explore that avenue.
![IMG_20230405_160503|690x263](upload://7zphVcqZ5G3yY8pfVf

Ah ok… so I’ll try using your MQTT block and see how it goes, thanks for the tip!!

That’s just a suggestion because I’ve never used your block.
I’ll try doing a test with this zigbee2mqtt block to see the result I get.

1 Like

Great idea! It’s true that an inoperative Zigbee sensor isn’t reported as faulty, which is still problematic!
I just tried @_Will_71’s flow and observed the same thing as @guim31.

I replaced msg.topic with msg.payload in the ‹ change › block and in the ‹ Timeout › function script (lines 19 and 29)

It seems to work correctly for me after this change.

1 Like

I don’t agree with you.

Why did I choose the topic and not the payload? Because I don’t need the payload — I’m not trying to use the sensor’s data.

So I used a change node to standardize the sensor’s topic so I don’t have to modify it in the script of my TimeOut function. After this step it may be optional, but in that case you would need to adapt the topic in the function.

Your sensor still always emits a payload with its data on a dedicated topic. (see image below)

Then you modified lines 19 and 29 of my function. OK for line 19 if you use the payload, but why line 29?
Line 29 is a test to retrieve the topic from the inject node that I send every minute.

As for why it doesn’t work on your side, I don’t know — maybe you copied it wrong or didn’t use the same nodes as me, I don’t know!!