As there is for some time options to see Lastseen timestamp for Zigbee devices, here is the script for Zigbee devices only, which might be more accurate for Zigbee devices. Thanks @Caseda for your overview script
Version 0.3 - updated for HomeyScript v. 3.5.1
Version 0.3b - fixed for THEN card (returning TEXT value), primary use-better use is for AND card though
Version 0.4 - added option to filter out routers and/or end devices
Version 0.5 - added option to exclude ZONES, moved variables to the top to be more user friendly
Version 0.6 - improved formatting of the output
Version 0.7 - fixes
// Version 0.7
// Script checks LastSeen property of Zigbee device
// Using 2 hours default threshold
// Thanks Caseda for [Homey Pro] overview script, which this was originally inspired by
// Thanks to ChatGPT for consultations ;-)
// Define the duration after which the sensor is considered as not responding
const NotReportingThreshold = 2; // 2 hours
// Define zones to exclude (case-insensitive match)
const EXCLUDED_ZONES = ['Bathroom', 'Main Entry', 'Living Room'];
// const EXCLUDED_ZONES = []; // Example not to Exclude any
// Define device class and name filters
const INCLUDED_DEVICE_CLASSES_REGEX = /sensor|button|remote|socket|lights/i;
const EXCLUDED_DEVICE_NAME_PATTERN = /smoke|flood|bulb|spot/i;
const INCLUDED_DEVICE_NAME_PATTERN = /.*/i; // By default nothing excluded, e.g., change to /temperature/i to include only devices with "temperature" in name
// Options to include device types
const includeEndDevices = true;
const includeRouters = true;
// -------------- don't modify anything below --------------------
const thresholdInMillis = NotReportingThreshold * 3600000; // Convert hours to milliseconds
let notReportingCount = 0;
let DevicesNotReporting = [];
// Helper function to format a date as "dd-mm-yyyy, hh:mm:ss"
function formatDate(date) {
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear().toString();
const hours = date.getHours().toString().padStart(2, '0');
const mins = date.getMinutes().toString().padStart(2, '0');
const secs = date.getSeconds().toString().padStart(2, '0');
return `${day}-${month}-${year}, ${hours}:${mins}:${secs}`;
}
// Simple pad-right function for column formatting
function padRight(str, width) {
if (str.length >= width) return str.slice(0, width);
return str + ' '.repeat(width - str.length);
}
async function checkZigbeeLastSeen() {
try {
const zonesObj = await Homey.zones.getZones();
const devicesObj = await Homey.devices.getDevices();
const allDevices = Object.values(devicesObj);
// Create a zone map
const zoneMap = {};
Object.values(zonesObj).forEach(z => { zoneMap[z.id] = z.name; });
// Get Zigbee state
const zigbeeState = await Homey.zigbee.getState();
// Arrays to hold our formatted rows
const okRows = [];
const nokRows = [];
// Separate counters for device types
let routerCount = 0;
let endDevCount = 0;
let totalCount = 0;
for (const node of Object.values(zigbeeState.nodes)) {
const typeLower = node.type?.toLowerCase() || '';
if (!includeRouters && typeLower === 'router') continue;
if (!includeEndDevices && typeLower === 'enddevice') continue;
totalCount++;
const homeyDevice = allDevices.find(d => d.name === node.name);
let zoneName = null;
if (homeyDevice && homeyDevice.zone) {
zoneName = zoneMap[homeyDevice.zone] || null;
}
// If device is in an excluded zone, skip it
if (homeyDevice && zoneName && EXCLUDED_ZONES.some(z => z.toLowerCase() === zoneName.toLowerCase())) {
continue;
}
// Apply device class and name filters
if (homeyDevice) {
const devClass = homeyDevice.class || '';
if (!INCLUDED_DEVICE_CLASSES_REGEX.test(devClass)) continue;
if (homeyDevice.name && !INCLUDED_DEVICE_NAME_PATTERN.test(homeyDevice.name)) continue;
if (EXCLUDED_DEVICE_NAME_PATTERN.test(homeyDevice.name)) continue;
}
// LastSeen check
const lastSeenDate = new Date(node.lastSeen);
const timeDiff = Date.now() - lastSeenDate.getTime();
const dateFormatted = formatDate(lastSeenDate);
let statusMark = '(OK)';
let devLabel = `${node.name} ${dateFormatted} (${typeLower})`;
if (timeDiff >= thresholdInMillis) {
statusMark = '(NOK)';
notReportingCount++;
DevicesNotReporting.push(devLabel + ' (NOK)');
}
// Count the router / enddevice if relevant
if (typeLower === 'router') routerCount++;
if (typeLower === 'enddevice') endDevCount++;
// Build row data
let rowObj = {
name : node.name || '(unknown)',
date : dateFormatted,
type : typeLower,
status : statusMark
};
// Append to OK or NOK arrays based on status
if (statusMark === '(OK)') {
okRows.push(rowObj);
} else {
nokRows.push(rowObj);
}
}
// Log summary
console.log(`${totalCount} ZigBee device(s) scanned.`);
console.log(`OK : ${okRows.length}`);
console.log(`NOK: ${nokRows.length}`);
console.log(`Router: ${routerCount}, EndDevice: ${endDevCount}`);
console.log('---------------------------------------------');
// Prepare column headers
const header = [
padRight('#', 3),
padRight('Device Name', 35),
padRight('Last Seen', 20),
padRight('Type', 10),
padRight('Status', 6)
].join(' ');
// Print function for rows
function printRows(rowArr) {
rowArr.forEach((row, idx) => {
let out = [
padRight(String(idx + 1), 3),
padRight(row.name, 35),
padRight(row.date, 20),
padRight(row.type, 10),
padRight(row.status, 6)
].join(' ');
console.log(out);
});
}
// Print OK devices
if (okRows.length > 0) {
console.log(`\nOK ZigBee device(s): ${okRows.length}`);
console.log(header);
console.log('-'.repeat(header.length));
printRows(okRows);
}
// Print NOK devices
if (nokRows.length > 0) {
console.log(`\nNOK ZigBee device(s): ${nokRows.length}`);
console.log(header);
console.log('-'.repeat(header.length));
printRows(nokRows);
}
console.log('---------------------------------------------\n');
// Set tags
await tag('InvalidatedDevices', DevicesNotReporting.join('\n'));
await tag('notReportingCount', notReportingCount);
// Final script return
const myTag = `Not Reporting Count: ${notReportingCount}\nDevices Not Reporting:\n${DevicesNotReporting.join('\n')}`;
return myTag;
} catch (error) {
console.error('Failed: Getting ZigBee state', error);
await tag('InvalidatedDevices', '');
await tag('notReportingCount', -1);
return 'Error in retrieving ZigBee state';
}
}
const myTag = await checkZigbeeLastSeen();
return myTag;
Example for 2 hours threshold :
All device types version : A script to check sensor last update - #25 by Sharkys