A script to check sensor last update

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 :wink:

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

7 Likes