Hallo zusammen
Ich habe für das ein Script geschrieben (in Zusammenarbeit mit der Matrix
)
=> das Script wird via Advanced Flow jeden Abend ausgeführt und gibt mir den entsprechenden Output wo ich was prüfen oder Batterie wechseln sollte
- Neben der Batterie wir geprüft, wann das letzte Mal ein Wert rapportiert wurde
- Hier bin ich fan der Shelly Blu Door Window sensoren, da die Signalstärke immer wieder gemeldet wird und ich somit weis, das Gerät ist “da”
→ geht z.B. bei einfacheren Sensoren nicht, da muss ich dann halt prüfen gehen (aber immer noch besser als wenn das Gerät keine Batterie hat und eh nichts meldet) 
Nachtrag: neue Devices sind dann auch mit dabei 
Script (vereinfacht ohne meine Custom Rules)
/*
* --- Homey Device Monitor & Battery Checker v1.0 ---
* Editor: Rick_D
* * BESCHREIBUNG:
* Überwacht Homey-Geräte auf Erreichbarkeit und Batteriestand.
* Das Script erstellt eine übersichtliche Tabelle im Log und setzt Homey-Tags für Flows.
* * * VERFÜGBARE REGEL-OPTIONEN (in DEVICE_RULES):
* - notReportingDays: Erlaubte Tage ohne Meldung (überschreibt Global).
* - batteryThreshold: Batteriewarnung in % (überschreibt Global).
* - excludeBattery: (true) Batterieprüfung für dieses Gerät deaktivieren.
* - onlyCheckBattery: (true) Ignoriert Erreichbarkeit, prüft nur Batterie.
* - excludeAll: (true) Gerät komplett ignorieren.
* * * BEDEUTUNG DER REGEL-KÜRZEL (Spalte 'Rgl'):
* - ID: Treffer über die Geräte-ID (Eindeutigste Zuordnung).
* - NM: Treffer über den exakten Gerätenamen.
* - PT: Treffer über ein Regex-Muster (Pattern).
* - --: Keine Regel gefunden, globale Standardwerte werden angewendet.
*/
// --- KONFIGURATION ---
// Globale Standards (wenn keine spezifische Regel greift)
const NOT_REPORTING_THRESHOLD_HOURS = 24;
const BATTERY_THRESHOLD_PERCENT = 30;
const DEVICE_RULES = {
// BEISPIEL: Match via ID - Erlaubt längere Inaktivität (z.B. 7 Tage) & ignoriert Batterie
"id:00000000-0000-0000-0000-000000000000": {
notReportingDays: 7,
excludeBattery: true
},
// BEISPIEL: Match via Name - Niedrige Batterieschwelle (Warnung erst bei 10%)
"name:Mein Sensor im Garten": {
batteryThreshold: 10
},
// BEISPIEL: Match via ID - Nur Batterie prüfen (z.B. für Saison-Geräte)
"id:11111111-1111-1111-1111-111111111111": {
onlyCheckBattery: true
},
// BEISPIEL: Match via Pattern (Regex) - Schließt alle Geräte mit diesen Begriffen im Namen aus
"pattern:/Licht|Plug|Relay|Virtual/i": {
excludeAll: true
}
};
// Filter für den Scan
const EXCLUDED_ZONES = ['n-a']; // Zonen in Kleinbuchstaben
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_CLASS_REGEX = /sensor|button|washer_and_dryer|thermostat|remote|socket|lights|lock|other|bulb|vacuumcleaner|camera|windowcoverings/i;
// --- SKRIPTLOGIK ---
let allDevices = [];
let DevicesNotReporting = [];
let DevicesLowBattery = [];
let ExcludedDevicesAll = [];
const devices = await Homey.devices.getDevices();
const zones = await Homey.zones.getZones();
const zoneMap = Object.fromEntries(Object.values(zones).map(zone => [zone.id, zone.name]));
// Hilfsfunktionen für Formatierung
function formatDate(date) {
if (!date || isNaN(date.getTime())) return "Unbekannt";
return date.toLocaleString('de-DE');
}
function padRight(str, width) {
str = String(str);
return str.length >= width ? str.slice(0, width) : str + ' '.repeat(width - str.length);
}
function printRows(devArray) {
if (devArray.length === 0) { console.log("Keine Einträge."); return; }
let header = [
padRight('#', 3),
padRight('Name', 30),
padRight('Update', 18),
padRight('Batt', 5),
padRight('Stat', 4),
padRight('Rgl', 3),
'Geräte-ID'
].join(' ');
console.log(header);
console.log('-'.repeat(header.length + 36));
devArray.forEach((d, i) => {
console.log([
padRight(i+1, 3),
padRight(d.name, 30),
padRight(d.lastUpdated, 18),
padRight(d.batt, 5),
padRight(d.status, 4),
padRight(d.ruleApplied, 3),
d.id
].join(' '));
});
}
// Haupt-Scan
for (const device of Object.values(devices)) {
let customRule = {};
let ruleApplied = '--';
let isExcludedAll = false;
// Matching (ID -> Name -> Pattern)
if (DEVICE_RULES[`id:${device.id}`]) {
customRule = DEVICE_RULES[`id:${device.id}`];
ruleApplied = 'ID';
} else if (DEVICE_RULES[`name:${device.name}`]) {
customRule = DEVICE_RULES[`name:${device.name}`];
ruleApplied = 'NM';
} else {
for (const key in DEVICE_RULES) {
if (key.startsWith('pattern:') && new RegExp(key.substring(8)).test(device.name)) {
customRule = DEVICE_RULES[key];
ruleApplied = 'PT';
if (customRule.excludeAll) isExcludedAll = true;
break;
}
}
}
if (isExcludedAll) {
ExcludedDevicesAll.push({ name: device.name, id: device.id });
continue;
}
// Filterkriterien prüfen
if (device.driverUri && EXCLUDED_DRIVER_URI_PATTERN.test(device.driverUri)) continue;
const zoneName = device.zone ? zoneMap[device.zone] : null;
if (zoneName && EXCLUDED_ZONES.includes(zoneName.toLowerCase())) continue;
if (!device.class || !INCLUDED_DEVICE_CLASS_REGEX.test(device.class)) continue;
// Erreichbarkeit prüfen
let maxLastUpdatedTime = null;
let isReporting = true;
if (!customRule.onlyCheckBattery) {
for (const cap of Object.values(device.capabilitiesObj || {})) {
const time = new Date(cap.lastUpdated).getTime();
if (time > (maxLastUpdatedTime || 0)) maxLastUpdatedTime = time;
}
const thresholdHrs = customRule.notReportingDays ? customRule.notReportingDays * 24 : NOT_REPORTING_THRESHOLD_HOURS;
isReporting = (Date.now() - (maxLastUpdatedTime || 0)) < (thresholdHrs * 3600000);
}
// Batterie prüfen
let batteryStatus = 'N/A';
if (customRule.excludeBattery) {
batteryStatus = 'EXCL';
} else {
const measuredPct = device.capabilitiesObj?.measure_battery?.value;
const alarmBattery = device.capabilitiesObj?.alarm_battery?.value;
const threshold = customRule.batteryThreshold !== undefined ? customRule.batteryThreshold : BATTERY_THRESHOLD_PERCENT;
if (typeof measuredPct === 'number' && !Number.isNaN(measuredPct)) {
batteryStatus = `${Math.round(measuredPct)}%`;
if (measuredPct <= threshold) {
DevicesLowBattery.push({ name: device.name, status: batteryStatus, id: device.id });
}
} else if (alarmBattery) {
batteryStatus = 'ALARM';
DevicesLowBattery.push({ name: device.name, status: 'ALARM', id: device.id });
} else {
batteryStatus = device.capabilities.includes('measure_battery') ? 'OK' : 'N/A';
}
}
allDevices.push({
name: device.name,
id: device.id,
lastUpdated: maxLastUpdatedTime ? formatDate(new Date(maxLastUpdatedTime)) : 'Keine Daten',
batt: batteryStatus,
status: isReporting ? 'OK' : 'NOK',
ruleApplied: ruleApplied
});
if (!isReporting) {
DevicesNotReporting.push(`${device.name} (ID: ${device.id})`);
}
}
// --- AUSGABE ---
console.log(`\n--- MONITORING ZUSAMMENFASSUNG ---`);
console.log(`Geräte im Scan: ${allDevices.length} | Offline: ${DevicesNotReporting.length} | Batterie leer: ${DevicesLowBattery.length}`);
console.log(`----------------------------------\n`);
console.log(`[!] KRITISCH: Offline oder meldet nicht:`);
printRows(allDevices.filter(d => d.status === 'NOK'));
console.log(`\n[!] BATTERIE-WARNUNGEN:`);
if (DevicesLowBattery.length > 0) {
DevicesLowBattery.forEach((d, i) => {
console.log(`${padRight(i+1, 3)} ${padRight(d.name, 30)} Status: ${padRight(d.status, 6)} ID: ${d.id}`);
});
} else {
console.log("Keine.");
}
console.log(`\n[i] ONLINE & OK:`);
printRows(allDevices.filter(d => d.status === 'OK'));
console.log(`\n[x] IGNORIERTE GERÄTE (excludeAll via Pattern):`);
if (ExcludedDevicesAll.length > 0) {
ExcludedDevicesAll.forEach((d, i) => {
console.log(`${padRight(i+1, 3)} ${padRight(d.name, 30)} ID: ${d.id}`);
});
} else {
console.log("Keine.");
}
// Homey Tags setzen (für Flows)
await tag('notReportingCount', DevicesNotReporting.length);
await tag('lowBatteryCount', DevicesLowBattery.length);
await tag('InvalidatedDevices', DevicesNotReporting.join('\n'));
await tag('LowBatteryDevices', DevicesLowBattery.map(d => `${d.name} (${d.status})`).join('\n'));
return `Scan abgeschlossen: ${allDevices.length} Geräte geprüft.`;
Ausgabe Bsp:
könnt ihr kopieren und mit ein paar Iterationen an Eure Bedürfnisse anpassen 
→ ich habe viele custom rules (die wachsen ständig)
Flow
Der ist dann ganz einfach
→ ich arbeite mit Simple Sys Log und alles mit Notice kriege ich eine Push-Notification (normale Benachrichtigung würde natürlich auch gehen).