A script to check sensor last update

Updated script I posted above - added counter of non responding devices - eg, in case you would like to initiate some action based on number of not-responding devices - please note this is the version I’m using, so feel free to change relevant variables below the section // Filter devices to check

Changelog :
0.1 - update - converting notReportingCount to a number - if you used previous version of script, delete tag by await tag(‘notReportingCount’, null);*
0.2 - added filtering based on device app type - thanks @Peter_Kawa*
0.3 - update for new HomeyScript - compatible now only with HomeyScript v. 3.5.1 and later
0.3b - fixed for THEN card (returning TEXT value), primary use-better use is for AND card though
0.4 - added option to exclude ZONEs
0.5 - better output formatting and fixes date times sometimes N/A
0.6 - added ability to exclude ZWave/Zigbee devices or all others

// Version 0.6
// Script checks devices based on the last updated time against a threshold
// Focuses on temperature sensors and specified device classes but you can adjust it by changing FILTER OPTIONS

// Constants
const NOT_REPORTING_THRESHOLD_HOURS = 0.8; // 0.8 hours, which is 48 minutes
const THRESHOLD_IN_MILLIS = NOT_REPORTING_THRESHOLD_HOURS * 3600000; // Convert hours to milliseconds

// Filter Options
const EXCLUDED_ZONES = ['Garage', 'Living Room']; // Add your zone names here (case-insensitive) to be  excluded
const EXCLUDED_DRIVER_URI_PATTERN = /vdevice|nl\.qluster-it\.DeviceCapabilities|nl\.fellownet\.chronograph|net\.i-dev\.betterlogic|com\.swttt\.devicegroups|com\.gruijter\.callmebot|com\.netscan/i;
const INCLUDED_DEVICE_NAME_REGEX = /.*/i; // Device names (case-insensitive) to be included
// EXAMPLE to include ALL : const INCLUDED_DEVICE_NAME_REGEX = /.*/i;
const EXCLUDED_DEVICE_NAME_PATTERN = /Flood|Netatmo Rain|Motion|Flora|Rear gate Vibration Sensor|Vibration Sensor Attic Doors/i;  // Device names (case-insensitive) to be excluded
const INCLUDED_DEVICE_CLASS_REGEX = /sensor|button|remote|socket|lights|bulb/i; // Device types/classes (case-insensitive) to be included
const EXCLUDED_FLAGS = ['']; // eg. to exclude ZWAVE and ZIGBEE - const EXCLUDED_FLAGS = ['zigbee','zwave'];. You can also set lowbattery, to exclude those devices with low battery state
const EXCLUDE_EMPTY_FLAGS = false; // Set to true to exclude devices with empty flags (eg. OTHER then Zigbee / ZWAVE devices)

// -------------- don't modify anything below --------------------

// Prepare tracking arrays
let okDevices = [];
let nokDevices = [];

// Overall tracking
let DevicesNotReporting = [];
let notReportingCount = 0;

// Fetch all devices and zones
const devices = await Homey.devices.getDevices();
const zones = await Homey.zones.getZones();
const zonesArray = Array.isArray(zones) ? zones : Object.values(zones);

// Create a map of zone ID to zone name
const zoneMap = {};
zonesArray.forEach(zone => {
  zoneMap[zone.id] = zone.name;
});

// Function to format date as "dd-mm-yyyy, hh:mm:ss"
function formatDate(date) {
  if (!date || isNaN(date.getTime())) return "Unknown";
  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 minutes = date.getMinutes().toString().padStart(2, '0');
  const seconds = date.getSeconds().toString().padStart(2, '0');
  return `${day}-${month}-${year}, ${hours}:${minutes}:${seconds}`;
}

// Simple right-padding function for column formatting
function padRight(str, width) {
  if (str.length >= width) return str.slice(0, width);
  return str + ' '.repeat(width - str.length);
}

// For each device, apply filters and check lastUpdated times
for (const device of Object.values(devices)) {
  // 1) Exclude certain driver URIs (virtual devices, app placeholders, etc.)
  if (device.driverUri && EXCLUDED_DRIVER_URI_PATTERN.test(device.driverUri)) continue;

  // 2) Zone checks
  const zoneName = device.zone ? zoneMap[device.zone] : null;
  if (zoneName && EXCLUDED_ZONES.some(z => z.toLowerCase() === zoneName.toLowerCase())) {
    continue;
  }

  // 3) Name/class filters
  if (!device.name || !INCLUDED_DEVICE_NAME_REGEX.test(device.name)) continue;
  if (EXCLUDED_DEVICE_NAME_PATTERN.test(device.name)) continue;
  if (!device.class || !INCLUDED_DEVICE_CLASS_REGEX.test(device.class)) continue;

  // 4) Exclude based on technology
  if (
  (device.flags && EXCLUDED_FLAGS.some(flag => device.flags.includes(flag))) ||
  (EXCLUDE_EMPTY_FLAGS && Array.isArray(device.flags) && (device.flags.length === 0 || device.flags.includes('lowBattery'))) ) continue;

  // We'll track the most recent (max) lastUpdated across all capabilities
  let maxLastUpdatedTime = null;

  // Gather the latest lastUpdated
  if (device.capabilitiesObj) {
    for (const capability of Object.values(device.capabilitiesObj)) {
      if (!capability.lastUpdated) continue;
      const capTime = new Date(capability.lastUpdated).getTime();
      if (!maxLastUpdatedTime || capTime > maxLastUpdatedTime) {
        maxLastUpdatedTime = capTime;
      }
    }
  }

  // Now decide if the device is reporting or not
  let isReporting = false;
  if (maxLastUpdatedTime) {
    const timeSinceLastUpdated = Date.now() - maxLastUpdatedTime;
    // If the device's lastUpdated is within threshold, it's reporting
    if (timeSinceLastUpdated < THRESHOLD_IN_MILLIS) {
      isReporting = true;
    }
  }

  // Format the date from the maximum lastUpdated we found
  const lastUpdatedDate = maxLastUpdatedTime ? new Date(maxLastUpdatedTime) : null;
  const deviceInfo = {
    name: device.name,
    formattedDate: formatDate(lastUpdatedDate),
    class: device.class,
    status: isReporting ? '(OK)' : '(NOK)'
  };

  // If not reporting
  if (!isReporting) {
    notReportingCount++;
    DevicesNotReporting.push(`${deviceInfo.name} (${deviceInfo.formattedDate} - ${deviceInfo.class}) (NOK)`);
    nokDevices.push(deviceInfo);
  } else {
    okDevices.push(deviceInfo);
  }
}

// Log results in columns
const totalDevices = okDevices.length + nokDevices.length;
console.log(`${totalDevices} device(s) scanned.`);
console.log(`OK:  ${okDevices.length}`);
console.log(`NOK: ${nokDevices.length}`);
console.log('---------------------------------------------');

// Prepare column headers
const header = [
  padRight('#', 4),
  padRight('Device Name', 35),
  padRight('Last Updated', 20),
  padRight('Class', 10),
  padRight('Status', 6)
].join(' ');

// Helper to print rows in columns
function printRows(devArray) {
  devArray.forEach((row, idx) => {
    const line = [
      padRight(String(idx + 1), 4),
      padRight(row.name, 35),
      padRight(row.formattedDate, 20),
      padRight(row.class, 10),
      padRight(row.status, 6)
    ].join(' ');
    console.log(line);
  });
}

// Print OK devices
if (okDevices.length > 0) {
  console.log(`\nOK device(s): ${okDevices.length}`);
  console.log(header);
  console.log('-'.repeat(header.length));
  printRows(okDevices);
}

// Print NOK devices
if (nokDevices.length > 0) {
  console.log(`\nNOK device(s): ${nokDevices.length}`);
  console.log(header);
  console.log('-'.repeat(header.length));
  printRows(nokDevices);
}

console.log('---------------------------------------------\n');

// Output for script AND card
await tag('InvalidatedDevices', DevicesNotReporting.join('\n'));
await tag('notReportingCount', notReportingCount);

// Define a return value
const myTag = `Not Reporting Count: ${notReportingCount}\nDevices Not Reporting:\n${DevicesNotReporting.join('\n')}`;
return myTag;


Zigbee ONLY version : A script to check sensor last update - #39 by Sharkys

3 Likes