smarthome.codes

Zigbee2MQTT Device Health Check with Node-RED for Smart Home

Zigbee2MQTT + Code

Zigbee2MQTT Device Health Check with Node-RED: Automatic Monitoring of Your Smart Home Devices

With this Node-RED flow, you can monitor the online status of your Zigbee devices and receive automatic notifications when devices go offline. The system logs Zigbee device data and stores the connection status in the Flow Context for reliable smart home monitoring.

Table of Contents

What is Zigbee2MQTT and why do we need a Health Check?

Zigbee2MQTT is software that acts as a bridge between your Zigbee devices and MQTT. It allows you to integrate your Zigbee devices into your smart home without proprietary hubs or bridges and control them via the standardized MQTT protocol.

In a smart home system with many Zigbee devices, however, individual devices may lose contact or go offline. The reasons for this can be varied: battery problems, signal interference, or simply faulty devices. Without automatic monitoring, you may not notice these problems until you want to use the affected device.

A Device Health Check solves this problem by continuously monitoring the online status of all devices and notifying you as soon as a device is no longer reachable. This allows you to act proactively before problems occur in your smart home.

How the Device Health Check System works

Our Node-RED flow for the Zigbee2MQTT Device Health Check consists of three main components:

  1. Log function: Logs all data sent by the Zigbee devices to understand and evaluate the available information.
  2. Device Health Check: Monitors the online/offline status of the devices and stores it in the Flow Context.
  3. Pushover notification: Sends you a message when devices go offline or are not functioning properly.

The flow receives data via MQTT from your Zigbee2MQTT installations, processes the “availability” messages, and can perform a status check at set times (in our example at 8 PM) and inform you about offline devices.

Log function for Zigbee devices

Before we dive into the health check, it’s important to understand what data our Zigbee devices actually provide. For this, we implement a simple log function that writes all MQTT messages to a file.

// Create an object that contains both the topic and the payload let logEntry = { timestamp: new Date().toISOString(), // Optional: adds a timestamp topic: msg.topic, payload: msg.payload };
// Convert the object to a JSON string and add a line break
msg.payload = JSON.stringify(logEntry) + "\n";
return msg;

This function captures the topic and payload for each MQTT message, adds a timestamp, and saves everything as a JSON entry in a log file. This allows you to see exactly what data your devices are sending and which of them are relevant for the health check.

Particularly important for the health check are messages with the suffix /availability, as they contain the online status of the respective device.

Zigbee2MQTT Device Health Check

The core of our system is the Device Health Check function. It monitors the availability of devices and stores their status in the Flow Context.

// Initialize the device object in the flow context if it doesn't exist yet let devices = flow.get('zigbee2mqtt_devices') || {};
// Function to extract the device name from the topic
function extractDeviceName(topic) {
// Assumption: Topic format is "zigbee2mqtt_X/DeviceName/availability"
let parts = topic.split('/');
return parts;
}

// Function to check if the topic has one of the allowed prefixes
function hasValidPrefix(topic) {
const prefixes = ["zigbee2mqtt_1/", "zigbee2mqtt_2/"];
return prefixes.some(prefix => topic.startsWith(prefix));
}

// Check if the message is an availability message
if (msg.topic && hasValidPrefix(msg.topic) && msg.topic.endsWith("/availability")) {
// This is an availability message
let deviceName = extractDeviceName(msg.topic);

text
// **New filter: Ignore devices whose names start with '0x'**
if (deviceName.startsWith("0x")) {
    // Ignore device, no further processing
    return null;
}

let state = msg.payload.state; // Assumption: payload is an object with 'state'

// Update the device object
devices[deviceName] = state;

// Save the updated object back to the flow context
flow.set('zigbee2mqtt_devices', devices);

// Don't forward any message
return null;
} else {
// Assumption: This is a trigger from an inject node
// Check if msg.payload.trigger is set to true
if (msg.payload && msg.payload.trigger === true) {
// Create a new message with the current device state
let newMsg = { payload: devices };

text
    // Send the new message
    return newMsg;
} else {
    // If trigger is not true, do nothing more
    return null;
}
}

Let’s take a closer look at how it works:

Storage in Flow Context

The function uses Node-RED’s Flow Context to store the status of devices between different calls. Each time it starts, the current device status is loaded from the context or an empty object is initialized if no data is available yet.

Two Operating Modes

The function works in two modes:

  1. Update mode: When an availability message is received, the status of the corresponding device is updated in the Flow Context.
  2. Query mode: Activated by a trigger and returns the current status of all known devices.

Intelligent Filtering

The code filters incoming messages in various ways:

  • Only messages with valid prefixes (zigbee2mqtt_1/ or zigbee2mqtt_2/) are processed.
  • Only topics with the suffix /availability are considered for status tracking.
  • Devices with names starting with 0x (typically internal Zigbee IDs) are ignored.

Notifications with Pushover

After the health check has determined the status of all devices, the notification function comes into play. It analyzes which devices are offline and sends a corresponding message via Pushover.

// Assumption: msg.payload is an object with device names as keys and status as values let devices = msg.payload; let offlineDevices = [];
// Iterate over all devices and filter those that are offline or undefined
for (let device in devices) {
if (devices.hasOwnProperty(device)) {
let status = devices[device];

text
    if (status === undefined) {
        // Device has no defined status
        offlineDevices.push(`${device} (Status: undefined)`);
    } else if (typeof status === 'string' && status.toLowerCase() === "offline") {
        // Device is offline
        offlineDevices.push(device);
    }
}
}

let msgToSend = null;

if (offlineDevices.length > 0) {
// There is at least one device that is offline or has no defined status
let message = "The following devices are offline or have no defined status:\n" + offlineDevices.join("\n");
msgToSend = { payload: message };
} else {
// All devices are online
let message = "All devices are online";
msgToSend = { payload: message };
}

// Send the corresponding message
return msgToSend;

This function:

  • Analyzes the status of all known devices
  • Identifies devices that are offline or have an undefined status
  • Creates a formatted message with a list of problematic devices
  • If all devices are online, a positive confirmation message is sent

The message is then forwarded to a Pushover node, which sends it as a push notification to your smartphone. This way, you are immediately informed when a device is no longer reachable.

Setting up and customizing the flow

To implement the health check in your own Node-RED, you need to make some adjustments:

MQTT Configuration

In the example flow, two MQTT sources are monitored: zigbee2mqtt_1/# and zigbee2mqtt_2/#. You need to adapt these to your Zigbee2MQTT installation. If you only use one instance, you can remove one of the MQTT nodes.

Pushover Setup

For notifications, you need a Pushover account and must configure the corresponding node in Node-RED. Enter your API keys and customize the notification title.

Schedule Adjustment

The flow is configured to perform a status check every day at 8 PM. You can adjust this time in the settings of the inject node or add more times. For regular checks multiple times a day, it may be useful to use multiple inject nodes with different schedules.

Log File Path

In the example, the log file is saved under /data/zigbee2mqtt.log. Adjust this path according to your Node-RED installation and make sure that the user under which Node-RED runs has write permissions for this directory.

The complete Node-RED flow

Here is the complete Node-RED flow to import. Copy the following code and import it into your Node-RED instance via the menu (Import → Clipboard).

[{"id":"b54a32a9e33f104d","type":"inject","z":"f48cf010.9333b","name":"Trigger 20 Uhr","props":[{"p":"payload.trigger","v":"true","vt":"bool"}],"repeat":"","crontab":"00 20 * * *","once":false,"onceDelay":0.1,"topic":"","x":800,"y":1020,"wires":[["db54f60627a316cf"]]},{"id":"309cd5f00343ef33","type":"mqtt in","z":"f48cf010.9333b","name":"","topic":"zigbee2mqtt_1/#","qos":"2","datatype":"auto-detect","broker":"a898461e.979668","nl":false,"rap":true,"rh":0,"inputs":0,"x":780,"y":800,"wires":[["ada861d5855e07f1","db54f60627a316cf"]]},{"id":"39880180350e465a","type":"mqtt in","z":"f48cf010.9333b","name":"","topic":"zigbee2mqtt_2/#","qos":"2","datatype":"auto-detect","broker":"a898461e.979668","nl":false,"rap":true,"rh":0,"inputs":0,"x":780,"y":840,"wires":[["ada861d5855e07f1","db54f60627a316cf"]]},{"id":"ada861d5855e07f1","type":"function","z":"f48cf010.9333b","name":"logs","func":"// Create an object that contains both the topic and the payload\nlet logEntry = {\n timestamp: new Date().toISOString(), // Optional: adds a timestamp\n topic: msg.topic,\n payload: msg.payload\n};\n\n// Convert the object to a JSON string and add a line break\nmsg.payload = JSON.stringify(logEntry) + \"\\n\";\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":990,"y":840,"wires":[["d4bea2321993faba"]]},{"id":"d4bea2321993faba","type":"file","z":"f48cf010.9333b","name":"zigbee2mqtt log","filename":"/data/zigbee2mqtt.log","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":1160,"y":840,"wires":[[]]},{"id":"db54f60627a316cf","type":"function","z":"f48cf010.9333b","name":"Zigbee2MQTT Device Health Check","func":"// Initialize the device object in the flow context if it doesn't exist yet\nlet devices = flow.get('zigbee2mqtt_devices') || {};\n\n// Function to extract the device name from the topic\nfunction extractDeviceName(topic) {\n // Assumption: Topic format is \"zigbee2mqtt_X/DeviceName/availability\"\n let parts = topic.split('/');\n return parts;\n}\n\n// Function to check if the topic has one of the allowed prefixes\nfunction hasValidPrefix(topic) {\n const prefixes = [\"zigbee2mqtt_1/\", \"zigbee2mqtt_2/\"];\n return prefixes.some(prefix => topic.startsWith(prefix));\n}\n\n// Check if the message is an availability message\nif (msg.topic && hasValidPrefix(msg.topic) && msg.topic.endsWith(\"/availability\")) {\n // This is an availability message\n let deviceName = extractDeviceName(msg.topic);\n\n // **New filter: Ignore devices whose names start with '0x'**\n if (deviceName.startsWith(\"0x\")) {\n // Ignore device, no further processing\n return null;\n }\n\n let state = msg.payload.state; // Assumption: payload is an object with 'state'\n\n // Update the device object\n devices[deviceName] = state;\n\n // Save the updated object back to the flow context\n flow.set('zigbee2mqtt_devices', devices);\n\n // Don't forward any message\n return null;\n} else {\n // Assumption: This is a trigger from an inject node\n // Check if msg.payload.trigger is set to true\n if (msg.payload && msg.payload.trigger === true) {\n // Create a new message with the current device state\n let newMsg = { payload: devices };\n\n // Send the new message\n return newMsg;\n } else {\n // If trigger is not true, do nothing more\n return null;\n }\n}\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1090,"y":1020,"wires":[["57fb9007e3bf4b6d","0fbe7d51e75713d2"]]},{"id":"0fbe7d51e75713d2","type":"function","z":"f48cf010.9333b","name":"Pushover","func":"// Assumption: msg.payload is an object with device names as keys and status as values\nlet devices = msg.payload;\nlet offlineDevices = [];\n\n// Iterate over all devices and filter those that are offline or undefined\nfor (let device in devices) {\n if (devices.hasOwnProperty(device)) {\n let status = devices[device];\n \n if (status === undefined) {\n // Device has no defined status\n offlineDevices.push(`${device} (Status: undefined)`);\n } else if (typeof status === 'string' && status.toLowerCase() === \"offline\") {\n // Device is offline\n offlineDevices.push(device);\n }\n }\n}\n\nlet msgToSend = null;\n\nif (offlineDevices.length > 0) {\n // There is at least one device that is offline or has no defined status\n let message = \"The following devices are offline or have no defined status:\\n\" + offlineDevices.join(\"\\n\");\n msgToSend = { payload: message };\n} else {\n // All devices are online\n let message = \"All devices are online\";\n msgToSend = { payload: message };\n}\n\n// Send the corresponding message\nreturn msgToSend;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1340,"y":1020,"wires":[["04ccd4c344465c6e","dbd1c76a73ae9e1e"]]},{"id":"57fb9007e3bf4b6d","type":"debug","z":"f48cf010.9333b","name":"Debug Output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1360,"y":980,"wires":[]},{"id":"dbd1c76a73ae9e1e","type":"pushover api","z":"f48cf010.9333b","keys":"c1da56b8.1433f8","title":"zigbee2mqtt #1 + #2 - Geräte Healthcheck","name":"","x":1500,"y":1020,"wires":[]},{"id":"04ccd4c344465c6e","type":"debug","z":"f48cf010.9333b","name":"debug 18","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1500,"y":1060,"wires":[]},{"id":"a898461e.979668","type":"mqtt-broker","name":"mqtt-server (fhem-main-ext - docker)","broker":"172.18.0.6","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"c1da56b8.1433f8","type":"pushover-keys","name":"Reinhards Pushover"}] 

Note: translated from the German version of the article. Therefore some screenshots are in German.

Adriana

I have been fascinated by everything to do with technology for many years, especially when it comes to making my own home smarter and more comfortable. For me, a smart home is not just a technical gimmick, but above all a real improvement in quality of life. It all started very small with a networked thermostat, but this little experiment quickly turned into a great passion. My home now comprises a sophisticated system of lighting control, heating automation and security solutions that make my everyday life noticeably easier. For me, a smart home is much more than just technology: it means convenience, sustainability and a real improvement in quality of life. It is particularly important to me that systems are intuitive to use and can be easily integrated into everyday life, regardless of brand or manufacturer. My vision is a home that not only reacts to commands, but also thinks and acts with foresight. I dream of a smart home that saves energy, adapts individually to residents and the environment and always puts people at the center.

Here on my blog, I share personal experiences, helpful tips and interesting insights on the topic of smart homes.
I'm particularly looking forward to exchanging ideas with you, whether you're just starting out with your smart home or are already a real pro.

Do you have any questions, suggestions or just want to chat? Then feel free to write me a comment.

Add comment

Donations and Support

PayPal

Donate quickly and securely with PayPal – no registration required.

Patreon

Become a member and support smarthome.codes

Buy Me a Coffee

Support my work with a coffee and a personal message.