<nitpicking mode>
If you like, you can use it without the homeyscript tag, with the THEN card âRun code with Argument and return Text-tagâ.
You can adjust the threshold value a bit easier, or even use a variable for that,
//GetDevicesNotReporting with argument [time in seconds]
const NotReportingThreshold = args[0]; // Trigger when not reported in the last x secs
//await tag('DevicesNotReporting', DevicesNotReporting.join(',\n'));
return (DevicesNotReporting.join(',\n'));
Peter what about this one, seems to be working better for me ⌠it checks now if ANY capability has been updated and seems the time-date error is gone for me and the results are much more relevant.
@JeeHaa maybe it will help also with lights problem you mentioned but not sure how it behaves if user actually change the capability himself (so not the device by user/app etc.)
I am pretty sure the returned values are correct, some devices just donât update as frequently on Homey.
However, I found a workaround to âpollâ Lights: just send an On/Off command that is identical to its current state + using a Try/Catch. Effectively nothing changes, but the lastUpdated time gets updated.
//GetDevicesNotReporting
let NotReportingThreshold = 60;
if (args[0]){ let NotReportingThreshold = args[0] }
let DevicesNotReporting = [];
let devices = await Homey.devices.getDevices();
for (const device of Object.values(devices)) {
// Filter devices to check
//if (device.driverUri.match('vdevice')) continue; // Exclude Virtual Devices
if (!device.name.match(/lamp|^pc|plug/i)) continue; // Include by device name
//if (!device.class.match('light|socket')) continue; // Include device types
//if (device.class.match('sensor|button|remote|socket')) continue; // Exclude device types
//if (!device.name.match('Plug JH')) continue; // Include by device name
// Any capability updated recently? Set LastSeen to most recent update.
IsReporting = false
LastSeen = 1000000
for (const capabilityObj of Object.values(device.capabilitiesObj)) {
let lastUpdated = new Date(capabilityObj.lastUpdated)
let timeSinceReport = Math.floor((Date.now() - lastUpdated)/1000/60) // Minutes since last update
if (timeSinceReport < LastSeen){LastSeen = timeSinceReport }
//log('LastUpdated: '+ device.name + ' ' + capabilityObj.id + ' ' + timeSinceReport) + 'min')
if (timeSinceReport < (NotReportingThreshold)) { IsReporting = true }
}
// Add to list if not reported
//DevicesNotReporting.push(device.name + ' [' + LastSeen + 'm]');
if (!IsReporting) {
// Try to send command to wake up device if supported
if (device.class === 'light' || device.virtualClass === 'light') {
try {
log('WAKE: ' + device.name + ' - ' + device.class + ' - ' + device.virtualClass + ' [' + LastSeen + 'm]')
await device.setCapabilityValue('onoff', device.capabilitiesObj['onoff'].value)
} catch {
DevicesNotReporting.push(device.name + ' [' + LastSeen + 'm]');
log('FAIL WAKE: ' + device.name + ' - ' + device.class + ' - ' + device.virtualClass + ' [' + LastSeen + 'm]')
}
} else {
DevicesNotReporting.push(device.name + ' [' + LastSeen + 'm]');
log('NOK: ' + device.name + ' - ' + device.class + ' - ' + device.virtualClass + ' [' + LastSeen + 'm]')
}
} else {
log('OK: ' + device.name + ' - ' + device.class + ' - ' + device.virtualClass + ' [' + LastSeen + 'm]')
}
}
// Output for script AND card
await tag('DevicesNotReporting', DevicesNotReporting.join(', '));
return DevicesNotReporting.length != 0;
//return DevicesNotReporting
This works fine for disconnected lights ("Catchâ gets executed as expected), but unfortunately my Tuya Plugs give an unexpected result. If a plug is removed from the wall socket this command still succeeds after 5s, and the LastUpdated time even updates (reported to Athom).
Can anyone test with their plugs if they throw an error as expected when disconnected from power?
This might help you in your endeavor - Iâve had good experience with the a script I found some time ago: https://homey.solweb.no/battery/battery (not mine).
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
// Version 0.3b
// Script checks devices based on the last updated time against a threshold
// Focuses on temperature sensors and specified device classes
const NotReportingThreshold = 0.8; // 0.8 hours, which is 48 minutes
const thresholdInMillis = NotReportingThreshold * 3600000; // Convert hours to milliseconds
let DevicesNotReporting = [];
let devices = await Homey.devices.getDevices();
let notReportingCount = 0; // Variable to keep track of the count
// Function to format date as "dd-mm-yyyy, hh:mm:ss"
function formatDate(date) {
if (!date) return "Unknown"; // Return 'Unknown' if date is null or invalid
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are 0-indexed
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}`;
}
for (const device of Object.values(devices)) {
// Exclude Virtual Devices and specific apps
if (device.driverUri && device.driverUri.match('vdevice|nl.qluster-it.DeviceCapabilities|nl.fellownet.chronograph|net.i-dev.betterlogic|com.swttt.devicegroups|com.gruijter.callmebot|com.netscan')) continue;
// Include by regex device name, focusing on temperature-related devices
if (!device.name || !device.name.match(/temperature/i)) continue;
// Exclude devices based on specific name patterns (if necessary, uncomment and modify)
// if (device.name.match(/Flood|TVOC|Netatmo Rain|Motion|Flora|Rear gate Vibration Sensor|Vibration Sensor Attic Doors/i)) continue;
// Include devices based on device class
if (!device.class || !device.class.match(/sensor|button|remote|socket|lights/i)) continue;
let IsReporting = false;
let lastUpdated = null;
for (const capability of Object.values(device.capabilitiesObj)) {
if (!capability.lastUpdated) continue; // Skip capabilities without lastUpdated info
let timeSinceReport = Date.now() - new Date(capability.lastUpdated).getTime();
if (timeSinceReport < thresholdInMillis) {
IsReporting = true;
break;
}
// Track the most recent update
if (!lastUpdated || new Date(capability.lastUpdated) > new Date(lastUpdated)) {
lastUpdated = new Date(capability.lastUpdated);
}
}
if (!IsReporting) {
let deviceInfo = `${device.name} (${formatDate(lastUpdated)} - ${device.class}) (NOK)`;
DevicesNotReporting.push(deviceInfo);
console.log('NOK:', deviceInfo);
// Increment the count
notReportingCount++;
} else {
let deviceInfo = `${device.name} (${formatDate(lastUpdated)} - ${device.class}) (OK)`;
console.log('OK:', deviceInfo);
}
}
// 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 the defined value
return myTag;
Hi, how do you mean and do this?
Can I use it to pull more updates out of a sensor?
I have the aware humidity and temp sensor. It doesnât update a lot. And am triggering my shower ventilator with this moist measurement. If it takes too long, it doesnât work correctly.
Sorry, in general You can NOT pull more updates from sensor - most radio protocols on bat-powered devices save power and switch radio off. That means, the device is not listening radio traffic unless âwake up periodsâ or if there is something important to say ( for example: âHi! i detected water here!â )
Yes, of course You can play with reporting intervals and also with threshholds on sensor - but this may reduce baterry life ( if bat. powered device ).
From my personal experience - also attempted first to check room moisture. Nice to have, but to slow and data to disperced ( for my case there is difference in moisture during summer and winter - but not only ) So⌠i just bougth a water leakage sensor and put it under the shower. Not disturbing at all - a small box hanging directly on wall under the shower head, around 5 centimeters abowe the floor. NB! there may be smart to put behind little bit more 2-sided tape, because there may be water staying between wall and sensor ( which one holds alarm on). And actually i use also timer in central box - the water alarm switch vent on & starts 1.5h timer. If timer reaches back to zero, then vent is switched off.
Ok, there are more whistles also⌠âhardwareâ switch on also sets countdown⌠etc.
For the lines with the filters, I use these for example. Maybe it makes a bit more sense to you.
// Exclude Virtual Devices and apps:
if (device.driverUri.match('vdevice|nl.qluster-it.DeviceCapabilities|nl.fellownet.chronograph|net.i-dev.betterlogic|com.swttt.devicegroups|com.gruijter.callmebot|com.netscan' )) continue;
// Exclude by (parts of) device name
if (device.name.match('^se|^pr|TST|z_|z |z. |IR|Arri|Rebo|rada|KNM|Timeline')) continue;
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.4
// 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 ;-)
const NotReportingThreshold = 2; // 2 hours
const thresholdInMillis = NotReportingThreshold * 3600000; // convert hours to milliseconds
const includeEndDevices = true; // Option to include end devices
const includeRouters = true; // Option to include router devices
let ZigbeeDevicesLastSeen = [];
let notReportingCount = 0;
let DevicesNotReporting = [];
// 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'); // Months are 0-indexed
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}`;
}
// Function to check last seen state of Zigbee devices
async function checkZigbeeLastSeen() {
try {
const result = await Homey.zigbee.getState();
const okDevices = [];
const nokDevices = [];
const routerDevices = [];
const endDevices = [];
for (const device of Object.values(result.nodes)) {
// Filter devices to check
if (device.class && !device.class.match(/sensor|button|remote|socket|lights/i)) continue; // Include device types
if (device.name.match(/dimmer|light|bulb|spot/i)) continue; // Exclude by regex device name
// Include devices based on type
if (!includeRouters && device.type.toLowerCase() === 'router') continue;
if (!includeEndDevices && device.type.toLowerCase() === 'enddevice') continue;
// Include last seen information
const lastSeenDate = new Date(device.lastSeen);
const lastSeenFormatted = formatDate(lastSeenDate);
// Check against NotReportingThreshold
const currentTime = Date.now();
const timeSinceLastSeen = currentTime - lastSeenDate.getTime();
let deviceName = device.name + ' ' + lastSeenFormatted + ' (' + device.type.toLowerCase() + ')';
if (timeSinceLastSeen >= thresholdInMillis) {
deviceName += ' (NOK)';
nokDevices.push(deviceName);
notReportingCount++; // Increment the count of not reporting devices
DevicesNotReporting.push(deviceName); // Add the device to the list of not reporting devices
} else {
deviceName += ' (OK)';
okDevices.push(deviceName);
}
if (device.type.toLowerCase() === 'router') routerDevices.push(deviceName);
if (device.type.toLowerCase() === 'enddevice') endDevices.push(deviceName);
}
// Log Zigbee devices and their types
console.log(okDevices.length, 'OK Zigbee devices', '(' + routerDevices.length + ' Router, ' + endDevices.length + ' End device based on selection)');
console.log(nokDevices.length, 'NOK Zigbee devices');
console.log('---------------------------------------------');
console.log('OK ZigBee device(s):');
console.log(okDevices.join('\r\n'));
console.log('---------------------------------------------');
console.log('NOK ZigBee device(s):');
console.log(nokDevices.join('\r\n'));
console.log('---------------------------------------------');
// 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 the defined value
return myTag;
} catch (error) {
console.error('Failed: Getting ZigBee state', error);
// Output for script AND card
await tag('InvalidatedDevices', '');
await tag('notReportingCount', -1); // Set to -1 to indicate an error
// Define a return value for error state
const myTag = `Error in retrieving ZigBee state`;
// Return the defined value
return myTag;
}
}
// Execute the Zigbee check function
const myTag = await checkZigbeeLastSeen();
// Return the defined value
return myTag;