NEWS
[Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen
-
Hallo zusammen,
ich möchte euch heute ein Projekt vorstellen, das das Thema Anwesenheitssimulation einmal anders angeht. Anstatt starrer Pläne oder durchschaubarer Zufallsschaltungen nutzt dieses Skript einen Snapshot-Ansatz, der sich vollautomatisch eurem echten Leben anpasst.
Das Konzept: Ein System, das mitdenkt
Das Skript erstellt alle 15 Minuten einen "Snapshot" eurer Lichter (oder anderer Geräte) – aber nur, wenn ihr wirklich zu Hause seid. Im Abwesenheitsmodus (Urlaub) spielt es diese Aufzeichnungen zeitversetzt wieder ab.
Die Highlights:
- Absolut wartungsfrei: Einmal eingerichtet, müsst ihr nie wieder eingreifen. Keine Sommer-/Winterzeit-Umstellung, kein Nachjustieren bei neuen Gewohnheiten.
- Ganzjährig nutzbar: Da das Skript kontinuierlich lernt und alte Snapshots nach 5 Tagen automatisch überschreibt (Ringspeicher), "weiß" es im Dezember von selbst, dass das Licht früher brennen muss als im Juli.
- Natürliches Verhalten: Es spiegelt euer echtes Leben. Werden die Tage kürzer oder ändert sich euer Rhythmus, lernt die Simulation das einfach mit.
- Intelligente Variation: Aus den Snapshots der letzten Tage wird per Zufall gewählt und mit einem "Jitter" (zufällige Verzögerung) geschaltet. Das wirkt für Außenstehende absolut menschlich.
Einrichtung & Installation
- Datenpunkte
0_userdata.0.Anwesenheit(Urlaubs-Trigger) und0_userdata.0.Anwesenheitssimulation_Snapshots(JSON-Speicher) anlegen. - Eure Geräte-IDs im
devices-Array eintragen. Fertig.
Das Skript (TypeScript):
// ===================================================================== // TITEL: Smart Presence Simulation (KI-Ghost-Mode) // // ZUSAMMENFASSUNG: // Dieses Skript simuliert Anwesenheit bei Abwesenheit/Urlaub durch das // Erlernen echter Gewohnheiten anstatt durch starre, unnatürliche Timer. // // 1. Lern-Modus (Snapshots): Bei Anwesenheit macht das Skript alle 15 // Minuten ein "Foto" der ausgewählten Lichter. Ein zufälliger Delay // (0-5 Min) vor dem Foto verhindert das starre Lernen von genauen // Uhrzeiten (z.B. pünktlich zur Tagesschau). Es merkt sich die // letzten 5 Tage. // 2. Urlaubssicher: Speichert nur Daten, wenn jemand physisch zuhause // ist. Im Urlaub friert die Datenbank ein und verblasst nicht. // 3. KI-Chaos-Faktor (Ghost-Modus): Beim Abspielen berechnet das Skript // die mathematische Ähnlichkeit der gespeicherten Fotos. Bei starren // Routinen (z.B. Wecker morgens) schaltet es hochpräzise. Bei völlig // chaotischen Routinen (z.B. Feierabend) variiert es die Schaltzeit // um bis zu 14 Minuten. // ===================================================================== // === 1. KONFIGURATION === const DP_PRESENCE: string = '0_userdata.0.Anwesenheit'; // <-- Anwesenheits-Datenpunkt (true = zuhause, false = urlaub/weg) const LIGHTS: string[] = [ 'alias.0.Licht.Flur.STATE', // <-- Trage hier alle Lampen ein, die 'alias.0.Licht.Wohnzimmer.STATE', // von der Straße aus sichtbar sind. 'alias.0.Licht.Kueche.STATE', 'alias.0.Licht.Bad.STATE' ]; // === 2. EXPERTEN-EINSTELLUNGEN === const MAX_SNAPSHOTS: number = 5; // Wie viele Anwesenheitstage pro 15-Min-Block gemerkt werden sollen const MAX_SAMPLING_DELAY: number = 300000; // Max. Verzögerung beim Lernen (5 Minuten in ms) -> Verhindert "Tagesschau-Effekt" const MAX_PLAYBACK_DELAY: number = 840000; // Max. Verzögerung beim Abspielen (14 Minuten in ms) -> Verschleiert das 15-Min-Raster // Interner Speicher (wird vom Skript automatisch angelegt) const DP_STORAGE: string = '0_userdata.0.Simulation.Snapshot_DB'; // ============================================================ // === TYPEN & INTERFACES (TypeScript Spezifisch) === // ============================================================ interface LightSnapshot { [lightId: string]: boolean; } interface SnapshotDatabase { [slotIndex: number]: LightSnapshot[]; } // ============================================================ // === AB HIER NICHTS MEHR ÄNDERN === // ============================================================ // === HILFSFUNKTIONEN === function getSlotIndex(): number { // Teilt den Tag in 96 Blöcke á 15 Minuten (z.B. 20:15 Uhr = Block 81) const now = new Date(); return Math.floor((now.getHours() * 60 + now.getMinutes()) / 15); } function getSafeBoolean(oid: string, fallback: boolean = false): boolean { if (oid && existsState(oid)) { const val = getState(oid).val; // Strikte Konvertierung in einen echten Boolean return (val === true || val === 'true' || val === 1); } return fallback; } // ========================================== // 1. LERN-MODUS (Datenbank füttern) // ========================================== function runLearning(db: SnapshotDatabase, currentSlot: number): void { let snapshot: LightSnapshot = {}; // Aktuellen Zustand aller Lampen einfrieren LIGHTS.forEach(light => { snapshot[light] = getSafeBoolean(light, false); }); if (!db[currentSlot]) db[currentSlot] = []; // Neues Foto abheften und älteste wegwerfen, wenn das Limit erreicht ist db[currentSlot].push(snapshot); if (db[currentSlot].length > MAX_SNAPSHOTS) { db[currentSlot].shift(); } // In den ioBroker Datenpunkt schreiben setState(DP_STORAGE, JSON.stringify(db), true); log(`[Simulation LERNEN] Block ${currentSlot} gespeichert. (${db[currentSlot].length}/${MAX_SNAPSHOTS} Snapshots)`, 'debug'); } // ========================================== // 2. GHOST-MODUS (Berechnen & Abspielen) // ========================================== function runSimulation(db: SnapshotDatabase, currentSlot: number): void { if (!db[currentSlot] || db[currentSlot].length === 0) { // Keine Daten für diese Uhrzeit (z.B. weil das Haus neu ist) -> Alles aus zur Sicherheit log(`[Simulation GHOST] Keine Daten für Block ${currentSlot}. Schalte Lichter aus.`, 'debug'); LIGHTS.forEach(light => { if (getSafeBoolean(light, false) === true) setState(light, false, false); }); return; } const slotSnapshots: LightSnapshot[] = db[currentSlot]; // --- DIE MAGIE: CHAOS-FAKTOR BERECHNEN --- // Wir wandeln alle Snapshots in Text um und zählen, wie viele unterschiedliche Kombinationen es gibt const uniqueCount = new Set(slotSnapshots.map(s => JSON.stringify(s))).size; const chaosFactor = uniqueCount / slotSnapshots.length; // Ergibt einen Wert zwischen >0 und 1.0 // Daraus berechnen wir die dynamische Verzögerung für exakt diese Tageszeit const dynamicMaxDelay = Math.floor(chaosFactor * MAX_PLAYBACK_DELAY); const randomDelayMs = Math.floor(Math.random() * dynamicMaxDelay); log(`[Simulation GHOST] Block ${currentSlot} | Chaos-Faktor: ${Math.round(chaosFactor*100)}% | Geplanter Delay: ${Math.round(randomDelayMs/1000)} Sekunden`, 'info'); // --- ABSPIELEN MIT BERECHNETER VERZÖGERUNG --- setTimeout(() => { // Blind ein Foto aus den gespeicherten ziehen const randomIndex = Math.floor(Math.random() * slotSnapshots.length); const chosenSnapshot = slotSnapshots[randomIndex]; LIGHTS.forEach(light => { const targetState = chosenSnapshot[light]; const currentState = getSafeBoolean(light, false); // Schont das System und den Funkverkehr: Nur schalten, wenn nötig if (targetState !== currentState) { setState(light, targetState, false); } }); }, randomDelayMs); } // === INIT & CRONJOB === createState(DP_STORAGE, JSON.stringify({}), { type: 'string', name: 'Snapshot Datenbank (JSON)', role: 'json' }, () => { // Der Cronjob feuert gnadenlos exakt alle 15 Minuten (xx:00, xx:15, xx:30, xx:45) schedule("*/15 * * * *", () => { const isPresent = getSafeBoolean(DP_PRESENCE, true); // WICHTIG: Den aktuellen Block (Slot) SOFORT festhalten, bevor das Skript eventuell wartet! const currentSlot = getSlotIndex(); // Datenbank aus dem ioBroker laden let db: SnapshotDatabase = {}; try { const raw = getState(DP_STORAGE).val as string; if (raw) db = JSON.parse(raw) as SnapshotDatabase; } catch (e) { log('[Simulation] Fehler beim Lesen der Datenbank, starte mit leerer DB neu.', 'warn'); } if (isPresent) { // ========================================== // LERN-MODUS MIT SAMPLING-VARIANZ // ========================================== // Wir warten zufällig 0 bis 5 Minuten, bevor das Foto gemacht wird. const samplingDelayMs = Math.floor(Math.random() * MAX_SAMPLING_DELAY); log(`[Simulation LERNEN] Block ${currentSlot} ausgelöst. Mache das Foto in ${Math.round(samplingDelayMs/1000)} Sekunden...`, 'debug'); setTimeout(() => { runLearning(db, currentSlot); }, samplingDelayMs); } else { // ========================================== // GHOST-MODUS (ABWESEND) // ========================================== runSimulation(db, currentSlot); } }); log('[Simulation] KI-Snapshot-Engine gestartet. (TypeScript Edition)'); });Fazit
Für mich war wichtig, dass ich mich vor dem Urlaub nicht um die Simulation kümmern muss. Durch den lernenden Charakter ist das System das ganze Jahr über "scharf" und bildet immer den aktuellen Lebensstil ab.
Ich freue mich auf euer Feedback!
-
Finde den Ansatz sehr cool und praktisch.
Werde es evtl mal testen.Im Moment basiert meine Simulation auf dem Sonnenuntergang mit einen willkürlichem Offset der plus oder minus sein kann und dann dem schalten der relevanten Lampen in einer willkürlichen Reihenfolge mit willkürlichem offset.
Das bildet natürlich keine Gewohnheiten ab. Das einzig wirklich Variable ist bei mir aber aicu nur das Klo 🚽 🪠🤣.Ich fände es praktisch, wenn man die Lampen über eine Funktion der DPs vergeben kann.
So mache ich es.