Device-Availability. Setting Guidance

I was looking at my connected zigbee devices and could see an ‘Availability’ field.
This is currently set to disabled.
Reading the documentation I feel that this should probably be set to enabled.
Just a couple of questions.

  1. Would it be better to set this to enabled so it can be seen if devices are offline.
  2. If set to enabled, would it then be possible to have a flow that would report devices when their state changes to offline.

I don’t want to go ahead a change something that could cause issues and lead me down a different rabbit hole.
Many thanks.

1 Like

It’s a matter of taste, it has gotten fairly good at keeping track of this properly per device, it is also possible to do this with a flow in NR keeping track of all devices automatically based on receiving data from them, but it’s not necessarily any better, just different.
You could very well use that information in a flow and create an alert with it, the way I do it is probably not useful for everyone, but if there is interest I can share the flow:

1 Like

I just want to add that it’s the default with z2m to have this off and not OLL’s insistence, although they could change it before distribution. But I see nothing wrong with it as-is because it is so easily changed.

@bobbles, I too like it on.

Please do share. I’ve got something worked up, but it’s probably no where near as good as yours.

In fact, why dont you just share all of your flows! :grin:

3 Likes

Dear God No!!! Are you trying to scare everyone away? :stuck_out_tongue_winking_eye:

6 Likes

:dragon: :dragon_face:
Ok, I’ll share this one, it has not been cleaned up and there may be dragons…:

[{"id":"cb4eadb4.7c938","type":"mqtt in","z":"1ae1a884.3eb117","name":"","topic":"zigbee2mqtt/#","qos":"2","datatype":"auto","broker":"756cfdc4.e690b4","nl":false,"rap":true,"rh":0,"inputs":0,"x":130,"y":140,"wires":[["a9c0e5d5.3640a8","bc07260b.c393e8"]]},{"id":"a9c0e5d5.3640a8","type":"function","z":"1ae1a884.3eb117","name":"Check Device Health & Save Global State","func":"// -- DO NOT EDIT BELOW THIS LINE --\n// Actual code\nconst defaultTimeout = context.get(\"defaultTimeout\");\nconst deviceOverrides = context.get(\"deviceOverrides\");\n\nif(msg.topic.startsWith(\"zigbee2mqtt/bridge/\")) {\n    // These messages we don't need, in general, but renames are important\n    if(msg.topic == \"zigbee2mqtt/bridge/response/device/rename\") {\n        node.log(msg);\n        const payloadObj = JSON.parse(msg.payload);\n        flow.set(\"dh*_\" + encodeURI(payloadObj.data.to.replace(/\\s/g, '_')), flow.get(\"dh*_\" + encodeURI(payloadObj.data.from.replace(/\\s/g, '_'))));\n        flow.set(\"dh*_\" + encodeURI(payloadObj.data.from.replace(/\\s/g, '_')), undefined);\n        flow.set(\"dh*_\" + encodeURI(payloadObj.data.from), undefined);\n    }\n    return null;\n} else {\n    if(msg.topic == \"PING\" || msg.topic == \"STATUS\") {\n        // Cleanup code, only used temporarily\n        // for(const savedDeviceNameEncoded of global.keys()) {\n        //     if(savedDeviceNameEncoded.includes('%20')) {\n        //         global.set(savedDeviceNameEncoded, undefined);\n        //     }\n        // }\n        let deviceStatus = {};\n        let deviceOffline = {};\n        let allOnline = true;\n        const now = new Date().getTime();\n        // const now = new Date().getTime();\n        msg.now = now;\n        const lastSeenTime = new Date(null);\n        for(const deviceNameEncoded of flow.keys()) {\n            if(deviceNameEncoded.startsWith(\"dh*_\")) {\n                if(deviceNameEncoded.includes(\"%20\")) {\n                    // This is an old and incorrect property\n                    flow.set(deviceNameEncoded, undefined);\n                } else {\n                    const lastSeen = flow.get(deviceNameEncoded);\n                    const deviceName = decodeURI(deviceNameEncoded.substring(4).replace(/_/g, ' '));\n                    if(deviceName.startsWith(\"0x\")) {\n                            // This is an unnamed device, ignore it\n                            flow.set(deviceNameEncoded, undefined);\n                            \n                    } else if(lastSeen !== null) {\n                        let timeout = defaultTimeout;\n                        if(deviceOverrides.hasOwnProperty(deviceName)) timeout = deviceOverrides[deviceName];\n                        const timeLapsedNum = Math.round((now - lastSeen) / 1000)\n                        msg.timeLapsedNum = timeLapsedNum;\n                        lastSeenTime.setSeconds(timeLapsedNum);\n                        const timeLapsedStr = lastSeenTime.toISOString().substr(11, 8);\n                        if(timeout == -1) {\n                            deviceStatus[deviceName] = \"SKIPPED: \" + timeLapsedNum;\n                        } else if(timeLapsedNum < timeout) {\n                            deviceStatus[deviceName] = \"ONLINE: \" + timeLapsedNum + \", \" + lastSeen;\n                        } else {\n                            deviceStatus[deviceName] = \"OFFLINE: \" + timeLapsedNum + \", \" + lastSeen;\n                            // Clean up offline devices\n                            global.set(\"zigbee_motion_\" + encodeURI(deviceName.replace(/\\s/g, '_')), undefined);\n                            global.set(\"zigbee_contact_\" + encodeURI(deviceName.replace(/\\s/g, '_')), undefined);\n                            global.set(\"zigbee_\" + encodeURI(deviceName.replace(/\\s/g, '_')), undefined);\n                            allOnline = false;\n                            deviceOffline[deviceName] = timeLapsedNum;\n                        }\n                    }\n                }\n            }\n        }\n        if(allOnline) {\n            node.status({fill:\"green\",shape:\"dot\",text:\"online\"});\n        } else {\n            node.status({fill:\"red\",shape:\"ring\",text:\"offline\"});\n        }\n        msg.payload = deviceStatus;\n        if(Object.keys(deviceOffline).length > 0) {\n            msg.deviceOffline = deviceOffline;\n        } else {\n            msg.deviceOffline = null;\n        }\n        if(msg.topic == \"PING\") {\n            if(msg.deviceOffline === null) {\n                return null;\n            } else {\n                msg.topic = \"DeviceOffline\";\n            }\n        }\n        return msg;\n    } else {\n        const deviceNameRegExp = /zigbee2mqtt\\/([^\\/]*)/;\n        const deviceNameExtendedRegExp = /zigbee2mqtt\\/([^\\/]*)\\/([^\\/]*)/;\n        let deviceName = deviceNameRegExp.exec(msg.topic);\n        let deviceNameExtended = deviceNameExtendedRegExp.exec(msg.topic);\n        if(deviceName !== null && deviceName.length == 2 && (deviceNameExtended === null || deviceNameExtended.length < 3)) {\n            const deviceNameEncoded = encodeURI(deviceName[1].replace(/\\s/g, '_'));\n            if(msg.payload != '') {\n                let payload = JSON.parse(msg.payload);\n                if(payload.hasOwnProperty(\"last_seen\")) {\n                    flow.set(\"dh*_\" + deviceNameEncoded, payload.last_seen);\n                }\n                // Save the device state to global variables\n                if(payload.hasOwnProperty(\"occupancy\")) {\n                    global.set(\"zigbee_motion_\" + deviceNameEncoded, payload.occupancy);\n                    // Also make a combined state available\n                    if(payload.occupancy == true) {\n                        global.set(\"zigbee_any_motion\", true);\n                    } else {\n                        let motion = false;\n                        for(const savedDeviceNameEncoded of global.keys()) {\n                            if(savedDeviceNameEncoded.startsWith(\"zigbee_motion_\") && global.get(savedDeviceNameEncoded) == true) {\n                                motion = true;\n                                break;\n                            }\n                        }\n                        global.set(\"zigbee_any_motion\", motion);\n                    }\n                }\n                if(payload.hasOwnProperty(\"contact\")) {\n                    global.set(\"zigbee_contact_\" + deviceNameEncoded, payload.contact);\n                }\n                global.set(\"zigbee_\" + deviceNameEncoded, payload);\n            }\n        }\n    }\n}\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\n// Settings\n// IMPORTANT: Device NAMES can't contain a forward slash \"/\"!!!\nconst defaultTimeout = 2 * 3600;     // 4 hours = 4 * 3600 seconds\nconst deviceOverrides = {\n    \"EXAMPLE Repeater - Balcony (IKEA)\": -1,    // -1 = Do not check for Device Health\n    \"EXAMPLE SillySensor\": 8 * 60 * 60,         // Checks in once every 8 hours\n    \"Entrance - Doorbell Button OLD\": -1,\n    \"Bathroom - Motion Shower\": 21 * 3600,\n    \"Undefined - Unused Sonoff Contact\": -1,\n    \"Kitchen - Fridge Temperature Top\": 8 * 3600,\n    \"Bathroom - Motion Basin\":  8 * 3600,\n    \"Kitchen - Motion (Ceiling)\":  8 * 3600,\n    // \"Curtain - Livingroom\": 24 * 3600, \n    // \"Curtain - Bedroom\": 24 * 3600,\n    // \"Button - Study Alice (Aqara)\": 4 * 3600,\n    // \"Balcony - Contact Door\": 4 * 3600,\n    // \"Study - GU10 Gledopto\": 24 * 3600,\n    // \"Livingroom - Yellow Lamp\": 24*3600,\n    \"Study - Bulb (IKEA 1 color)\": -1,\n    // \"Study - Alice Lamp (IKEA)\": -1,\n    \"Study - TuYa Motion\": -1,\n    \"AqaraCubeUnassigned2\": -1,\n    \"T&H - Study (Aqara T1)\": -1,\n    // \"Livingroom - Coffee Machine Water Leak\": -1,\n    \"Kitchen - Main Gas Valve\": 12*3600,\n    // \"Kitchen - Sink Water Sensor\": 4*3600,\n    \"Kitchen - Contact Door\": 4*3600,\n    \"Corridor - Temp & Humidity\": 7*3600,\n    // \"Entrance - Motion\": -1\n    \"Study - iHorn Motion\": -1,\n    \"StudyAqaraCube\": -1,\n}\n// -- DO NOT EDIT BELOW THIS LINE --\n// Actual code\ncontext.set(\"defaultTimeout\", defaultTimeout);\ncontext.set(\"deviceOverrides\", deviceOverrides);\nlet deviceStatus = {};\nlet deviceOffline = {};\nlet allOnline = true;\n// const now = new Date().getTime();\nconst now = new Date().getTime();\nfor(const deviceNameEncoded of flow.keys()) {\n    if(deviceNameEncoded.startsWith(\"dh*_\")) {\n        if(deviceNameEncoded.includes(\"%20\")) {\n            // This is an old and incorrect property\n            flow.set(deviceNameEncoded, undefined);\n        } else {\n            const lastSeen = flow.get(deviceNameEncoded);\n            if(lastSeen !== null) {\n                const deviceName = decodeURI(deviceNameEncoded.substring(4).replace(/_/g, ' '));\n                let timeout = defaultTimeout;\n                if(deviceOverrides.hasOwnProperty(deviceName)) timeout = deviceOverrides[deviceName];\n                if(timeout == -1) {\n                    deviceStatus[deviceName] = \"SKIPPED\";\n                } else if((now - lastSeen) < (timeout * 1000)) {\n                    deviceStatus[deviceName] = \"ONLINE\";\n                } else {\n                    deviceStatus[deviceName] = \"OFFLINE\";\n                    allOnline = false;\n                    deviceOffline[deviceName] = Math.round((now - lastSeen) / 1000);\n                }\n            }\n        }\n    }\n}\nif(allOnline) {\n    node.status({fill:\"green\",shape:\"dot\",text:\"online\"});\n} else {\n    node.status({fill:\"red\",shape:\"ring\",text:\"offline\"});\n}","finalize":"","libs":[],"x":460,"y":140,"wires":[["bd829a9c.bf1978"]]},{"id":"37778eb9.f7dc12","type":"debug","z":"1ae1a884.3eb117","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":950,"y":200,"wires":[]},{"id":"5a25e056.2c10d","type":"inject","z":"1ae1a884.3eb117","name":"Check Health Ping","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"300","crontab":"","once":true,"onceDelay":"10","topic":"PING","payload":"PING","payloadType":"str","x":160,"y":260,"wires":[["a9c0e5d5.3640a8"]]},{"id":"2e3ae0b7.cf33c","type":"inject","z":"1ae1a884.3eb117","name":"Get Health Status","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"10","topic":"STATUS","payloadType":"str","x":140,"y":220,"wires":[["a9c0e5d5.3640a8"]]},{"id":"bd829a9c.bf1978","type":"switch","z":"1ae1a884.3eb117","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"DeviceOffline","vt":"str"},{"t":"neq","v":"DeviceOffline","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":730,"y":140,"wires":[["c1ade6d8.4bf888"],["37778eb9.f7dc12","5356c370.f1d45c"]]},{"id":"814eaffd.df059","type":"function","z":"1ae1a884.3eb117","name":"Format Device Offline Message","func":"msg.topic = \"Device Offline!\";\nlet payload = \"Zigbee Device Health\\n\";\npayload +=    \"--------------------\\n\";\nconst lastSeenTime = new Date(null);\nfor(const [offlineDevice, lastSeen] of Object.entries(msg.deviceOffline)) {\n    lastSeenTime.setSeconds(lastSeen);\n    payload += offlineDevice + \": \" + lastSeenTime.toISOString().substr(11, 8) + '\\n';\n}\nmsg.payload = payload;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1270,"y":140,"wires":[["37778eb9.f7dc12","bdb48a5f.4eed18","5356c370.f1d45c"]]},{"id":"bc07260b.c393e8","type":"debug","z":"1ae1a884.3eb117","name":"MQTT debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":100,"wires":[]},{"id":"5356c370.f1d45c","type":"debug","z":"1ae1a884.3eb117","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":970,"y":240,"wires":[]},{"id":"c1ade6d8.4bf888","type":"throttle","z":"1ae1a884.3eb117","name":"Limit Offline Messages","throttleType":"time","timeLimit":"30","timeLimitType":"minutes","countLimit":0,"blockSize":0,"locked":false,"x":960,"y":140,"wires":[["37778eb9.f7dc12","814eaffd.df059","5356c370.f1d45c"]]},{"id":"756cfdc4.e690b4","type":"mqtt-broker","name":"CORE MQTT NON-CLEAN","broker":"10.10.2.1","port":"1883","clientid":"CORE-MQTT-NON-CLEAN1","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":false,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]

Do check the “On Start” tab in the function node. This is a function-based flow which has not been adapted to be shared, but maybe it’ll be useful.

2 Likes