Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. [Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen

NEWS

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    431

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    10
    1
    358

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    959

[Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
javascripttemplatesecurity
21 Beiträge 7 Kommentatoren 752 Aufrufe 15 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • M Offline
    M Offline
    mrMuppet
    schrieb am zuletzt editiert von mrMuppet
    #3

    Hier ist ein kleines Update:

    1. Enum-/Aufzählungs-Support: Du kannst Lampen jetzt optional über eine ioBroker-Funktion (z.B. enum.functions.anwesenheitssimulation) verwalten. Das Skript kombiniert die Einträge aus der Liste LIGHTS und dem Enum automatisch. Du kannst sogar ganze Kanäle in das Enum ziehen – das Skript sucht sich den richtigen Datenpunkt (.STATE oder .ON) selbst.
    2. Helligkeits-Tracking: Das Skript sucht automatisch nach passenden Helligkeits-Datenpunkten im selben Kanal (z.B. .LEVEL, .ACTUAL, .BRIGHTNESS) und speichert den Dimmwert ab.
    3. Auto-Migration: Falls du das Skript startest und noch alte "Foto"-Daten in der Datenbank hast (die nur An/Aus kannten), stürzt das Skript nicht ab, sondern verarbeitet diese nahtlos weiter, bis sie nach 5 Tagen aus dem Gedächtnis rotieren.

    // =====================================================================
    // TITEL: Smart Presence Simulation (KI-Ghost-Mode) - v2
    // =====================================================================
    
    // === 1. KONFIGURATION ===
    const DP_PRESENCE: string = '0_userdata.0.Anwesenheit'; 
    
    // NEU: Optional Lampen über ioBroker "Aufzählungen" (Enums) laden. 
    // Leer lassen (''), falls nicht gewünscht.
    const ENUM_FUNKTION: string = 'enum.functions.anwesenheitssimulation'; 
    
    // Hier können Lampen weiterhin manuell eingetragen werden (Kombination mit Enum ist möglich)
    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;           
    const MAX_SAMPLING_DELAY: number = 300000; 
    const MAX_PLAYBACK_DELAY: number = 840000; 
    
    const DP_STORAGE: string = '0_userdata.0.Simulation.Snapshot_DB';
    
    // ============================================================
    // === TYPEN & INTERFACES ===
    // ============================================================
    interface LightStateData {
        on: boolean;
        bri?: number; // Optionaler Helligkeitswert
    }
    
    interface LightSnapshot {
        // Unterstützt sowohl das neue Format (LightStateData) als auch das alte (boolean) für reibungslose Migration
        [baseId: string]: LightStateData | boolean; 
    }
    
    interface SnapshotDatabase {
        [slotIndex: number]: LightSnapshot[];
    }
    
    interface ParsedLight {
        baseId: string;
        powerId: string;
        briId: string | null;
    }
    
    // ============================================================
    // === HILFSFUNKTIONEN ===
    // ============================================================
    function getSlotIndex(): number {
        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;
            return (val === true || val === 'true' || val === 1);
        }
        return fallback;
    }
    
    // NEU: Sammelt dynamisch alle Lampen aus dem Array und dem Enum
    function getActiveLights(): ParsedLight[] {
        let rawIds: string[] = [...LIGHTS];
        
        if (ENUM_FUNKTION && existsObject(ENUM_FUNKTION)) {
            const enumObj = getObject(ENUM_FUNKTION);
            if (enumObj && enumObj.common && enumObj.common.members) {
                rawIds = rawIds.concat(enumObj.common.members);
            }
        }
        
        // Duplikate entfernen
        rawIds = [...new Set(rawIds)];
        
        const result: ParsedLight[] = [];
        
        for (const id of rawIds) {
            if (!existsObject(id)) continue;
            
            let powerId = id;
            const obj = getObject(id);
            
            // Falls der Nutzer einen ganzen Kanal ins Enum gezogen hat, suchen wir den STATE
            if (obj && obj.type === 'channel') {
                if (existsState(`${id}.STATE`)) powerId = `${id}.STATE`;
                else if (existsState(`${id}.ON`)) powerId = `${id}.ON`;
            }
    
            // Helligkeits-Datenpunkt dynamisch im selben Kanal suchen
            const parts = powerId.split('.');
            parts.pop(); 
            const channel = parts.join('.');
            
            let briId: string | null = null;
            const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'DIMMER', 'SET'];
            
            for (const candidate of briCandidates) {
                if (existsState(`${channel}.${candidate}`)) {
                    briId = `${channel}.${candidate}`;
                    break;
                }
            }
            
            result.push({ baseId: powerId, powerId, briId });
        }
        
        return result;
    }
    
    // ==========================================
    // 1. LERN-MODUS (Datenbank füttern)
    // ==========================================
    function runLearning(db: SnapshotDatabase, currentSlot: number): void {
        let snapshot: LightSnapshot = {};
        const activeLights = getActiveLights();
        
        // Aktuellen Zustand inkl. Helligkeit aller Lampen einfrieren
        for (const light of activeLights) {
            const isOn = getSafeBoolean(light.powerId, false);
            const stateData: LightStateData = { on: isOn };
            
            if (isOn && light.briId && existsState(light.briId)) {
                const briVal = getState(light.briId).val;
                if (typeof briVal === 'number') stateData.bri = briVal;
            }
            
            snapshot[light.baseId] = stateData;
        }
    
        if (!db[currentSlot]) db[currentSlot] = [];
        
        db[currentSlot].push(snapshot);
        if (db[currentSlot].length > MAX_SNAPSHOTS) {
            db[currentSlot].shift();
        }
    
        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 {
        const activeLights = getActiveLights();
    
        if (!db[currentSlot] || db[currentSlot].length === 0) {
            log(`[Simulation GHOST] Keine Daten für Block ${currentSlot}. Schalte Lichter aus.`, 'debug');
            for (const light of activeLights) {
                if (getSafeBoolean(light.powerId, false) === true) setState(light.powerId, false, false);
            }
            return;
        }
    
        const slotSnapshots: LightSnapshot[] = db[currentSlot];
        
        const uniqueCount = new Set(slotSnapshots.map(s => JSON.stringify(s))).size;
        const chaosFactor = uniqueCount / slotSnapshots.length; 
        
        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)}s`, 'info');
    
        // --- ABSPIELEN MIT BERECHNETER VERZÖGERUNG ---
        setTimeout(() => {
            const randomIndex = Math.floor(Math.random() * slotSnapshots.length);
            const chosenSnapshot = slotSnapshots[randomIndex];
            
            for (const light of activeLights) {
                const targetRaw = chosenSnapshot[light.baseId];
                if (targetRaw === undefined) continue;
    
                // Migration: Kompatibilität zu alten Boolean-Werten aus V1
                let targetOn = false;
                let targetBri: number | undefined = undefined;
                
                if (typeof targetRaw === 'boolean') {
                    targetOn = targetRaw;
                } else {
                    targetOn = targetRaw.on;
                    targetBri = targetRaw.bri;
                }
    
                const currentOn = getSafeBoolean(light.powerId, false);
                
                if (targetOn) {
                    // Wenn Lampe an sein soll: Zuerst (optional) dimmen, dann einschalten
                    if (targetBri !== undefined && light.briId) {
                        const currentBri = getState(light.briId).val;
                        if (currentBri !== targetBri) setState(light.briId, targetBri, false);
                    }
                    if (!currentOn) setState(light.powerId, true, false);
                } else {
                    // Wenn Lampe aus sein soll
                    if (currentOn) setState(light.powerId, false, false);
                }
            }
        }, randomDelayMs);
    }
    
    // === INIT & CRONJOB ===
    createState(DP_STORAGE, JSON.stringify({}), { type: 'string', name: 'Snapshot Datenbank (JSON)', role: 'json' }, () => {
        
        schedule("*/15 * * * *", () => {
            const isPresent = getSafeBoolean(DP_PRESENCE, true);
            const currentSlot = getSlotIndex(); 
            
            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) {
                const samplingDelayMs = Math.floor(Math.random() * MAX_SAMPLING_DELAY); 
                log(`[Simulation LERNEN] Block ${currentSlot} ausgelöst. Mache das Foto in ${Math.round(samplingDelayMs/1000)}s...`, 'debug');
                
                setTimeout(() => {
                    runLearning(db, currentSlot);
                }, samplingDelayMs);
    
            } else {
                runSimulation(db, currentSlot);
            }
        });
    
        log('[Simulation] KI-Snapshot-Engine gestartet. (V2: Enums & Helligkeit integriert)');
    });
    
    

    ioBroker auf NUC (Celeron mit Ubuntu-Server)

    Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

    1 Antwort Letzte Antwort
    2
    • C Online
      C Online
      Chrille1507
      schrieb am zuletzt editiert von
      #4

      Hallo,
      eine tolle Idee, die gleich mal testen werde.

      Eine kurze Frage, sind

      "0_userdata.0.Anwesenheitssimulation_Snapshots" und "0_userdata.0.Simulation.Snapshot_DB" nicht die gleichen Datenpunkte?

      M 1 Antwort Letzte Antwort
      0
      • M mrMuppet

        Update V13.1

        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.

        Neu in Version 13.1:

        • Rolläden werden jetzt mit simuliert: Das Skript fotografiert jetzt nicht mehr nur eure Lampen, sondern (wenn ihr wollt) auch eure Rolläden/Jalousien. Tragt die entsprechenden Datenpunkte einfach ins neue BLINDS Array ein. Der Ghost-Mode spielt dann nicht nur das Licht, sondern auch eure typischen Rolladen-Bewegungen ab.
        • Intelligenter Blickschutz (Strom sparen): Abends und nachts schaltet die Simulation jetzt das Licht in allen konfigurierten Räumen aus und pausiert, sobald alle konfigurierten Rolläden geschlossen sind. Niemand auf der Straße sieht euer Licht mehr, also müssen wir auch keinen Strom verschwenden.
        • Power-First Logik (Für smarte Birnen an dummen Relais): Wer smarte Birnen (Hue, Ikea, etc.) hat, die physisch an einem Relais/Aktor hängen, kennt das Problem: Man kann sie nicht dimmen, wenn sie keinen Strom haben. Dafür gibt es jetzt die POWER_FIRST_MAP.

        So tragt ihr es ein: Ihr schreibt links die ID eurer smarten Birne und rechts die ID des Relais, das ihr den Strom gibt (Format: 'Smarte_Birne' : 'Relais_ID'). Das Skript schaltet dann zuerst den Strom ein, wartet 2 Sekunden, bis die smarte Birne hochgefahren und im WLAN angemeldet ist, und sendet erst dann den Dimm-Befehl.

        • Die Logik-Sperre (Kein Quatsch mehr): Das Skript filtert nun unlogische Snapshots automatisch heraus. Es ist jetzt so schlau, dass es abends/nachts niemals einen Rolladen wieder hochfährt, der bereits geschlossen ist (egal, was in der Datenbank steht). Und morgens/vormittags wird kein Rolladen mehr geschlossen, der bereits offen ist.

        Neu in V2:

        • Enum-/Aufzählungs-Support: Du kannst Lampen jetzt optional über eine ioBroker-Funktion (z.B. enum.functions.anwesenheitssimulation) verwalten. Das Skript kombiniert die Einträge aus der Liste LIGHTS und dem Enum automatisch. Du kannst sogar ganze Kanäle in das Enum ziehen – das Skript sucht sich den richtigen Datenpunkt (.STATE oder .ON) selbst.
        • Helligkeits-Tracking: Das Skript sucht automatisch nach passenden Helligkeits-Datenpunkten im selben Kanal (z.B. .LEVEL, .ACTUAL, .BRIGHTNESS) und speichert den Dimmwert ab.
        • Auto-Migration: Falls du das Skript startest und noch alte "Foto"-Daten in der Datenbank hast (die nur An/Aus kannten), stürzt das Skript nicht ab, sondern verarbeitet diese nahtlos weiter, bis sie nach 5 Tagen aus dem Gedächtnis rotieren.

        Einrichtung & Installation

        1. Datenpunkte 0_userdata.0.Anwesenheit (Urlaubs-Trigger) und 0_userdata.0.Anwesenheitssimulation_Snapshots_DB (JSON-Speicher) anlegen.
        2. Eure Geräte-IDs im devices-Array eintragen. Fertig.

        Das Skript (TypeScript):

        // INKL. DYNAMISCHEM HARDWARE-LOCK, BLICKSCHUTZ & POWER-FIRST MAP
        // CHANGELOG v13.1: 
        // - FIX: Edge-Case im Power-First (Dimmen, wenn Relais bereits an) behoben.
        // - FIX: Slot-Index wird nun vor dem Delay eingefroren (verhindert Slot-Sprünge).
        // =====================================================================
        
        // === 1. KONFIGURATION ===
        
        // Datenpunkt für Anwesenheit (true = da, false = Ghost-Mode aktiv)
        const DP_PRESENCE: string = '0_userdata.0.Heizung.Anwesenheit'; 
        
        // Optionale ioBroker Aufzählung (Enum). Leer lassen (''), falls nicht genutzt.
        const ENUM_FUNKTION: string = 'enum.functions.anwesenheitssimulation'; 
        
        // Manuelle Lampenliste (nur direkte .STATE oder .ON Datenpunkte)
        const LIGHTS: string[] = [
            // 'alias.0.Licht.Essbereich.STATE', 
            // 'alias.0.Licht.Kueche.STATE'
        ];
        
        // Mapping für Power-First Lampen!
        // Format: 'ID_der_smarten_Lampe' : 'ID_des_dummen_Strom_Relais'
        // Wenn die smarte Lampe simuliert wird, schaltet das Skript zuerst das Relais, wartet 2s, und dimmt dann.
        const POWER_FIRST_MAP: Record<string, string> = {
            // Beispiel:
            // 'alias.0.Licht.Wohnzimmer_Smarte_Birne.STATE': 'alias.0.Licht.Wohnzimmer_Relais.STATE'
        };
        
        // Rolläden für die Blickschutz-Logik (Zustand meist 0-100%).
        const BLINDS: string[] = [
            // 'alias.0.Beschattung.Kueche.Rolladen_Links.LEVEL'
        ];
        
        
        // === 2. EXPERTEN-EINSTELLUNGEN ===
        const MAX_SNAPSHOTS: number = 5;           
        const MAX_SAMPLING_DELAY: number = 300000; // Bis zu 5 Min Verzögerung beim Lernen
        const MAX_PLAYBACK_DELAY: number = 840000; // Bis zu 14 Min Verzögerung beim Abspielen
        const DP_STORAGE: string = '0_userdata.0.Simulation.Snapshot_DB';
        
        // ============================================================
        // === TYPEN & INTERFACES ===
        // ============================================================
        interface DeviceStateData { on?: boolean; bri?: number; level?: number; }
        interface Snapshot { [baseId: string]: DeviceStateData | boolean; }
        interface SnapshotDatabase { [slotIndex: number]: Snapshot[]; }
        
        // ============================================================
        // === HILFSFUNKTIONEN ===
        // ============================================================
        function getSlotIndex(): number {
            const now = new Date();
            return Math.floor((now.getHours() * 60 + now.getMinutes()) / 15);
        }
        
        function getSafeBoolean(oid: string): boolean {
            if (existsState(oid)) {
                const val = getState(oid).val;
                return (val === true || val === 'true' || val === 1);
            }
            return false;
        }
        
        function getSafeNumber(oid: string): number {
            if (existsState(oid)) return Number(getState(oid).val) || 0;
            return 0;
        }
        
        function getActiveLights() {
            let rawIds = [...LIGHTS];
            if (ENUM_FUNKTION && existsObject(ENUM_FUNKTION)) {
                const enumObj = getObject(ENUM_FUNKTION);
                if (enumObj && enumObj.common && enumObj.common.members) rawIds = rawIds.concat(enumObj.common.members);
            }
            rawIds = [...new Set(rawIds)];
            const result = [];
            
            for (const id of rawIds) {
                if (!existsObject(id)) continue;
                let powerId = id;
                const obj = getObject(id);
                
                if (obj && obj.type === 'channel') {
                    if (existsState(`${id}.STATE`)) powerId = `${id}.STATE`;
                    else if (existsState(`${id}.ON`)) powerId = `${id}.ON`;
                }
                
                const parts = powerId.split('.'); 
                parts.pop(); 
                const channel = parts.join('.');
                
                let briId = null;
                const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'DIMMER', 'SET'];
                for (const candidate of briCandidates) {
                    if (existsState(`${channel}.${candidate}`)) { briId = `${channel}.${candidate}`; break; }
                }
                result.push({ baseId: powerId, powerId, briId });
            }
            return result;
        }
        
        function getActiveBlinds() {
            const result = [];
            for (const id of BLINDS) { if (existsState(id)) result.push({ baseId: id, levelId: id }); }
            return result;
        }
        
        // ==========================================
        // 1. LERN-MODUS (Haus scannen & speichern)
        // ==========================================
        function runLearning(currentSlot: number): void {
            let db: SnapshotDatabase = {};
            try { 
                const raw = getState(DP_STORAGE).val as string;
                if (raw && raw !== '{}') db = JSON.parse(raw);
            } catch (e) {}
        
            let snapshot: Snapshot = {};
            const lights = getActiveLights();
            const blinds = getActiveBlinds();
            
            for (const l of lights) {
                const isOn = getSafeBoolean(l.powerId);
                const data: DeviceStateData = { on: isOn };
                if (isOn && l.briId) data.bri = getSafeNumber(l.briId);
                snapshot[l.baseId] = data;
            }
            
            for (const b of blinds) { 
                snapshot[b.baseId] = { level: getSafeNumber(b.levelId) }; 
            }
        
            if (!db[currentSlot]) db[currentSlot] = [];
            db[currentSlot].push(snapshot);
            if (db[currentSlot].length > MAX_SNAPSHOTS) db[currentSlot].shift();
            
            setState(DP_STORAGE, JSON.stringify(db), true);
            log(`[Simulation] LERNEN: Slot ${currentSlot} gespeichert.`, 'debug');
        }
        
        // ==========================================
        // 2. GHOST-MODUS (Abspielen)
        // ==========================================
        function runSimulation(currentSlot: number): void {
            let db: SnapshotDatabase = {};
            try { 
                const raw = getState(DP_STORAGE).val as string;
                if (raw && raw !== '{}') db = JSON.parse(raw);
            } catch (e) {}
        
            const lights = getActiveLights();
            const blinds = getActiveBlinds();
        
            if (!db[currentSlot] || db[currentSlot].length === 0) {
                log(`[Simulation] GHOST: Slot ${currentSlot} ist leer.`, 'warn');
                return;
            }
        
            const allSnapshots = db[currentSlot];
            const currentHour = new Date().getHours();
            const isMorning = currentHour >= 5 && currentHour < 14;
            const isEvening = currentHour >= 14 || currentHour < 5; 
        
            // --- 1. BLICKSCHUTZ PRÜFEN ---
            const anyBlindCurrentlyOpen = blinds.some(b => getSafeNumber(b.levelId) > 50);
            const anyBlindCurrentlyClosed = blinds.some(b => getSafeNumber(b.levelId) <= 50);
            const allBlindsCurrentlyClosed = blinds.length > 0 && blinds.every(b => getSafeNumber(b.levelId) <= 5);
        
            if (isEvening && allBlindsCurrentlyClosed) {
                log(`[Simulation] Blickschutz aktiv (Alle Rolladen zu). Schalte Lichter aus & pausiere.`, 'info');
                for (const l of lights) if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                return; 
            }
        
            // --- 2. SNAPSHOTS FILTERN ---
            let filtered = allSnapshots.filter(snap => {
                let wantsToOpen = false;
                let wantsToClose = false;
        
                for (const id of BLINDS) {
                    const dev = snap[id];
                    if (dev && typeof dev === 'object' && dev.level !== undefined) {
                        if (dev.level > 50) wantsToOpen = true;
                        if (dev.level <= 50) wantsToClose = true;
                    }
                }
                
                if (isMorning && anyBlindCurrentlyOpen && wantsToClose) return false;
                if (isEvening && anyBlindCurrentlyClosed && wantsToOpen) return false;
                return true;
            });
        
            if (filtered.length === 0) {
                log(`[Simulation] Filter-Fallback: Nutze alle Snapshots.`, 'debug');
                filtered = allSnapshots;
            }
        
            const chosen = filtered[Math.floor(Math.random() * filtered.length)];
            const delay = Math.floor(Math.random() * MAX_PLAYBACK_DELAY * 0.5);
        
            log(`[Simulation] GHOST: Slot ${currentSlot} aktiv. Verzögerung: ${Math.round(delay/1000)}s.`, 'info');
        
            setTimeout(() => {
                // --- 3. LICHT SCHALTEN ---
                for (const l of lights) {
                    const val = chosen[l.baseId];
                    if (val === undefined) continue; 
        
                    let tOn = (typeof val === 'boolean') ? val : val.on;
                    let tBri = (typeof val === 'object') ? val.bri : undefined;
        
                    if (tOn) {
                        const relayId = POWER_FIRST_MAP[l.baseId];
                        
                        if (relayId) {
                            if (!getSafeBoolean(relayId)) {
                                log(`[Simulation] Power-First: Strom für ${l.baseId} via ${relayId} ein.`, 'debug');
                                setState(relayId, true, false); 
                                if (tBri !== undefined && l.briId) setStateDelayed(l.briId, tBri, 2000, false);
                                continue;
                            }
                            // FIX v13.1: Relais war schon an, dimmen & schalten (und Schleife fortsetzen!)
                            if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                            if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                            continue; 
                        }
                        
                        // NORMALFALL (kein Relais)
                        if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                        if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                    } else {
                        if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                    }
                }
        
                // --- 4. ROLLADEN SCHALTEN ---
                for (const b of blinds) {
                    const val = chosen[b.baseId];
                    if (!val || typeof val === 'boolean' || val.level === undefined) continue;
                    
                    const tLevel = val.level;
                    const cLevel = getSafeNumber(b.levelId);
                    let block = false;
        
                    if (isMorning && cLevel > 50 && tLevel <= 50) block = true;
                    if (isEvening && cLevel <= 50 && tLevel > 50) block = true;
        
                    if (!block && Math.abs(tLevel - cLevel) > 5) {
                        setState(b.levelId, tLevel, false);
                    }
                }
            }, delay);
        }
        
        // === INIT & CRONJOB ===
        function startEngine() {
            schedule("*/15 * * * *", () => {
                const isPresent = getSafeBoolean(DP_PRESENCE);
                // FIX v13.1: Slot sofort einfrieren, damit er sich durch den Delay nicht ändert
                const currentSlot = getSlotIndex(); 
                
                if (isPresent) {
                    const samplingDelayMs = Math.floor(Math.random() * MAX_SAMPLING_DELAY); 
                    setTimeout(() => { runLearning(currentSlot); }, samplingDelayMs);
                } else {
                    runSimulation(currentSlot);
                }
            });
            log('[Simulation] KI-Snapshot-Engine v13.1 gestartet.');
        }
        
        if (!existsState(DP_STORAGE)) {
            createState(DP_STORAGE, '{}', { type: 'string', name: 'Snapshot DB', role: 'json', read: true, write: true }, startEngine);
        } else {
            startEngine();
        }
        
        

        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!

        Edit 18.03.2026: Ich habe hier in Post 1 auch die V2 eingefügt und die Anleitung entsprechend ergänzt. Der Datenpunkt "0_userdata.0.Anwesenheitssimulation_Snapshots" wurde in "0_userdata.0.Simulation.Snapshot_DB" umbenannt.
        Edit: 21.04.2026: Version 13.1 eingefügt.

        NegaleinN Offline
        NegaleinN Offline
        Negalein
        Global Moderator
        schrieb am zuletzt editiert von
        #5

        @mrMuppet sagte in [Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen:

        Ich freue mich auf euer Feedback!

        Hallo

        Ich war so frei und hab dein Script in die Sammlung aufgenommen. :)

        ° Node.js & System Update ---> sudo apt update, iob stop, sudo apt full-upgrade
        ° Node.js Fixer ---> iob nodejs-update
        ° Fixer ---> iob fix

        1 Antwort Letzte Antwort
        1
        • C Chrille1507

          Hallo,
          eine tolle Idee, die gleich mal testen werde.

          Eine kurze Frage, sind

          "0_userdata.0.Anwesenheitssimulation_Snapshots" und "0_userdata.0.Simulation.Snapshot_DB" nicht die gleichen Datenpunkte?

          M Offline
          M Offline
          mrMuppet
          schrieb am zuletzt editiert von mrMuppet
          #6

          @Chrille1507 sagte in [Vorlage] Anwesenheitssimulation mit dauerhaftem Lernen:

          Eine kurze Frage, sind

          "0_userdata.0.Anwesenheitssimulation_Snapshots" und "0_userdata.0.Simulation.Snapshot_DB" nicht die gleichen Datenpunkte?

          Stimmt. Das sollte der gleiche Datenpunkt sein.

          ioBroker auf NUC (Celeron mit Ubuntu-Server)

          Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

          1 Antwort Letzte Antwort
          0
          • M Offline
            M Offline
            mrMuppet
            schrieb am zuletzt editiert von
            #7

            Ich habe in Post 1 auch die V2 eingefügt und die Anleitung entsprechend ergänzt. Der Datenpunkt "0_userdata.0.Anwesenheitssimulation_Snapshots" wurde in "0_userdata.0.Simulation.Snapshot_DB" umbenannt.

            Wer das Skript schon nutzt und updaten möchte: Kopiert euch einfach den neuen Code aus dem ersten Beitrag und löscht den alten Datenpunkt einmal händisch aus euren Objekten. Danach läuft alles wie gewohnt!

            ioBroker auf NUC (Celeron mit Ubuntu-Server)

            Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

            1 Antwort Letzte Antwort
            0
            • E Offline
              E Offline
              emil70
              schrieb am zuletzt editiert von emil70
              #8

              @mrmuppet

              ich bekomme bei deinem script ein

              SyntaxError: Missing initializer in const declaration
              

              Log

              javascript.0
              	2026-03-19 19:24:22.602	info	script.js.common.Anwesenheit.Skript_1: Stopping script
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at async JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:564:25)
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:2096:44)
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:1829:25)
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at new Script (node:vm:117:7)
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: SyntaxError: Missing initializer in const declaration
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: ^^^^^^^^^^^
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: const DP_PRESENCE: string = '0_userdata.0.Anwesenheit_Familie.Anwesenheit';
              javascript.0
              	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: compile failed at: script.js.common.Anwesenheit.Skript_1:6
              

              Wenn ich dann

              //const DP_PRESENCE: string = '0_userdata.0.Anwesenheit_Familie.Anwesenheit'; 
              

              kommt

              Log

              	javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at async JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:564:25)
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:2096:44)
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:1829:25)
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at new Script (node:vm:117:7)
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: SyntaxError: Missing initializer in const declaration
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: ^^^^^^^^^^^^^
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: const ENUM_FUNKTION: string = '';
              javascript.0
              	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: compile failed at: script.js.common.Anwesenheit.Skript_1:11
              

              usw

              Woran liegt das? Fehlt da iregndo win Zeichen im Script?

              EdiT: die Fehlermeldung kommt auch, wenn ich dein orginal script nehme

              gruss emil70

              iobroker,pihole,homematic,motioneys,solaranlage laufen auf Proxmox (16 x AMD Ryzen 7 6800H with Radeon Graphics )

              M 1 Antwort Letzte Antwort
              0
              • E emil70

                @mrmuppet

                ich bekomme bei deinem script ein

                SyntaxError: Missing initializer in const declaration
                

                Log

                javascript.0
                	2026-03-19 19:24:22.602	info	script.js.common.Anwesenheit.Skript_1: Stopping script
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at async JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:564:25)
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:2096:44)
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:1829:25)
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: at new Script (node:vm:117:7)
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: SyntaxError: Missing initializer in const declaration
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: ^^^^^^^^^^^
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: const DP_PRESENCE: string = '0_userdata.0.Anwesenheit_Familie.Anwesenheit';
                javascript.0
                	2026-03-19 19:24:20.058	error	script.js.common.Anwesenheit.Skript_1: compile failed at: script.js.common.Anwesenheit.Skript_1:6
                

                Wenn ich dann

                //const DP_PRESENCE: string = '0_userdata.0.Anwesenheit_Familie.Anwesenheit'; 
                

                kommt

                Log

                	javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at async JavaScript.onObjectChange (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:564:25)
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.prepareScript (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:2096:44)
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at JavaScript.createVM (/opt/iobroker/node_modules/iobroker.javascript/build/main.js:1829:25)
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: at new Script (node:vm:117:7)
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: SyntaxError: Missing initializer in const declaration
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: ^^^^^^^^^^^^^
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: const ENUM_FUNKTION: string = '';
                javascript.0
                	2026-03-19 19:26:08.872	error	script.js.common.Anwesenheit.Skript_1: compile failed at: script.js.common.Anwesenheit.Skript_1:11
                

                usw

                Woran liegt das? Fehlt da iregndo win Zeichen im Script?

                EdiT: die Fehlermeldung kommt auch, wenn ich dein orginal script nehme

                M Offline
                M Offline
                mrMuppet
                schrieb am zuletzt editiert von
                #9

                @emil70 Das Problem ist sehr einfach zu beheben: Du hast das Skript als JavaScript angelegt. Es ist aber in TypeScript geschrieben. Einfach noch mal neu anlegen und reinkopieren. Dann klappts!

                ioBroker auf NUC (Celeron mit Ubuntu-Server)

                Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                1 Antwort Letzte Antwort
                1
                • J Offline
                  J Offline
                  Joel
                  schrieb am zuletzt editiert von Joel
                  #10

                  Wow, super.
                  Ich teste das mal gerade, bin aber nicht so fit, daher die Fragen...

                  3 Sachen für mich, dass ich es auch richtig verstehe:
                  Den DP Anwesenheit muss ich selbst durch einen Trigger setzten (z.B. über ein Mobilephone) oder wird der DP vom skript gesetzt?
                  Der DP Anwesenheitssimulation_Snapshots_DB (JSON-Speicher) habe ich richtig verstanden ist Zustandstyp=jason?
                  Gibt es evtl. eine kleine Anleitung wegen den Enums wie man das macht?

                  Danke und Grüße
                  Joel

                  1 Antwort Letzte Antwort
                  0
                  • J Offline
                    J Offline
                    Joel
                    schrieb am zuletzt editiert von Joel
                    #11

                    Und dann sehe ich auch z.B. das in dem DB Datenpunkt:

                    "95": [
                        {
                          "alias.0.Bad.Deckenlampe_Bad.ON": {
                            "on": true,
                            "bri": 100
                          },
                          "alias.0.Büro.Deckenlampe_Büro.ON": {
                            "on": false
                          },
                          "alias.0.Esszimmer.Deckenlampe_Esszimmer_1.ON": {
                            "on": false
                          },
                          "alias.0.Flur.Hue1_Decke.ON": {
                            "on": true,
                            "bri": 50
                          },
                          "alias.0.Küche.Deckenlampe_Küche.ON": {
                            "on": false
                          },
                          "alias.0.Schlafzimmer.Deckenlampe_Schlafzimmer.ON": {
                            "on": false
                          },
                          "alias.0.Wohnzimmer.Deckenlampe_Wohnzimmer.ON": {
                            "on": false
                          },
                          "alias.0.Wohnzimmer.Ikea_Stehlampe.ON": {
                            "on": false
                          },
                          "hue.0.Hue_Esszimmer_1": {
                            "on": false
                          },
                          "hue.0.Hue_Flur_1": {
                            "on": false
                          },
                          "hue.0.Hue_Küche": {
                            "on": false
                          },
                          "hue.0.Hue_Schlafzimmer": {
                            "on": false
                          },
                          "hue.0.Hue_Wohnzimmer": {
                            "on": false
                          },
                          "hue.0.Hue_go_Wohnzimmer_links": {
                            "on": false
                          },
                          "hue.0.Hue_go_Wohnzimmer_rechts": {
                            "on": false
                          },
                          "hue.0.Ikea_Stehlampe": {
                            "on": false
                          },
                          "hue.0.Hue_Bad": {
                            "on": false
                          },
                          "hue.0.Hue_Büro": {
                            "on": false
                          }
                        }
                      ]
                    }
                    

                    Hier der Teil meiner Konfig:

                    const LIGHTS: string[] = [
                          'alias.0.Bad.Deckenlampe_Bad.ON',        // <-- Trage hier alle Lampen ein, die 
                          'alias.0.Büro.Deckenlampe_Büro.ON',  //     von der Straße aus sichtbar sind.
                          'alias.0.Esszimmer.Deckenlampe_Esszimmer_1.ON',
                          'alias.0.Flur.Hue1_Decke.ON',
                          'alias.0.Küche.Deckenlampe_Küche.ON',
                          'alias.0.Schlafzimmer.Deckenlampe_Schlafzimmer.ON',
                          'alias.0.Wohnzimmer.Deckenlampe_Wohnzimmer.ON',
                          'alias.0.Wohnzimmer.Ikea_Stehlampe.ON'
                    ];
                    

                    Es ist aber so, dass zum Zeitpunkt des Schreibens in den DP das Licht in der Küche und im Esszimmer an war. Dennoch sagen die Daten im DP etwas anderes.
                    Liegt es an einer falschen Konfig von mir?
                    Danke!

                    1 Antwort Letzte Antwort
                    0
                    • J Offline
                      J Offline
                      Joel
                      schrieb am zuletzt editiert von
                      #12

                      Ich habe das nun etwa knappe 2 Wochen im Einsatz und ich denke es läuft gut. Musste mich nun, da der Autor aktuell nicht antwortet, selbst "durchwursteln" wo ich Unklarheiten hatte. Die Fragen oben haben sich durch probieren soweit erledigt. Was ich nicht weiß ist, wie sich das skript verhält, wenn ich mal z.B. 4 Wochen in Urlaub gehe und der Ringspeicher sich dann wie oben steht nach 5 Tagen leert. In der Abwesenheit wir ja nicht weiter "dazugelernt". Oder? Simuliert das Skript dann weiterhin auch bei längerer Abwesenheit? Vielleicht hört man ja noch was vom Entwickler....

                      M 1 Antwort Letzte Antwort
                      0
                      • J Joel

                        Ich habe das nun etwa knappe 2 Wochen im Einsatz und ich denke es läuft gut. Musste mich nun, da der Autor aktuell nicht antwortet, selbst "durchwursteln" wo ich Unklarheiten hatte. Die Fragen oben haben sich durch probieren soweit erledigt. Was ich nicht weiß ist, wie sich das skript verhält, wenn ich mal z.B. 4 Wochen in Urlaub gehe und der Ringspeicher sich dann wie oben steht nach 5 Tagen leert. In der Abwesenheit wir ja nicht weiter "dazugelernt". Oder? Simuliert das Skript dann weiterhin auch bei längerer Abwesenheit? Vielleicht hört man ja noch was vom Entwickler....

                        M Offline
                        M Offline
                        mrMuppet
                        schrieb am zuletzt editiert von mrMuppet
                        #13

                        Hallo @Joel,

                        erstmal ein dickes Sorry für die späte Rückmeldung! Ich habe die Benachrichtigungen aus dem Forum völlig übersehen. Es freut mich riesig, dass das Skript bei dir schon seit zwei Wochen den Dienst verrichtet!

                        Zu deinen Fragen:

                        Den DP Anwesenheit muss ich selbst durch einen Trigger setzten oder wird der DP vom skript gesetzt?

                        Richtig, den musst du selbst setzen! Das Skript ist quasi "blind" für die Außenwelt. Es verlässt sich darauf, dass dein ioBroker ihm sagt: "Wir sind jetzt weg." Die meisten User machen das über den TR-064 Adapter (Handy im WLAN angemeldet), einen Ping-Adapter, HomeKit oder per Geofencing. Sobald dieser Datenpunkt auf false springt, übernimmt der Ghost-Mode das Haus.

                        Der DP Anwesenheitssimulation_Snapshots_DB habe ich richtig verstanden ist Zustandstyp=jason?

                        Ganz genau (Typ string, Rolle json). Aber du musst ihn eigentlich gar nicht von Hand anlegen. Das Skript nutzt den Befehl createState(...) und legt ihn bei seinem allerersten Start automatisch richtig für dich an, falls er noch nicht existiert.

                        Gibt es evtl. eine kleine Anleitung wegen den Enums wie man das macht?

                        Klar! Enums sind im ioBroker die "Aufzählungen" (oft im Menü unter dem Reiter "Aufzählungen" zu finden). Du erstellst dort unter "Funktionen" eine neue Gruppe namens anwesenheitssimulation. Danach kannst du im Objektbaum deine Lampen (wichtig: immer den direkten Schalt-Datenpunkt, z.B. .STATE oder .ON) per Drag & Drop in diese Gruppe ziehen. Das Skript saugt sich dann alle Lampen aus dieser Gruppe automatisch an.

                        Es ist aber so, dass zum Zeitpunkt des Schreibens in den DP das Licht in der Küche und im Esszimmer an war. Dennoch sagen die Daten im DP etwas anderes.

                        Das liegt an zwei Dingen, die mir in deinem Log aufgefallen sind:

                        1. Das KI-Timing: Das Skript macht das "Foto" deines Hauses nicht in der Sekunde, in der du das Licht einschaltest. Es wartet den 15-Minuten-Takt ab und schlägt dann noch einmal einen zufälligen Delay von bis zu 5 Minuten obendrauf. Wenn du also das Licht anmachst und direkt ins JSON schaust, siehst du noch den alten Zustand.
                        2. Der Enum-Mix: In deinem JSON tauchen plötzlich Geräte auf (wie "hue.0.Hue_Esszimmer_1"), die in deiner Array-Konfiguration gar nicht stehen. Das passiert, wenn du in den ioBroker-Aufzählungen (Enums) aus Versehen komplette Geräte-Ordner statt der genauen Schalter hineingezogen hast. Nimm die Lampen am besten alle aus der Aufzählung raus und trage sie nur oben in das LIGHTS Array ein, das ist meistens fehlerfreier!

                        Was ich nicht weiß ist, wie sich das skript verhält, wenn ich mal z.B. 4 Wochen in Urlaub gehe und der Ringspeicher sich dann nach 5 Tagen leert. In der Abwesenheit wird ja nicht weiter dazugelernt.

                        Hier kann ich dich absolut beruhigen! Der Ringspeicher fasst 5 Snapshots pro Slot. Aber: Er lernt und überschreibt diese Snapshots nur, wenn du zu Hause bist!
                        Sobald du das Haus verlässt (Anwesenheit = false), friert das Skript die Datenbank komplett ein. Es wird nichts gelöscht oder überschrieben. Das Skript mischt dann einfach 4 Wochen lang deine gespeicherten Verhaltensmuster immer wieder neu und spielt sie ab.


                        Update: neue Version

                        Ich habe das Skript in den letzten Wochen komplett überarbeitet. Es ist jetzt intelligenter und bringt neue Features, die ihr optional nutzen könnt. Wenn ihr sie nicht braucht, lasst die Arrays einfach leer ([]).

                        Was ist neu?
                        Rolladen-Simulation:
                        Das Skript fotografiert im Lern-Modus jetzt nicht mehr nur eure Lampen, sondern auch eure Rolläden. Tragt dazu einfach die entsprechenden Datenpunkte (meist .LEVEL oder .ACTUAL) ins neue BLINDS Array ein. Der Ghost-Mode spielt bei Abwesenheit dann nicht nur euer Licht, sondern auch eure typischen Rolladen-Bewegungen ab.

                        Intelligente Logik-Sperre:
                        Damit die Simulation absolut realistisch wirkt und nicht aus Versehen mitten in der Nacht die Rolladen hoch gehen, habe ich eine Sperre eingebaut:

                        • Abends/Nachts: Das Skript filtert automatisch alle Snapshots heraus, die einen Rolladen öffnen würden. Was einmal zu ist, bleibt zu!
                        • Morgens/Vormittags: Genau umgekehrt. Einmal geöffnete Rolläden werden vom Ghost-Mode nicht wieder geschlossen.

                        Power-First Logik: Wer smarte Birnen hat, die aber physisch an einem Relais/Aktor hängen, kennt das Problem: Man kann sie nicht dimmen, wenn sie keinen Strom haben. Tragt diese Lampen ins neue POWER_FIRST_LIGHTS Array ein. Das Skript schaltet dann zuerst den Strom ein, wartet 2 Sekunden, und sendet erst dann den Dimm-Befehl.

                        // TITEL: Smart Presence Simulation (KI-Ghost-Mode) - v13.1
                        // INKL. DYNAMISCHEM HARDWARE-LOCK, BLICKSCHUTZ & POWER-FIRST MAP
                        // CHANGELOG v13.1: 
                        // - FIX: Edge-Case im Power-First (Dimmen, wenn Relais bereits an) behoben.
                        // - FIX: Slot-Index wird nun vor dem Delay eingefroren (verhindert Slot-Sprünge).
                        // =====================================================================
                        
                        // === 1. KONFIGURATION ===
                        
                        // Datenpunkt für Anwesenheit (true = da, false = Ghost-Mode aktiv)
                        const DP_PRESENCE: string = '0_userdata.0.Heizung.Anwesenheit'; 
                        
                        // Optionale ioBroker Aufzählung (Enum). Leer lassen (''), falls nicht genutzt.
                        const ENUM_FUNKTION: string = 'enum.functions.anwesenheitssimulation'; 
                        
                        // Manuelle Lampenliste (nur direkte .STATE oder .ON Datenpunkte)
                        const LIGHTS: string[] = [
                            // 'alias.0.Licht.Essbereich.STATE', 
                            // 'alias.0.Licht.Kueche.STATE'
                        ];
                        
                        // Mapping für Power-First Lampen!
                        // Format: 'ID_der_smarten_Lampe' : 'ID_des_dummen_Strom_Relais'
                        // Wenn die smarte Lampe simuliert wird, schaltet das Skript zuerst das Relais, wartet 2s, und dimmt dann.
                        const POWER_FIRST_MAP: Record<string, string> = {
                            // Beispiel:
                            // 'alias.0.Licht.Wohnzimmer_Smarte_Birne.STATE': 'alias.0.Licht.Wohnzimmer_Relais.STATE'
                        };
                        
                        // Rolläden für die Blickschutz-Logik (Zustand meist 0-100%).
                        const BLINDS: string[] = [
                            // 'alias.0.Beschattung.Kueche.Rolladen_Links.LEVEL'
                        ];
                        
                        
                        // === 2. EXPERTEN-EINSTELLUNGEN ===
                        const MAX_SNAPSHOTS: number = 5;           
                        const MAX_SAMPLING_DELAY: number = 300000; // Bis zu 5 Min Verzögerung beim Lernen
                        const MAX_PLAYBACK_DELAY: number = 840000; // Bis zu 14 Min Verzögerung beim Abspielen
                        const DP_STORAGE: string = '0_userdata.0.Simulation.Snapshot_DB';
                        
                        // ============================================================
                        // === TYPEN & INTERFACES ===
                        // ============================================================
                        interface DeviceStateData { on?: boolean; bri?: number; level?: number; }
                        interface Snapshot { [baseId: string]: DeviceStateData | boolean; }
                        interface SnapshotDatabase { [slotIndex: number]: Snapshot[]; }
                        
                        // ============================================================
                        // === HILFSFUNKTIONEN ===
                        // ============================================================
                        function getSlotIndex(): number {
                            const now = new Date();
                            return Math.floor((now.getHours() * 60 + now.getMinutes()) / 15);
                        }
                        
                        function getSafeBoolean(oid: string): boolean {
                            if (existsState(oid)) {
                                const val = getState(oid).val;
                                return (val === true || val === 'true' || val === 1);
                            }
                            return false;
                        }
                        
                        function getSafeNumber(oid: string): number {
                            if (existsState(oid)) return Number(getState(oid).val) || 0;
                            return 0;
                        }
                        
                        function getActiveLights() {
                            let rawIds = [...LIGHTS];
                            if (ENUM_FUNKTION && existsObject(ENUM_FUNKTION)) {
                                const enumObj = getObject(ENUM_FUNKTION);
                                if (enumObj && enumObj.common && enumObj.common.members) rawIds = rawIds.concat(enumObj.common.members);
                            }
                            rawIds = [...new Set(rawIds)];
                            const result = [];
                            
                            for (const id of rawIds) {
                                if (!existsObject(id)) continue;
                                let powerId = id;
                                const obj = getObject(id);
                                
                                if (obj && obj.type === 'channel') {
                                    if (existsState(`${id}.STATE`)) powerId = `${id}.STATE`;
                                    else if (existsState(`${id}.ON`)) powerId = `${id}.ON`;
                                }
                                
                                const parts = powerId.split('.'); 
                                parts.pop(); 
                                const channel = parts.join('.');
                                
                                let briId = null;
                                const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'DIMMER', 'SET'];
                                for (const candidate of briCandidates) {
                                    if (existsState(`${channel}.${candidate}`)) { briId = `${channel}.${candidate}`; break; }
                                }
                                result.push({ baseId: powerId, powerId, briId });
                            }
                            return result;
                        }
                        
                        function getActiveBlinds() {
                            const result = [];
                            for (const id of BLINDS) { if (existsState(id)) result.push({ baseId: id, levelId: id }); }
                            return result;
                        }
                        
                        // ==========================================
                        // 1. LERN-MODUS (Haus scannen & speichern)
                        // ==========================================
                        function runLearning(currentSlot: number): void {
                            let db: SnapshotDatabase = {};
                            try { 
                                const raw = getState(DP_STORAGE).val as string;
                                if (raw && raw !== '{}') db = JSON.parse(raw);
                            } catch (e) {}
                        
                            let snapshot: Snapshot = {};
                            const lights = getActiveLights();
                            const blinds = getActiveBlinds();
                            
                            for (const l of lights) {
                                const isOn = getSafeBoolean(l.powerId);
                                const data: DeviceStateData = { on: isOn };
                                if (isOn && l.briId) data.bri = getSafeNumber(l.briId);
                                snapshot[l.baseId] = data;
                            }
                            
                            for (const b of blinds) { 
                                snapshot[b.baseId] = { level: getSafeNumber(b.levelId) }; 
                            }
                        
                            if (!db[currentSlot]) db[currentSlot] = [];
                            db[currentSlot].push(snapshot);
                            if (db[currentSlot].length > MAX_SNAPSHOTS) db[currentSlot].shift();
                            
                            setState(DP_STORAGE, JSON.stringify(db), true);
                            log(`[Simulation] LERNEN: Slot ${currentSlot} gespeichert.`, 'debug');
                        }
                        
                        // ==========================================
                        // 2. GHOST-MODUS (Abspielen)
                        // ==========================================
                        function runSimulation(currentSlot: number): void {
                            let db: SnapshotDatabase = {};
                            try { 
                                const raw = getState(DP_STORAGE).val as string;
                                if (raw && raw !== '{}') db = JSON.parse(raw);
                            } catch (e) {}
                        
                            const lights = getActiveLights();
                            const blinds = getActiveBlinds();
                        
                            if (!db[currentSlot] || db[currentSlot].length === 0) {
                                log(`[Simulation] GHOST: Slot ${currentSlot} ist leer.`, 'warn');
                                return;
                            }
                        
                            const allSnapshots = db[currentSlot];
                            const currentHour = new Date().getHours();
                            const isMorning = currentHour >= 5 && currentHour < 14;
                            const isEvening = currentHour >= 14 || currentHour < 5; 
                        
                            // --- 1. BLICKSCHUTZ PRÜFEN ---
                            const anyBlindCurrentlyOpen = blinds.some(b => getSafeNumber(b.levelId) > 50);
                            const anyBlindCurrentlyClosed = blinds.some(b => getSafeNumber(b.levelId) <= 50);
                            const allBlindsCurrentlyClosed = blinds.length > 0 && blinds.every(b => getSafeNumber(b.levelId) <= 5);
                        
                            if (isEvening && allBlindsCurrentlyClosed) {
                                log(`[Simulation] Blickschutz aktiv (Alle Rolladen zu). Schalte Lichter aus & pausiere.`, 'info');
                                for (const l of lights) if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                                return; 
                            }
                        
                            // --- 2. SNAPSHOTS FILTERN ---
                            let filtered = allSnapshots.filter(snap => {
                                let wantsToOpen = false;
                                let wantsToClose = false;
                        
                                for (const id of BLINDS) {
                                    const dev = snap[id];
                                    if (dev && typeof dev === 'object' && dev.level !== undefined) {
                                        if (dev.level > 50) wantsToOpen = true;
                                        if (dev.level <= 50) wantsToClose = true;
                                    }
                                }
                                
                                if (isMorning && anyBlindCurrentlyOpen && wantsToClose) return false;
                                if (isEvening && anyBlindCurrentlyClosed && wantsToOpen) return false;
                                return true;
                            });
                        
                            if (filtered.length === 0) {
                                log(`[Simulation] Filter-Fallback: Nutze alle Snapshots.`, 'debug');
                                filtered = allSnapshots;
                            }
                        
                            const chosen = filtered[Math.floor(Math.random() * filtered.length)];
                            const delay = Math.floor(Math.random() * MAX_PLAYBACK_DELAY * 0.5);
                        
                            log(`[Simulation] GHOST: Slot ${currentSlot} aktiv. Verzögerung: ${Math.round(delay/1000)}s.`, 'info');
                        
                            setTimeout(() => {
                                // --- 3. LICHT SCHALTEN ---
                                for (const l of lights) {
                                    const val = chosen[l.baseId];
                                    if (val === undefined) continue; 
                        
                                    let tOn = (typeof val === 'boolean') ? val : val.on;
                                    let tBri = (typeof val === 'object') ? val.bri : undefined;
                        
                                    if (tOn) {
                                        const relayId = POWER_FIRST_MAP[l.baseId];
                                        
                                        if (relayId) {
                                            if (!getSafeBoolean(relayId)) {
                                                log(`[Simulation] Power-First: Strom für ${l.baseId} via ${relayId} ein.`, 'debug');
                                                setState(relayId, true, false); 
                                                if (tBri !== undefined && l.briId) setStateDelayed(l.briId, tBri, 2000, false);
                                                continue;
                                            }
                                            // FIX v13.1: Relais war schon an, dimmen & schalten (und Schleife fortsetzen!)
                                            if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                                            if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                                            continue; 
                                        }
                                        
                                        // NORMALFALL (kein Relais)
                                        if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                                        if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                                    } else {
                                        if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                                    }
                                }
                        
                                // --- 4. ROLLADEN SCHALTEN ---
                                for (const b of blinds) {
                                    const val = chosen[b.baseId];
                                    if (!val || typeof val === 'boolean' || val.level === undefined) continue;
                                    
                                    const tLevel = val.level;
                                    const cLevel = getSafeNumber(b.levelId);
                                    let block = false;
                        
                                    if (isMorning && cLevel > 50 && tLevel <= 50) block = true;
                                    if (isEvening && cLevel <= 50 && tLevel > 50) block = true;
                        
                                    if (!block && Math.abs(tLevel - cLevel) > 5) {
                                        setState(b.levelId, tLevel, false);
                                    }
                                }
                            }, delay);
                        }
                        
                        // === INIT & CRONJOB ===
                        function startEngine() {
                            schedule("*/15 * * * *", () => {
                                const isPresent = getSafeBoolean(DP_PRESENCE);
                                // FIX v13.1: Slot sofort einfrieren, damit er sich durch den Delay nicht ändert
                                const currentSlot = getSlotIndex(); 
                                
                                if (isPresent) {
                                    const samplingDelayMs = Math.floor(Math.random() * MAX_SAMPLING_DELAY); 
                                    setTimeout(() => { runLearning(currentSlot); }, samplingDelayMs);
                                } else {
                                    runSimulation(currentSlot);
                                }
                            });
                            log('[Simulation] KI-Snapshot-Engine v13.1 gestartet.');
                        }
                        
                        if (!existsState(DP_STORAGE)) {
                            createState(DP_STORAGE, '{}', { type: 'string', name: 'Snapshot DB', role: 'json', read: true, write: true }, startEngine);
                        } else {
                            startEngine();
                        }
                        

                        ioBroker auf NUC (Celeron mit Ubuntu-Server)

                        Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                        J 1 Antwort Letzte Antwort
                        0
                        • M Offline
                          M Offline
                          mrMuppet
                          schrieb am zuletzt editiert von mrMuppet
                          #14

                          habe jetzt auch Post 1 aktualisiert.

                          ioBroker auf NUC (Celeron mit Ubuntu-Server)

                          Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                          1 Antwort Letzte Antwort
                          0
                          • M mrMuppet

                            Hallo @Joel,

                            erstmal ein dickes Sorry für die späte Rückmeldung! Ich habe die Benachrichtigungen aus dem Forum völlig übersehen. Es freut mich riesig, dass das Skript bei dir schon seit zwei Wochen den Dienst verrichtet!

                            Zu deinen Fragen:

                            Den DP Anwesenheit muss ich selbst durch einen Trigger setzten oder wird der DP vom skript gesetzt?

                            Richtig, den musst du selbst setzen! Das Skript ist quasi "blind" für die Außenwelt. Es verlässt sich darauf, dass dein ioBroker ihm sagt: "Wir sind jetzt weg." Die meisten User machen das über den TR-064 Adapter (Handy im WLAN angemeldet), einen Ping-Adapter, HomeKit oder per Geofencing. Sobald dieser Datenpunkt auf false springt, übernimmt der Ghost-Mode das Haus.

                            Der DP Anwesenheitssimulation_Snapshots_DB habe ich richtig verstanden ist Zustandstyp=jason?

                            Ganz genau (Typ string, Rolle json). Aber du musst ihn eigentlich gar nicht von Hand anlegen. Das Skript nutzt den Befehl createState(...) und legt ihn bei seinem allerersten Start automatisch richtig für dich an, falls er noch nicht existiert.

                            Gibt es evtl. eine kleine Anleitung wegen den Enums wie man das macht?

                            Klar! Enums sind im ioBroker die "Aufzählungen" (oft im Menü unter dem Reiter "Aufzählungen" zu finden). Du erstellst dort unter "Funktionen" eine neue Gruppe namens anwesenheitssimulation. Danach kannst du im Objektbaum deine Lampen (wichtig: immer den direkten Schalt-Datenpunkt, z.B. .STATE oder .ON) per Drag & Drop in diese Gruppe ziehen. Das Skript saugt sich dann alle Lampen aus dieser Gruppe automatisch an.

                            Es ist aber so, dass zum Zeitpunkt des Schreibens in den DP das Licht in der Küche und im Esszimmer an war. Dennoch sagen die Daten im DP etwas anderes.

                            Das liegt an zwei Dingen, die mir in deinem Log aufgefallen sind:

                            1. Das KI-Timing: Das Skript macht das "Foto" deines Hauses nicht in der Sekunde, in der du das Licht einschaltest. Es wartet den 15-Minuten-Takt ab und schlägt dann noch einmal einen zufälligen Delay von bis zu 5 Minuten obendrauf. Wenn du also das Licht anmachst und direkt ins JSON schaust, siehst du noch den alten Zustand.
                            2. Der Enum-Mix: In deinem JSON tauchen plötzlich Geräte auf (wie "hue.0.Hue_Esszimmer_1"), die in deiner Array-Konfiguration gar nicht stehen. Das passiert, wenn du in den ioBroker-Aufzählungen (Enums) aus Versehen komplette Geräte-Ordner statt der genauen Schalter hineingezogen hast. Nimm die Lampen am besten alle aus der Aufzählung raus und trage sie nur oben in das LIGHTS Array ein, das ist meistens fehlerfreier!

                            Was ich nicht weiß ist, wie sich das skript verhält, wenn ich mal z.B. 4 Wochen in Urlaub gehe und der Ringspeicher sich dann nach 5 Tagen leert. In der Abwesenheit wird ja nicht weiter dazugelernt.

                            Hier kann ich dich absolut beruhigen! Der Ringspeicher fasst 5 Snapshots pro Slot. Aber: Er lernt und überschreibt diese Snapshots nur, wenn du zu Hause bist!
                            Sobald du das Haus verlässt (Anwesenheit = false), friert das Skript die Datenbank komplett ein. Es wird nichts gelöscht oder überschrieben. Das Skript mischt dann einfach 4 Wochen lang deine gespeicherten Verhaltensmuster immer wieder neu und spielt sie ab.


                            Update: neue Version

                            Ich habe das Skript in den letzten Wochen komplett überarbeitet. Es ist jetzt intelligenter und bringt neue Features, die ihr optional nutzen könnt. Wenn ihr sie nicht braucht, lasst die Arrays einfach leer ([]).

                            Was ist neu?
                            Rolladen-Simulation:
                            Das Skript fotografiert im Lern-Modus jetzt nicht mehr nur eure Lampen, sondern auch eure Rolläden. Tragt dazu einfach die entsprechenden Datenpunkte (meist .LEVEL oder .ACTUAL) ins neue BLINDS Array ein. Der Ghost-Mode spielt bei Abwesenheit dann nicht nur euer Licht, sondern auch eure typischen Rolladen-Bewegungen ab.

                            Intelligente Logik-Sperre:
                            Damit die Simulation absolut realistisch wirkt und nicht aus Versehen mitten in der Nacht die Rolladen hoch gehen, habe ich eine Sperre eingebaut:

                            • Abends/Nachts: Das Skript filtert automatisch alle Snapshots heraus, die einen Rolladen öffnen würden. Was einmal zu ist, bleibt zu!
                            • Morgens/Vormittags: Genau umgekehrt. Einmal geöffnete Rolläden werden vom Ghost-Mode nicht wieder geschlossen.

                            Power-First Logik: Wer smarte Birnen hat, die aber physisch an einem Relais/Aktor hängen, kennt das Problem: Man kann sie nicht dimmen, wenn sie keinen Strom haben. Tragt diese Lampen ins neue POWER_FIRST_LIGHTS Array ein. Das Skript schaltet dann zuerst den Strom ein, wartet 2 Sekunden, und sendet erst dann den Dimm-Befehl.

                            // TITEL: Smart Presence Simulation (KI-Ghost-Mode) - v13.1
                            // INKL. DYNAMISCHEM HARDWARE-LOCK, BLICKSCHUTZ & POWER-FIRST MAP
                            // CHANGELOG v13.1: 
                            // - FIX: Edge-Case im Power-First (Dimmen, wenn Relais bereits an) behoben.
                            // - FIX: Slot-Index wird nun vor dem Delay eingefroren (verhindert Slot-Sprünge).
                            // =====================================================================
                            
                            // === 1. KONFIGURATION ===
                            
                            // Datenpunkt für Anwesenheit (true = da, false = Ghost-Mode aktiv)
                            const DP_PRESENCE: string = '0_userdata.0.Heizung.Anwesenheit'; 
                            
                            // Optionale ioBroker Aufzählung (Enum). Leer lassen (''), falls nicht genutzt.
                            const ENUM_FUNKTION: string = 'enum.functions.anwesenheitssimulation'; 
                            
                            // Manuelle Lampenliste (nur direkte .STATE oder .ON Datenpunkte)
                            const LIGHTS: string[] = [
                                // 'alias.0.Licht.Essbereich.STATE', 
                                // 'alias.0.Licht.Kueche.STATE'
                            ];
                            
                            // Mapping für Power-First Lampen!
                            // Format: 'ID_der_smarten_Lampe' : 'ID_des_dummen_Strom_Relais'
                            // Wenn die smarte Lampe simuliert wird, schaltet das Skript zuerst das Relais, wartet 2s, und dimmt dann.
                            const POWER_FIRST_MAP: Record<string, string> = {
                                // Beispiel:
                                // 'alias.0.Licht.Wohnzimmer_Smarte_Birne.STATE': 'alias.0.Licht.Wohnzimmer_Relais.STATE'
                            };
                            
                            // Rolläden für die Blickschutz-Logik (Zustand meist 0-100%).
                            const BLINDS: string[] = [
                                // 'alias.0.Beschattung.Kueche.Rolladen_Links.LEVEL'
                            ];
                            
                            
                            // === 2. EXPERTEN-EINSTELLUNGEN ===
                            const MAX_SNAPSHOTS: number = 5;           
                            const MAX_SAMPLING_DELAY: number = 300000; // Bis zu 5 Min Verzögerung beim Lernen
                            const MAX_PLAYBACK_DELAY: number = 840000; // Bis zu 14 Min Verzögerung beim Abspielen
                            const DP_STORAGE: string = '0_userdata.0.Simulation.Snapshot_DB';
                            
                            // ============================================================
                            // === TYPEN & INTERFACES ===
                            // ============================================================
                            interface DeviceStateData { on?: boolean; bri?: number; level?: number; }
                            interface Snapshot { [baseId: string]: DeviceStateData | boolean; }
                            interface SnapshotDatabase { [slotIndex: number]: Snapshot[]; }
                            
                            // ============================================================
                            // === HILFSFUNKTIONEN ===
                            // ============================================================
                            function getSlotIndex(): number {
                                const now = new Date();
                                return Math.floor((now.getHours() * 60 + now.getMinutes()) / 15);
                            }
                            
                            function getSafeBoolean(oid: string): boolean {
                                if (existsState(oid)) {
                                    const val = getState(oid).val;
                                    return (val === true || val === 'true' || val === 1);
                                }
                                return false;
                            }
                            
                            function getSafeNumber(oid: string): number {
                                if (existsState(oid)) return Number(getState(oid).val) || 0;
                                return 0;
                            }
                            
                            function getActiveLights() {
                                let rawIds = [...LIGHTS];
                                if (ENUM_FUNKTION && existsObject(ENUM_FUNKTION)) {
                                    const enumObj = getObject(ENUM_FUNKTION);
                                    if (enumObj && enumObj.common && enumObj.common.members) rawIds = rawIds.concat(enumObj.common.members);
                                }
                                rawIds = [...new Set(rawIds)];
                                const result = [];
                                
                                for (const id of rawIds) {
                                    if (!existsObject(id)) continue;
                                    let powerId = id;
                                    const obj = getObject(id);
                                    
                                    if (obj && obj.type === 'channel') {
                                        if (existsState(`${id}.STATE`)) powerId = `${id}.STATE`;
                                        else if (existsState(`${id}.ON`)) powerId = `${id}.ON`;
                                    }
                                    
                                    const parts = powerId.split('.'); 
                                    parts.pop(); 
                                    const channel = parts.join('.');
                                    
                                    let briId = null;
                                    const briCandidates = ['LEVEL', 'ACTUAL', 'BRIGHTNESS', 'DIMMER', 'SET'];
                                    for (const candidate of briCandidates) {
                                        if (existsState(`${channel}.${candidate}`)) { briId = `${channel}.${candidate}`; break; }
                                    }
                                    result.push({ baseId: powerId, powerId, briId });
                                }
                                return result;
                            }
                            
                            function getActiveBlinds() {
                                const result = [];
                                for (const id of BLINDS) { if (existsState(id)) result.push({ baseId: id, levelId: id }); }
                                return result;
                            }
                            
                            // ==========================================
                            // 1. LERN-MODUS (Haus scannen & speichern)
                            // ==========================================
                            function runLearning(currentSlot: number): void {
                                let db: SnapshotDatabase = {};
                                try { 
                                    const raw = getState(DP_STORAGE).val as string;
                                    if (raw && raw !== '{}') db = JSON.parse(raw);
                                } catch (e) {}
                            
                                let snapshot: Snapshot = {};
                                const lights = getActiveLights();
                                const blinds = getActiveBlinds();
                                
                                for (const l of lights) {
                                    const isOn = getSafeBoolean(l.powerId);
                                    const data: DeviceStateData = { on: isOn };
                                    if (isOn && l.briId) data.bri = getSafeNumber(l.briId);
                                    snapshot[l.baseId] = data;
                                }
                                
                                for (const b of blinds) { 
                                    snapshot[b.baseId] = { level: getSafeNumber(b.levelId) }; 
                                }
                            
                                if (!db[currentSlot]) db[currentSlot] = [];
                                db[currentSlot].push(snapshot);
                                if (db[currentSlot].length > MAX_SNAPSHOTS) db[currentSlot].shift();
                                
                                setState(DP_STORAGE, JSON.stringify(db), true);
                                log(`[Simulation] LERNEN: Slot ${currentSlot} gespeichert.`, 'debug');
                            }
                            
                            // ==========================================
                            // 2. GHOST-MODUS (Abspielen)
                            // ==========================================
                            function runSimulation(currentSlot: number): void {
                                let db: SnapshotDatabase = {};
                                try { 
                                    const raw = getState(DP_STORAGE).val as string;
                                    if (raw && raw !== '{}') db = JSON.parse(raw);
                                } catch (e) {}
                            
                                const lights = getActiveLights();
                                const blinds = getActiveBlinds();
                            
                                if (!db[currentSlot] || db[currentSlot].length === 0) {
                                    log(`[Simulation] GHOST: Slot ${currentSlot} ist leer.`, 'warn');
                                    return;
                                }
                            
                                const allSnapshots = db[currentSlot];
                                const currentHour = new Date().getHours();
                                const isMorning = currentHour >= 5 && currentHour < 14;
                                const isEvening = currentHour >= 14 || currentHour < 5; 
                            
                                // --- 1. BLICKSCHUTZ PRÜFEN ---
                                const anyBlindCurrentlyOpen = blinds.some(b => getSafeNumber(b.levelId) > 50);
                                const anyBlindCurrentlyClosed = blinds.some(b => getSafeNumber(b.levelId) <= 50);
                                const allBlindsCurrentlyClosed = blinds.length > 0 && blinds.every(b => getSafeNumber(b.levelId) <= 5);
                            
                                if (isEvening && allBlindsCurrentlyClosed) {
                                    log(`[Simulation] Blickschutz aktiv (Alle Rolladen zu). Schalte Lichter aus & pausiere.`, 'info');
                                    for (const l of lights) if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                                    return; 
                                }
                            
                                // --- 2. SNAPSHOTS FILTERN ---
                                let filtered = allSnapshots.filter(snap => {
                                    let wantsToOpen = false;
                                    let wantsToClose = false;
                            
                                    for (const id of BLINDS) {
                                        const dev = snap[id];
                                        if (dev && typeof dev === 'object' && dev.level !== undefined) {
                                            if (dev.level > 50) wantsToOpen = true;
                                            if (dev.level <= 50) wantsToClose = true;
                                        }
                                    }
                                    
                                    if (isMorning && anyBlindCurrentlyOpen && wantsToClose) return false;
                                    if (isEvening && anyBlindCurrentlyClosed && wantsToOpen) return false;
                                    return true;
                                });
                            
                                if (filtered.length === 0) {
                                    log(`[Simulation] Filter-Fallback: Nutze alle Snapshots.`, 'debug');
                                    filtered = allSnapshots;
                                }
                            
                                const chosen = filtered[Math.floor(Math.random() * filtered.length)];
                                const delay = Math.floor(Math.random() * MAX_PLAYBACK_DELAY * 0.5);
                            
                                log(`[Simulation] GHOST: Slot ${currentSlot} aktiv. Verzögerung: ${Math.round(delay/1000)}s.`, 'info');
                            
                                setTimeout(() => {
                                    // --- 3. LICHT SCHALTEN ---
                                    for (const l of lights) {
                                        const val = chosen[l.baseId];
                                        if (val === undefined) continue; 
                            
                                        let tOn = (typeof val === 'boolean') ? val : val.on;
                                        let tBri = (typeof val === 'object') ? val.bri : undefined;
                            
                                        if (tOn) {
                                            const relayId = POWER_FIRST_MAP[l.baseId];
                                            
                                            if (relayId) {
                                                if (!getSafeBoolean(relayId)) {
                                                    log(`[Simulation] Power-First: Strom für ${l.baseId} via ${relayId} ein.`, 'debug');
                                                    setState(relayId, true, false); 
                                                    if (tBri !== undefined && l.briId) setStateDelayed(l.briId, tBri, 2000, false);
                                                    continue;
                                                }
                                                // FIX v13.1: Relais war schon an, dimmen & schalten (und Schleife fortsetzen!)
                                                if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                                                if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                                                continue; 
                                            }
                                            
                                            // NORMALFALL (kein Relais)
                                            if (tBri !== undefined && l.briId) setState(l.briId, tBri, false);
                                            if (!getSafeBoolean(l.powerId)) setState(l.powerId, true, false);
                                        } else {
                                            if (getSafeBoolean(l.powerId)) setState(l.powerId, false, false);
                                        }
                                    }
                            
                                    // --- 4. ROLLADEN SCHALTEN ---
                                    for (const b of blinds) {
                                        const val = chosen[b.baseId];
                                        if (!val || typeof val === 'boolean' || val.level === undefined) continue;
                                        
                                        const tLevel = val.level;
                                        const cLevel = getSafeNumber(b.levelId);
                                        let block = false;
                            
                                        if (isMorning && cLevel > 50 && tLevel <= 50) block = true;
                                        if (isEvening && cLevel <= 50 && tLevel > 50) block = true;
                            
                                        if (!block && Math.abs(tLevel - cLevel) > 5) {
                                            setState(b.levelId, tLevel, false);
                                        }
                                    }
                                }, delay);
                            }
                            
                            // === INIT & CRONJOB ===
                            function startEngine() {
                                schedule("*/15 * * * *", () => {
                                    const isPresent = getSafeBoolean(DP_PRESENCE);
                                    // FIX v13.1: Slot sofort einfrieren, damit er sich durch den Delay nicht ändert
                                    const currentSlot = getSlotIndex(); 
                                    
                                    if (isPresent) {
                                        const samplingDelayMs = Math.floor(Math.random() * MAX_SAMPLING_DELAY); 
                                        setTimeout(() => { runLearning(currentSlot); }, samplingDelayMs);
                                    } else {
                                        runSimulation(currentSlot);
                                    }
                                });
                                log('[Simulation] KI-Snapshot-Engine v13.1 gestartet.');
                            }
                            
                            if (!existsState(DP_STORAGE)) {
                                createState(DP_STORAGE, '{}', { type: 'string', name: 'Snapshot DB', role: 'json', read: true, write: true }, startEngine);
                            } else {
                                startEngine();
                            }
                            
                            J Offline
                            J Offline
                            Joel
                            schrieb am zuletzt editiert von
                            #15

                            @mrMuppet
                            Hallo,
                            macht doch nichts, soll ja weiter ein Hobby bleiben und nicht in Stress ausarten. (Wobei deine Skripte ja schon sehr professionell sind und du sicher beruflich auch aus der Ecke kommst).
                            Danke für die super Erklärungen, dann lag ich mit meinen Versuchen und Vermutungen auch gar nicht so daneben. Du hast mir sehr geholfen und dein neues Update habe ich nun mal übernommen, wobei ich die zusätzlichen Features aktuell nicht nutze.
                            Danke nochmals, wenn mir was auffällt melde ich mich hier.
                            Joel

                            1 Antwort Letzte Antwort
                            0
                            • J Offline
                              J Offline
                              Joel
                              schrieb am zuletzt editiert von Joel
                              #16

                              Noch eine Frage zum Verständnis:
                              Du schreibst:
                              ... Gruppe namens anwesenheitssimulation. Danach kannst du im Objektbaum deine Lampen (wichtig: immer den direkten Schalt-Datenpunkt, z.B. .STATE oder .ON) per Drag & Drop in diese Gruppe ziehen.
                              Oben in der Erklärung steht aber:
                              Du kannst sogar ganze Kanäle in das Enum ziehen – das Skript sucht sich den richtigen Datenpunkt (.STATE oder .ON) selbst.

                              Ich hatte seither die Kanäle von Hue im Enum Anwesehheitssimulation. Das Skript zog sich die Daten von on: und bri: der Hue Leuchte.
                              Was soll man nun nehmen den Kanal, oder nur .ON?
                              Das verwirrt mich jetzt.
                              Danke für eine Klärung.
                              LG
                              Joel

                              1 Antwort Letzte Antwort
                              0
                              • M Offline
                                M Offline
                                mrMuppet
                                schrieb am zuletzt editiert von
                                #17

                                Hallo @Joel,

                                Sorry für die Verwirrung, das liest sich in der Anleitung tatsächlich im ersten Moment widersprüchlich.

                                Um deine Frage direkt zu beantworten: Beides stimmt und beides funktioniert! Hier ist die genaue Erklärung, warum es diese zwei Aussagen gibt und was das Skript im Hintergrund macht:

                                1. Der bequeme Weg (Ganze Kanäle - so wie du es gemacht hast)
                                  Das Skript hat eine eingebaute "Auto-Detect" Funktion. Wenn du einen ganzen Lampen-Kanal (also den Über-Ordner der Lampe, z. B. bei Hue) in das Enum ziehst, erkennt das Skript: "Aha, das ist ein Ordner!" Es schaut dann selbstständig in diesen Ordner hinein und sucht nach den passenden Schalt-Datenpunkten (wie .STATE oder .ON) und sucht sich direkt im Anschluss auch noch den Dimm-Wert (bri, LEVEL, etc.) heraus. Für Standard-Adapter wie Philips Hue, Shelly oder Homematic funktioniert das völlig automatisch und fehlerfrei. Du hast also alles absolut richtig gemacht!

                                2. Der 100% sichere Weg (Direkter Datenpunkt - aus dem Sicherheitshinweis)
                                  Warum schreibe ich dann "WICHTIG: immer den direkten Schalt-Datenpunkt nehmen"?
                                  ioBroker ist riesig und es gibt hunderte verschiedene Adapter. Manche exotischen Adapter oder selbst angelegte Aliase verwenden wilde Bezeichnungen oder deklarieren ihre Ordner nicht sauber als channel. In solchen Fällen scheitert die Auto-Detect Funktion des Skripts, weil sie den Einschalt-Knopf im Ordner nicht findet.
                                  Wenn man stattdessen direkt den exakten Einschalt-Datenpunkt (z. B. alias.0.Licht.Lampe_XY.ON) in das Enum zieht, ist das zu 100 % idiotensicher (fail-safe). Das Skript nimmt dann genau diesen Schalter und sucht von dort aus "rückwärts" nach dem Dimm-Wert.

                                Fazit für dich:
                                Da du den Hue-Adapter nutzt und dieser sich strikt an die ioBroker-Standards hält, kannst du ganz bequem weiterhin einfach die kompletten Kanäle in dein Enum ziehen. Das Skript macht die Arbeit für dich.

                                Den Hinweis mit dem direkten Datenpunkt musst du nur im Hinterkopf behalten, falls du irgendwann mal eine sehr exotische China-Lampe eines Drittanbieters kaufst, bei der das Skript den Ein-/Ausschalter plötzlich nicht mehr von alleine findet. 😉

                                ioBroker auf NUC (Celeron mit Ubuntu-Server)

                                Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                                1 Antwort Letzte Antwort
                                0
                                • J Offline
                                  J Offline
                                  Joel
                                  schrieb am zuletzt editiert von
                                  #18

                                  Super, danke für die Erklärung.

                                  1 Antwort Letzte Antwort
                                  0
                                  • NashraN Offline
                                    NashraN Offline
                                    Nashra
                                    Most Active Forum Testing
                                    schrieb zuletzt editiert von Nashra
                                    #19

                                    Moin, was hat es mit dieser Meldung auf sich?

                                    javascript.0 	2026-04-26 16:30:00.010	warn	script.js.Urlaub.Anwesend-MrMuppet: [Simulation] GHOST: Slot 66 ist leer.
                                    

                                    Gruß Ralf
                                    Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                                    Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                                    M 1 Antwort Letzte Antwort
                                    0
                                    • NashraN Nashra

                                      Moin, was hat es mit dieser Meldung auf sich?

                                      javascript.0 	2026-04-26 16:30:00.010	warn	script.js.Urlaub.Anwesend-MrMuppet: [Simulation] GHOST: Slot 66 ist leer.
                                      
                                      M Offline
                                      M Offline
                                      mrMuppet
                                      schrieb zuletzt editiert von
                                      #20

                                      @Nashra
                                      Hi,
                                      die Meldung ist völlig harmlos und ganz leicht zu erklären.
                                      Das Skript teilt den Tag in 15-Minuten-Blöcke (die sogenannten "Slots") auf. Slot 66 entspricht dabei exakt 16:30 Uhr.
                                      Wenn du zuhause bist (Lern-Modus), macht das Skript alle 15 Minuten einen Snapshot aller Lampen und Rollläden und speichert die Zustände in dem jeweiligen Slot ab. Wenn du dann abwesend bist (Ghost-Modus), schaut das Skript um 16:30 Uhr in Slot 66 nach, welchen Zustand es aus der Vergangenheit laden und abspielen soll.
                                      Die Warnung "Slot 66 ist leer" bedeutet schlichtweg: Das Skript hat in seiner Datenbank für genau diese Uhrzeit noch keine einzige Aufzeichnung gefunden.
                                      Das hat meistens einen dieser Gründe:

                                      1. Laufzeit: Das Skript läuft noch nicht lange genug im Lern-Modus. Wenn du es z. B. erst gestern Abend eingerichtet hast, konnte es 16:30 Uhr noch gar nicht "miterleben" und aufzeichnen.
                                      2. Abwesenheit: Du bist um 16:30 Uhr generell nie zuhause. Das Skript zeichnet nur auf, wenn der Anwesenheits-Datenpunkt auf true steht.
                                      3. Downtime: Der ioBroker oder das Skript liefen an den vergangenen Tagen genau um diese Uhrzeit nicht.
                                        Du musst hier gar nichts unternehmen. Das Skript überspringt diesen Durchlauf, belässt das Haus für die nächsten 15 Minuten einfach in dem Zustand, in dem es gerade ist, und macht um 16:45 Uhr (Slot 67) ganz normal weiter. Du kannst die Warnung also einfach ignorieren.

                                      ioBroker auf NUC (Celeron mit Ubuntu-Server)

                                      Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                                      NashraN 1 Antwort Letzte Antwort
                                      0
                                      • M mrMuppet

                                        @Nashra
                                        Hi,
                                        die Meldung ist völlig harmlos und ganz leicht zu erklären.
                                        Das Skript teilt den Tag in 15-Minuten-Blöcke (die sogenannten "Slots") auf. Slot 66 entspricht dabei exakt 16:30 Uhr.
                                        Wenn du zuhause bist (Lern-Modus), macht das Skript alle 15 Minuten einen Snapshot aller Lampen und Rollläden und speichert die Zustände in dem jeweiligen Slot ab. Wenn du dann abwesend bist (Ghost-Modus), schaut das Skript um 16:30 Uhr in Slot 66 nach, welchen Zustand es aus der Vergangenheit laden und abspielen soll.
                                        Die Warnung "Slot 66 ist leer" bedeutet schlichtweg: Das Skript hat in seiner Datenbank für genau diese Uhrzeit noch keine einzige Aufzeichnung gefunden.
                                        Das hat meistens einen dieser Gründe:

                                        1. Laufzeit: Das Skript läuft noch nicht lange genug im Lern-Modus. Wenn du es z. B. erst gestern Abend eingerichtet hast, konnte es 16:30 Uhr noch gar nicht "miterleben" und aufzeichnen.
                                        2. Abwesenheit: Du bist um 16:30 Uhr generell nie zuhause. Das Skript zeichnet nur auf, wenn der Anwesenheits-Datenpunkt auf true steht.
                                        3. Downtime: Der ioBroker oder das Skript liefen an den vergangenen Tagen genau um diese Uhrzeit nicht.
                                          Du musst hier gar nichts unternehmen. Das Skript überspringt diesen Durchlauf, belässt das Haus für die nächsten 15 Minuten einfach in dem Zustand, in dem es gerade ist, und macht um 16:45 Uhr (Slot 67) ganz normal weiter. Du kannst die Warnung also einfach ignorieren.
                                        NashraN Offline
                                        NashraN Offline
                                        Nashra
                                        Most Active Forum Testing
                                        schrieb zuletzt editiert von
                                        #21

                                        @mrMuppet
                                        Danke für die gute Erklärung 👍

                                        Gruß Ralf
                                        Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                                        Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                                        1 Antwort Letzte Antwort
                                        0

                                        Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                        Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                        Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                        Registrieren Anmelden
                                        Antworten
                                        • In einem neuen Thema antworten
                                        Anmelden zum Antworten
                                        • Älteste zuerst
                                        • Neuste zuerst
                                        • Meiste Stimmen


                                        Support us

                                        ioBroker
                                        Community Adapters
                                        Donate

                                        574

                                        Online

                                        32.8k

                                        Benutzer

                                        82.8k

                                        Themen

                                        1.3m

                                        Beiträge
                                        Community
                                        Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                        ioBroker Community 2014-2025
                                        logo
                                        • Anmelden

                                        • Du hast noch kein Konto? Registrieren

                                        • Anmelden oder registrieren, um zu suchen
                                        • Erster Beitrag
                                          Letzter Beitrag
                                        0
                                        • Home
                                        • Aktuell
                                        • Tags
                                        • Ungelesen 0
                                        • Kategorien
                                        • Unreplied
                                        • Beliebt
                                        • GitHub
                                        • Docu
                                        • Hilfe