Weiter zum Inhalt
  • Home
  • Aktuell
  • 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. verschiedene Skripte

NEWS

  • Monatsrückblick Juli / August 2026 ist online!
    BluefoxB
    Bluefox
    9
    1
    379

  • Der neue Monatsrückblick für Mai und Juni 2026 ist online!
    BluefoxB
    Bluefox
    8
    1
    1.3k

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    3.8k

verschiedene Skripte

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
javascript
11 Beiträge 2 Kommentatoren 428 Aufrufe 4 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.
  • H Online
    H Online
    helfi9999
    schrieb am zuletzt editiert von helfi9999
    #1

    Hallo zusammen,
    habe mal bei mir aufgeräumt und habe eine ganze Menge an Skripten die ich von hier habe und stellenweise auch umgebaut habe die ich jetzt gerne mit euch teilen möchte:

    z.B.
    Musikstatus je Raum mit Alexa und/oder Sonos mit States Erstellung.Voraussetzung ist der Alexa Adapter und der Sonos Adapter:
    884f31a3-f1c5-4988-9e9c-7e4f0f68c5d3-image.jpeg
    Ihr müsst nur die Geräte im Skript ändern.

    
    /*************************************************
    * Musikstatus je Raum
    * mit State-Erstellung + Provider-Erkennung
    *************************************************/
    
    const rooms = [
       {
           name: "Wohnzimmer",
           target: "0_userdata.0.Status_Musikboxen.Wohnzimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           sonosState: "sonos.0.root.192_168_178_XXX.state_simple",
           sonosType: "sonos.0.root.192_168_178_XXX.current_type",
           delayMs: 0,
           config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio",2:"Fernseher"} }
       },
       {
           name: "Esszimmer",
           target: "0_userdata.0.Status_Musikboxen.Esszimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           sonosState: "sonos.0.root.192_168_178_XXX.state_simple",
           sonosType: "sonos.0.root.192_168_178_XXX.current_type",
           delayMs: 0,
           config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} }
       },
       {
           name: "Küche",
           target: "0_userdata.0.Status_Musikboxen.Küche_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           sonosState: "sonos.0.root.192_168_178_XXX.state_simple",
           sonosType: "sonos.0.root.192_168_178_XXX.current_type",
           delayMs: 0,
           config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} }
       },
       {
           name: "Garten",
           target: "0_userdata.0.Status_Musikboxen.Garten_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           delayMs: 2000,
           config: { alexaDirectProvider: true }
       },
       {
           name: "Schlafzimmer",
           target: "0_userdata.0.Status_Musikboxen.Schlafzimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           delayMs: 2000,
           config: { alexaDirectProvider: true }
       },
       {
           name: "Spielzimmer",
           target: "0_userdata.0.Status_Musikboxen.Spielzimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           delayMs: 0,
           config: { alexaDirectProvider: true }
       },
       {
           name: "Kinderzimmer",
           target: "0_userdata.0.Status_Musikboxen.Kinderzimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           delayMs: 2000,
           config: { alexaDirectProvider: true }
       },
       {
           name: "Gäste WC",
           target: "0_userdata.0.Status_Musikboxen.Gaeste_WC",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           delayMs: 0,
           config: { alexaDirectProvider: true }
       },
       {
           name: "Badezimmer",
           target: "0_userdata.0.Status_Musikboxen.Badezimmer_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           sonosState: "sonos.0.root.192_168_178_XXX.state_simple",
           sonosType: "sonos.0.root.192_168_178_XXX.current_type",
           delayMs: 0,
           config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} }
       },
       {
           name: "Hobbyraum",
           target: "0_userdata.0.Status_Musikboxen.Hobbyraum_Status",
           alexaState: "Deine Alexa.Player.currentState",
           alexaProvider: "Deine Alexa.Player.providerName",
           sonosState: "sonos.0.root.192_168_178_XXX.state_simple",
           sonosType: "sonos.0.root.192_168_178_XXX.current_type",
           delayMs: 0,
           config: { alexaDirectProvider: false, sonosMap: {0:"Musik von Platte",1:"TuneIn-Liveradio"} }
       }
    ];
    
    /*********************** STATES ERSTELLEN ***********************/
    
    function createTargetState(room) {
       createState(room.target, "nichts", {
           name: `Musikstatus ${room.name}`,
           type: "string",
           role: "text",
           read: true,
           write: true
       });
    }
    
    /*********************** LOGIK ***********************/
    
    function readVal(id, def = null) {
       if (!id) return def;
       const s = getState(id);
       return s ? s.val : def;
    }
    
    function normalizeProvider(p) {
       if (!p) return "";
    
       const raw = String(p).trim();
       const t = raw.toLowerCase();
    
       if (t.includes("amazon")) return "Amazon Music";
       if (t.includes("spotify")) return "Spotify";
       if (t.includes("tunein") || t.includes("radio")) return "TuneIn-Liveradio";
       if (t.includes("audible")) return "Audible";
       if (t.includes("apple")) return "Apple Music";
       if (t.includes("deezer")) return "Deezer";
       if (t.includes("youtube")) return "YouTube";
       if (t.includes("podcast")) return "Podcast";
       if (t.includes("bluetooth") || t.includes("aux") || t.includes("line")) return "Externe Quelle";
    
       return raw;
    }
    
    function writeStatus(room, val) {
       if (room.delayMs > 0) {
           setStateDelayed(room.target, val, true, room.delayMs, true);
       } else {
           setState(room.target, val, true);
       }
    }
    
    function evalRoom(room) {
       const alexaPlaying = readVal(room.alexaState, false) === true;
       const alexaProvider = normalizeProvider(readVal(room.alexaProvider, ""));
       const sonosPlaying = readVal(room.sonosState, false) === true;
       const sonosType = readVal(room.sonosType, null);
    
       if (room.config.alexaDirectProvider) {
           if (alexaPlaying) {
               writeStatus(room, alexaProvider || "Andere Quelle");
               log(`+++ ${room.name}: ${alexaProvider || "Andere Quelle"} +++`, "debug");
           } else {
               writeStatus(room, "nichts");
           }
           return;
       }
    
       if (alexaPlaying && alexaProvider) {
           writeStatus(room, alexaProvider);
           return;
       }
    
       if (
           sonosPlaying &&
           room.config.sonosMap &&
           room.config.sonosMap[sonosType] !== undefined
       ) {
           writeStatus(room, room.config.sonosMap[sonosType]);
           return;
       }
    
       writeStatus(room, "nichts");
    }
    
    function initRoom(room) {
       createTargetState(room);
    
       const ids = [];
    
       if (room.alexaState) ids.push(room.alexaState);
       if (room.alexaProvider) ids.push(room.alexaProvider);
       if (room.sonosState) ids.push(room.sonosState);
       if (room.sonosType) ids.push(room.sonosType);
    
       if (ids.length === 0) {
           log(`Keine Trigger für ${room.name} konfiguriert`, "warn");
           return;
       }
    
       on({ id: ids, change: "ne" }, () => evalRoom(room));
    
       setTimeout(() => {
           evalRoom(room);
       }, 1000);
    }
    
    /*********************** START ***********************/
    
    rooms.forEach(initRoom);
    


    Lautstärken Reset in Verbindung mit dem Skript Musikstatus
    Funktionen:

      • Reagiert auf Statusänderung eines Raums
      • Prüft auf Zielstatus "nichts"
      • Setzt Lautstärke definierter Geräte nach optionaler Verzögerung
      • Verhindert Mehrfach-Timer pro Raum
      • Optionales Debug-Logging
      • Alexa notificationVolume wird explizit über notificationId gesetzt
      • Sonderfall Kinderzimmer: nur notificationVolume, kein Player.volume

    /**
     * Echo Stati / Lautstärke-Reset
     *
     * Funktionen:
     * - Reagiert auf Statusänderung eines Raums
     * - Prüft auf Zielstatus "nichts"
     * - Setzt Lautstärke definierter Geräte nach optionaler Verzögerung
     * - Verhindert Mehrfach-Timer pro Raum
     * - Optionales Debug-Logging
     * - Alexa notificationVolume wird explizit über notificationId gesetzt
     * - Sonderfall Kinderzimmer: nur notificationVolume, kein Player.volume
     */
    
    const CONFIG = {
        targetStatus: 'nichts',
        defaultDelayMs: 5000,
        debug: true,
        logPrefix: '[Musikboxen]',
        minNotificationVolume: 0,
        maxNotificationVolume: 100,
        defaultNotificationOffset: 5
    };
    
    const rooms = [
        {
            name: 'Küche',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Küche_Status',
            delayMs: 0,
            trigger: 'ne',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'alexa2.0.Echo-Devices.4879f7942cdb42d596f4e24aac81e908.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.661b5e9aa0b24161a4ec52b340173846.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'sonos.0.root.192_168_178_18.volume',
                    volume: 10
                }
            ]
        },
        {
            name: 'Wohnzimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Wohnzimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.025b455db7bb4720ad941b7139bcc805.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.d5794400dafd4c2cb0c8889fc15f0653.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'sonos.0.root.192_168_178_21.volume',
                    volume: 10
                }
            ]
        },
        {
            name: 'Esszimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Esszimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.084e4de76a484be18aeda245e8f3f4d4.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.c31ec0fc26584e078ca0555aecfc9188.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'sonos.0.root.192_168_178_20.volume',
                    volume: 10
                }
            ]
        },
        {
            name: 'Schlafzimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Schlafzimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'ne',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.G091MK0614730BNS.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.G091MK0614730BNS.Preferences.notificationVolume',
                    volume: 18,
                    notificationOffset: 12
                }
            ]
        },
        {
            name: 'Hobbyraum',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Hobbyraum_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'ne',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.d3127dcdc8bc4158807fcab2fcfe7fba.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.c9d487971e914e939baad79585dd7b2e.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                }
            ]
        },
        {
            name: 'Gäste WC',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Gaeste_WC',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'ne',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.G090LF10718513KQ.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.G090LF10718513KQ.Preferences.notificationVolume',
                    volume: 15,
                    notificationOffset: 8
                }
            ]
        },
        {
            name: 'Badezimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Badezimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.828b54738e2a44a29d9331868a046c38.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.eba89b7180384792a24f92847baea082.Preferences.notificationVolume',
                    volume: 10,
                    notificationOffset: 5
                },
                {
                    id: 'sonos.0.root.192_168_178_17.volume',
                    volume: 10
                }
            ]
        },
        {
            name: 'Spielzimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Spielzimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.G2A0P3077407031K.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.G2A0P3077407031K.Preferences.notificationVolume',
                    volume: 25,
                    notificationOffset: 15
                }
            ]
        },
        {
            name: 'Kinderzimmer',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Kinderzimmer_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    notificationId: 'alexa2.0.Echo-Devices.G0911B05926204Q7.Preferences.notificationVolume',
                    volume: 11,
                    notificationOffset: 6,
                    notificationOnly: true
                }
            ]
        },
        {
            name: 'Garten',
            enabled: true,
            statusId: '0_userdata.0.Status_Musikboxen.Garten_Status',
            delayMs: CONFIG.defaultDelayMs,
            trigger: 'any',
            devices: [
                {
                    id: 'alexa2.0.Echo-Devices.G091AA08052203UL.Player.volume',
                    notificationId: 'alexa2.0.Echo-Devices.G091AA08052203UL.Preferences.notificationVolume',
                    volume: 15,
                    notificationOffset: 8
                }
            ]
        }
    ];
    
    const activeTimers = {};
    
    function logInfo(msg) {
        log(`${CONFIG.logPrefix} ${msg}`, 'info');
    }
    
    function logDebug(msg) {
        if (CONFIG.debug) {
            log(`${CONFIG.logPrefix} ${msg}`, 'debug');
        }
    }
    
    function logWarn(msg) {
        log(`${CONFIG.logPrefix} ${msg}`, 'warn');
    }
    
    function logError(msg) {
        log(`${CONFIG.logPrefix} ${msg}`, 'error');
    }
    
    function isTargetStatus(value) {
        if (value === null || value === undefined) return false;
        return String(value).trim() === CONFIG.targetStatus;
    }
    
    function isValidVolume(volume) {
        return typeof volume === 'number' && isFinite(volume) && volume >= 0 && volume <= 100;
    }
    
    function stateExists(id) {
        try {
            return !!getObject(id);
        } catch (e) {
            return false;
        }
    }
    
    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }
    
    function getNotificationVolume(volume, notificationOffset) {
        const offset = typeof notificationOffset === 'number'
            ? notificationOffset
            : CONFIG.defaultNotificationOffset;
    
        return clamp(
            volume - offset,
            CONFIG.minNotificationVolume,
            CONFIG.maxNotificationVolume
        );
    }
    
    function clearRoomTimer(roomName) {
        if (activeTimers[roomName]) {
            clearTimeout(activeTimers[roomName]);
            activeTimers[roomName] = null;
            logDebug(`${roomName}: vorhandener Timer gelöscht`);
        }
    }
    
    function setDeviceVolume(device, roomName) {
        if (!device) {
            logWarn(`${roomName}: ungültiges Gerät übersprungen`);
            return;
        }
    
        if (!isValidVolume(device.volume)) {
            logWarn(`${roomName}: ungültige Lautstärke ${device.volume}`);
            return;
        }
    
        if (!device.notificationOnly) {
            if (!device.id) {
                logWarn(`${roomName}: Gerät ohne ID übersprungen`);
                return;
            }
    
            if (!stateExists(device.id)) {
                logWarn(`${roomName}: Datenpunkt nicht gefunden: ${device.id}`);
                return;
            }
    
            try {
                setState(device.id, device.volume);
                logInfo(`${roomName}: Volume ${device.volume} gesetzt -> ${device.id}`);
            } catch (e) {
                logError(`${roomName}: Fehler beim Setzen von ${device.id}: ${e}`);
                return;
            }
        }
    
        if (device.notificationId) {
            if (!stateExists(device.notificationId)) {
                logWarn(`${roomName}: notificationVolume-Datenpunkt nicht gefunden: ${device.notificationId}`);
                return;
            }
    
            const notificationVolume = getNotificationVolume(device.volume, device.notificationOffset);
    
            try {
                setState(device.notificationId, notificationVolume);
                logInfo(`${roomName}: notificationVolume ${notificationVolume} gesetzt -> ${device.notificationId}`);
            } catch (e) {
                logError(`${roomName}: Fehler beim Setzen von ${device.notificationId}: ${e}`);
            }
        }
    }
    
    function resetRoomVolume(room) {
        if (!room || !Array.isArray(room.devices) || room.devices.length === 0) {
            logWarn(`${room && room.name ? room.name : 'Unbekannter Raum'}: keine Geräte konfiguriert`);
            activeTimers[room && room.name ? room.name : 'unknown'] = null;
            return;
        }
    
        room.devices.forEach(device => setDeviceVolume(device, room.name));
        activeTimers[room.name] = null;
    }
    
    function scheduleReset(room) {
        clearRoomTimer(room.name);
    
        const delayMs = typeof room.delayMs === 'number' && room.delayMs >= 0
            ? room.delayMs
            : CONFIG.defaultDelayMs;
    
        if (delayMs === 0) {
            logDebug(`${room.name}: sofortiger Reset`);
            resetRoomVolume(room);
            return;
        }
    
        logDebug(`${room.name}: Reset in ${delayMs} ms geplant`);
    
        activeTimers[room.name] = setTimeout(() => {
            activeTimers[room.name] = null;
            resetRoomVolume(room);
        }, delayMs);
    }
    
    function registerRoom(room) {
        if (!room || !room.name || !room.statusId) {
            logWarn('Ungültige Raum-Konfiguration übersprungen');
            return;
        }
    
        if (!room.enabled) {
            logDebug(`${room.name}: deaktiviert`);
            return;
        }
    
        if (!stateExists(room.statusId)) {
            logWarn(`${room.name}: Status-Datenpunkt nicht gefunden: ${room.statusId}`);
            return;
        }
    
        const trigger = room.trigger || 'ne';
    
        on({ id: room.statusId, change: trigger }, obj => {
            try {
                if (!obj || !obj.state) {
                    logDebug(`${room.name}: Event ohne state erhalten`);
                    return;
                }
    
                const newValue = obj.state.val;
                const oldValue = obj.oldState ? obj.oldState.val : undefined;
                const ack = obj.state.ack;
    
                logDebug(`${room.name}: Event | alt="${oldValue}" neu="${newValue}" ack=${ack}`);
    
                if (!isTargetStatus(newValue)) {
                    logDebug(`${room.name}: Status ist nicht "${CONFIG.targetStatus}", kein Reset`);
                    return;
                }
    
                scheduleReset(room);
            } catch (e) {
                logError(`${room.name}: Fehler im Event-Handler: ${e}`);
            }
        });
    
        logInfo(`${room.name}: Trigger registriert (${trigger}) auf ${room.statusId}`);
    }
    
    rooms.forEach(registerRoom);
    logInfo('Lautstärke-Reset-Skript mit expliziten notificationIds gestartet');
    
    


    es werden noch weitere folgen

    Intel NUC mit Iobroker

    1 Antwort Letzte Antwort
    0
    • H Online
      H Online
      helfi9999
      schrieb am zuletzt editiert von
      #2

      Blink Kamera im Wechsel:

      Vorraussetzung ist der Blink Adapter und das von mir entworfene Skript:

      // ===============================
      // Blink aktive Kamera / Bild / Zeit
      // Version: 1.2.0
      // ===============================
      
      const VERSION = '1.2.0';
      
      const aktiveKamera = '0_userdata.0.Blink.aktive_Kamera';
      const aktivesBild  = '0_userdata.0.Blink.aktives_Bild';
      const aktiveZeit   = '0_userdata.0.Blink.aktive_Zeit';
      
      // Gemeinsame Blink-Sperre für alle Blink-Scripts
      const blinkBusyUntil = '0_userdata.0.Blink.busy_until';
      const blinkBusyInfo  = '0_userdata.0.Blink.busy_info';
      
      const kamera1 = 'blink.0.cameras.706479.live.image_base64';
      const kamera2 = 'blink.0.cameras.1327710.live.image_base64';
      
      const zeit1 = 'blink.0.cameras.706479.live.timestamp';
      const zeit2 = 'blink.0.cameras.1327710.live.timestamp';
      
      const snapshot1 = 'blink.0.cameras.706479.commands.snapshot';
      const snapshot2 = 'blink.0.cameras.1327710.commands.snapshot';
      
      const DEFAULT_KAMERA = '706479';
      
      // Blink ist empfindlich bei zu vielen Befehlen
      const BLINK_SPERRE_MS       = 90000;  // 90 Sekunden Pause nach Snapshot
      const SNAPSHOT_TIMEOUT_MS   = 30000;  // 30 Sekunden auf neuen Timestamp warten
      const DEBOUNCE_MS           = 1500;   // Kamerawechsel entprellen
      
      let letzterTimestamp706479 = '';
      let letzterTimestamp1327710 = '';
      
      let snapshotLaeuft = false;
      let pendingSnapshot = false;
      let debounceTimer = null;
      let waitTimer = null;
      let pollTimer = null;
      
      function ensureState(id, def, common) {
         if (!existsState(id)) {
             createState(id, def, common);
         }
      }
      
      ensureState(aktiveKamera, DEFAULT_KAMERA, {
         type: 'string',
         role: 'text',
         name: 'Aktive Blink Kamera',
         read: true,
         write: true
      });
      
      ensureState(aktivesBild, '', {
         type: 'string',
         role: 'text',
         name: 'Aktives Blink Bild',
         read: true,
         write: true
      });
      
      ensureState(aktiveZeit, '', {
         type: 'string',
         role: 'text',
         name: 'Aktive Blink Zeit',
         read: true,
         write: true
      });
      
      ensureState(blinkBusyUntil, 0, {
         type: 'number',
         role: 'value',
         name: 'Blink busy until timestamp',
         read: true,
         write: true
      });
      
      ensureState(blinkBusyInfo, '', {
         type: 'string',
         role: 'text',
         name: 'Blink busy info',
         read: true,
         write: true
      });
      
      function getBusyUntil() {
         const state = getState(blinkBusyUntil);
      
         if (!state || state.val === null || state.val === undefined) {
             return 0;
         }
      
         const value = Number(state.val);
         return isNaN(value) ? 0 : value;
      }
      
      function istBlinkBusy() {
         return Date.now() < getBusyUntil();
      }
      
      function getRestzeitMs() {
         return Math.max(0, getBusyUntil() - Date.now());
      }
      
      function setBlinkBusy(ms, info) {
         const until = Date.now() + ms;
      
         setState(blinkBusyUntil, until, true);
         setState(blinkBusyInfo, info, true);
      
         log(`[v${VERSION}] Blink gesperrt für ${Math.ceil(ms / 1000)}s: ${info}`, 'info');
      }
      
      function getKameraWert() {
         const state = getState(aktiveKamera);
      
         if (!state || state.val === null || state.val === undefined || state.val === '') {
             return DEFAULT_KAMERA;
         }
      
         const cam = String(state.val).replace(/"/g, '');
      
         if (cam !== '706479' && cam !== '1327710') {
             log(`[v${VERSION}] Blink: Ungültige Kamera "${cam}", verwende ${DEFAULT_KAMERA}`, 'warn');
             return DEFAULT_KAMERA;
         }
      
         return cam;
      }
      
      function getBildQuelle(cam) {
         return cam === '1327710' ? kamera2 : kamera1;
      }
      
      function getZeitQuelle(cam) {
         return cam === '1327710' ? zeit2 : zeit1;
      }
      
      function getSnapshotQuelle(cam) {
         return cam === '1327710' ? snapshot2 : snapshot1;
      }
      
      function getLetztenTimestamp(cam) {
         return cam === '1327710' ? letzterTimestamp1327710 : letzterTimestamp706479;
      }
      
      function setLetztenTimestamp(cam, value) {
         if (cam === '1327710') {
             letzterTimestamp1327710 = value;
         } else {
             letzterTimestamp706479 = value;
         }
      }
      
      function formatZeitBerlin(timestamp) {
         const datum = new Date(timestamp);
      
         if (isNaN(datum.getTime())) {
             return String(timestamp);
         }
      
         return datum.toLocaleString('de-DE', {
             timeZone: 'Europe/Berlin',
             day: '2-digit',
             month: '2-digit',
             year: 'numeric',
             hour: '2-digit',
             minute: '2-digit'
         }).replace(',', '');
      }
      
      function updateBild(cam) {
         const quelle = getBildQuelle(cam);
         const bildState = getState(quelle);
      
         if (!bildState || !bildState.val) {
             log(`[v${VERSION}] Blink: Kein Bild vorhanden von ${quelle}`, 'warn');
             return false;
         }
      
         if (getKameraWert() === cam) {
             setState(aktivesBild, String(bildState.val), true);
         }
      
         return true;
      }
      
      function updateZeit(cam) {
         const quelle = getZeitQuelle(cam);
         const zeitState = getState(quelle);
      
         if (!zeitState || !zeitState.val) {
             log(`[v${VERSION}] Blink: Keine Zeit vorhanden von ${quelle}`, 'warn');
             return false;
         }
      
         const formatiert = formatZeitBerlin(zeitState.val);
      
         if (getKameraWert() === cam) {
             setState(aktiveZeit, formatiert, true);
         }
      
         return true;
      }
      
      function updateAlles(cam) {
         cam = cam || getKameraWert();
      
         updateBild(cam);
         updateZeit(cam);
      }
      
      function snapshotAusloesen(cam) {
         const snapshot = getSnapshotQuelle(cam);
      
         log(`[v${VERSION}] Blink: Snapshot auslösen: ${cam}`, 'info');
      
         // Befehl aktiv auslösen
         setState(snapshot, true, false);
      }
      
      function planeSnapshotNachSperre() {
         if (waitTimer) {
             clearTimeout(waitTimer);
         }
      
         const wait = getRestzeitMs() + 1000;
      
         log(`[v${VERSION}] Blink ist busy. Snapshot wird in ${Math.ceil(wait / 1000)}s erneut versucht.`, 'warn');
      
         waitTimer = setTimeout(function () {
             waitTimer = null;
      
             if (pendingSnapshot && !snapshotLaeuft) {
                 pendingSnapshot = false;
                 updateNachSnapshot();
             }
         }, wait);
      }
      
      function updateNachSnapshot() {
         const cam = getKameraWert();
      
         if (snapshotLaeuft) {
             pendingSnapshot = true;
             log(`[v${VERSION}] Blink: Snapshot läuft bereits, neuer Wunsch wird gemerkt: ${cam}`, 'warn');
             return;
         }
      
         if (istBlinkBusy()) {
             pendingSnapshot = true;
             planeSnapshotNachSperre();
             return;
         }
      
         snapshotLaeuft = true;
         pendingSnapshot = false;
      
         const zeitQuelle = getZeitQuelle(cam);
         const alterTimestampState = getState(zeitQuelle);
         const alterTimestamp = alterTimestampState && alterTimestampState.val ? String(alterTimestampState.val) : '';
      
         setLetztenTimestamp(cam, alterTimestamp);
      
         setBlinkBusy(BLINK_SPERRE_MS, `Snapshot Kamera ${cam}`);
         snapshotAusloesen(cam);
      
         let vergangen = 0;
      
         if (pollTimer) {
             clearInterval(pollTimer);
             pollTimer = null;
         }
      
         pollTimer = setInterval(function () {
             vergangen += 1000;
      
             const aktuelleCam = getKameraWert();
             const neuerTimestampState = getState(zeitQuelle);
             const neuerTimestamp = neuerTimestampState && neuerTimestampState.val ? String(neuerTimestampState.val) : '';
      
             if (neuerTimestamp && neuerTimestamp !== getLetztenTimestamp(cam)) {
                 setLetztenTimestamp(cam, neuerTimestamp);
      
                 if (aktuelleCam === cam) {
                     updateAlles(cam);
                 }
      
                 clearInterval(pollTimer);
                 pollTimer = null;
                 snapshotLaeuft = false;
      
                 log(`[v${VERSION}] Blink: Snapshot erfolgreich aktualisiert: ${cam}`, 'info');
      
                 if (pendingSnapshot) {
                     planeSnapshotNachSperre();
                 }
      
                 return;
             }
      
             if (vergangen >= SNAPSHOT_TIMEOUT_MS) {
                 if (aktuelleCam === cam) {
                     updateAlles(cam);
                 }
      
                 clearInterval(pollTimer);
                 pollTimer = null;
                 snapshotLaeuft = false;
      
                 log(`[v${VERSION}] Blink: Snapshot hat nach ${SNAPSHOT_TIMEOUT_MS / 1000}s keinen neuen Timestamp geliefert: ${cam}`, 'warn');
      
                 if (pendingSnapshot) {
                     planeSnapshotNachSperre();
                 }
             }
         }, 1000);
      }
      
      function kameraWechselEntprellt() {
         if (debounceTimer) {
             clearTimeout(debounceTimer);
         }
      
         debounceTimer = setTimeout(function () {
             debounceTimer = null;
             updateNachSnapshot();
         }, DEBOUNCE_MS);
      }
      
      // Nur bei echter Änderung reagieren
      on({ id: aktiveKamera, change: 'ne' }, function () {
         kameraWechselEntprellt();
      });
      
      on({ id: kamera1, change: 'ne' }, function () {
         if (getKameraWert() === '706479') {
             updateBild('706479');
         }
      });
      
      on({ id: kamera2, change: 'ne' }, function () {
         if (getKameraWert() === '1327710') {
             updateBild('1327710');
         }
      });
      
      on({ id: zeit1, change: 'ne' }, function () {
         if (getKameraWert() === '706479') {
             updateZeit('706479');
         }
      });
      
      on({ id: zeit2, change: 'ne' }, function () {
         if (getKameraWert() === '1327710') {
             updateZeit('1327710');
         }
      });
      
      log(`[v${VERSION}] Blink aktive Kamera Script gestartet`, 'info');
      
      setTimeout(function () {
         updateAlles();
      }, 1000);
      


      Habe an der Terrassentür ein Türkontakt wenn die Tür auf ist nimmt die Kamera im Garten nicht auf das gleiche ist auch bei der Haustür.

      f0ad6a37-62ee-486d-86c0-a1ba28cb2530-image.jpeg

      Intel NUC mit Iobroker

      1 Antwort Letzte Antwort
      0
      • H Online
        H Online
        helfi9999
        schrieb am zuletzt editiert von helfi9999
        #3

        ECOVACS ioBroker UNIVERSAL – KOMPLETTPAKET v6.4

        Dieses Paket ist universell aufgebaut.

        Es ist NICHT fest auf Namen wie Luna oder Sky programmiert.
        Es erkennt alle eingebundenen Instanzen nach dem Muster:

        ecovacs-deebot.x
        

        und legt pro erkanntem Sauger eine eigene Struktur an:

        0_userdata.0.Sauger.<Gerätename>
        

        Beispiel:

        0_userdata.0.Sauger.Luna
        0_userdata.0.Sauger.Sky
        

        Bei einem anderen Benutzer entstehen automatisch dessen eigene Gerätenamen.
        ENTHALTENE SKRIPTE

        1. 01_Ecovacs_Universal_Setup_v6.0.js
          Aufgabe:
        • erkennt alle ecovacs-deebot.* Instanzen
        • versucht den Gerätenamen automatisch zu lesen
        • erkennt die Map-ID
        • erkennt alle spotAreas / Räume
        • legt die benötigten 0_userdata-Datenpunkte an

        Betrieb:

        • EINMAL starten
        • danach wieder AUS

        Wenn später ein neuer Sauger hinzukommt:

        • Setup erneut kurz starten
        • danach wieder AUS
        1. 02_Ecovacs_Universal_MapSource_v6.1.js
          Aufgabe:
        • sucht pro Sauger automatisch nach einer Kartenquelle

        • bevorzugt:
          ecovacs-deebot.x.map.<mapId>.map64

        • versucht bei vorhandenem loadMapImage die Karte regelmäßig neu anzufordern

        • schreibt die erkannte Quelle nach:

          0_userdata.0.Sauger.<Name>.Map.Source.type
          0_userdata.0.Sauger.<Name>.Map.Source.state
          0_userdata.0.Sauger.<Name>.Map.Source.image
          0_userdata.0.Sauger.<Name>.Map.Source.status

        Betrieb:

        • dauerhaft AN

        Hinweis:
        Die aktuelle VIS v6.4 verwendet das Kartenbild nicht mehr als Hintergrund.
        Der MapSource kann trotzdem aktiv bleiben, weil er weiterhin universell die
        Kartenquelle ermittelt und für Diagnose / spätere Erweiterungen verfügbar hält.

        1. 03_Ecovacs_Universal_Position_Fahrspur_v6.0.js

        Aufgabe:

        • liest pro Sauger:

          ecovacs-deebot.x.map.deebotPosition

        • berechnet daraus die Position auf der Karte

        • aktualisiert:
          Map.pixelXcss
          Map.pixelYcss
          Map.rotateCss

        • zeichnet eine Fahrspur

        • setzt die Fahrspur bei einem neuen Reinigungsvorgang zurück

        • übernimmt Status und Positionsname, sofern vorhanden

        Betrieb:

        • dauerhaft AN
        1. 04_Ecovacs_Universal_RaumSync_v6.0.js

        Aufgabe:

        • synchronisiert pro Raum:

          markForNextSpotAreaCleaning

        mit:

        0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
        

        Dadurch weiß die VIS, ob ein Raum ausgewählt ist.

        Betrieb:

        • dauerhaft AN
        1. 05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
          Aufgabe:
        • durchsucht jeden automatisch erkannten Raum nach Geometriedaten

        • sucht unter anderem nach:
          polygon
          boundary
          boundaries
          coordinates
          coords
          points
          vertices
          outline
          contour
          rect
          bounds

        • wandelt gefundene Raumgeometrien in SVG-Punkte um

        • speichert pro Raum:

          Rooms.<id>.geometryState
          Rooms.<id>.geometryType
          Rooms.<id>.polygonPoints
          Rooms.<id>.geometryStatus

        Betrieb:

        • dauerhaft AN

        Wenn ein Sauger keine Raumgeometrie bereitstellt:

        • polygonPoints bleibt leer
        • die VIS kann dann keine Raumfläche zeichnen
        1. 06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js

        Aufgabe:

        • erzeugt die komplette VIS-Darstellung
        • dunkler Hintergrund
        • automatisch erkannte farbige Raumflächen
        • Raumflächen direkt anklickbar
        • Fahrspur
        • Roboterposition
        • KEINE zusätzlichen Raum-Buttons mehr

        Betrieb:

        • dauerhaft AN

        INSTALLATION:

        1. In ioBroker unter JavaScript für jede .js-Datei ein eigenes Skript anlegen.

        2. Den Inhalt der jeweiligen .js-Datei komplett in das entsprechende
          JavaScript-Skript kopieren.

        EMPFOHLENE REIHENFOLGE
        Schritt 1:
        01_Ecovacs_Universal_Setup_v6.0.js
        kurz AN

        Warten, bis die Struktur unter:

        0_userdata.0.Sauger
        

        angelegt wurde.

        Danach:
        01 Setup AUS

        Schritt 2:
        Folgende Skripte dauerhaft AN:

        02_Ecovacs_Universal_MapSource_v6.1.js
        03_Ecovacs_Universal_Position_Fahrspur_v6.0.js
        04_Ecovacs_Universal_RaumSync_v6.0.js
        05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
        06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js
        

        WELCHE SKRIPTE SIND AN / AUS?
        01 Setup:
        AUS
        Nur bei Erstinstallation oder neuem Sauger kurz AN.

        02 MapSource:
        AN

        03 Position/Fahrspur:
        AN

        04 RaumSync:
        AN

        05 Raumflächen-Detektor:
        AN

        06 VIS:
        AN

        VIS EINRICHTEN

        In VIS ein Widget verwenden:

        Basic -> String (unescaped)
        

        Als Objekt-ID:

        0_userdata.0.Sauger.<Gerätename>.VIS.html
        

        Beispiel:

        0_userdata.0.Sauger.Luna.VIS.html
        

        Keine geschweiften Klammern verwenden.

        Also NICHT:

        {0_userdata.0.Sauger.Luna.VIS.html}
        

        sondern direkt:

        0_userdata.0.Sauger.Luna.VIS.html
        

        BEDIENUNG

        RAUM AUSWÄHLEN

        Einfach direkt auf die farbige Raumfläche in der Karte klicken.

        Beim Klick wird der zugehörige Adapter-State geschaltet:

        ecovacs-deebot.x.map.<mapId>.spotAreas.<id>.markForNextSpotAreaCleaning
        

        Der RaumSync übernimmt den Zustand anschließend nach:

        0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
        

        Ein ausgewählter Raum wird in der VIS stärker hervorgehoben.

        MEHRERE RÄUME

        Mehrere Raumflächen können nacheinander angeklickt werden.

        Die Auswahl wird direkt über die Adapter-States vorgenommen.

        FAHRSPUR

        Die Fahrspur wird aus deebotPosition erzeugt.

        Sie liegt in:

        0_userdata.0.Sauger.<Name>.Map.trailPoints
        

        Beim Beginn eines neuen Reinigungsvorgangs wird sie zurückgesetzt.

        ROBOTERPOSITION

        Die Position wird aus:

        ecovacs-deebot.x.map.deebotPosition
        

        ermittelt.

        Die VIS verwendet:

        Map.pixelXcss
        Map.pixelYcss
        Map.rotateCss
        

        RAUMFLÄCHEN-DIAGNOSE

        Wenn ein Raum nicht gezeichnet wird, prüfen:

        0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryStatus
        

        und:

        0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryState
        

        und:

        0_userdata.0.Sauger.<Name>.Rooms.<id>.polygonPoints
        

        Wenn geometryStatus meldet:

        Keine Raumgeometrie im Adapter gefunden
        

        dann liefert dieses Modell / diese Adapterversion für diesen Raum keine
        verwertbaren Polygon- oder Boundary-Daten.

        KARTENQUELLEN-DIAGNOSE

        Prüfen:

        0_userdata.0.Sauger.<Name>.Map.Source.status
        

        und:

        0_userdata.0.Sauger.<Name>.Map.Source.state
        

        POSITION PASST NICHT ZUR KARTE

        Die Koordinatenumrechnung verwendet pro Sauger:

        Map.Config.minX
        Map.Config.maxX
        Map.Config.minY
        Map.Config.maxY
        Map.Config.imgW
        Map.Config.imgH
        

        Standard:

        minX = -250
        maxX = 10700
        minY = -4350
        maxY = 400
        imgW = 540
        imgH = 231
        

        Diese Werte können pro Sauger angepasst werden.

        Wichtig:
        Die Raumflächen-Erkennung versucht selbst zu unterscheiden, ob Raumkoordinaten
        bereits Pixelwerte sind oder Weltkoordinaten umgerechnet werden müssen.

        NEUER SAUGER

        Wenn später eine weitere ecovacs-deebot.* Instanz installiert wird:

        1. Setup v6.0 kurz AN.
        2. Warten, bis der neue Sauger unter 0_userdata.0.Sauger erscheint.
        3. Setup wieder AUS.
        4. Die dauerhaft laufenden Skripte einmal neu starten.

        Danach wird der neue Sauger automatisch mit verarbeitet.

        MEHRERE SAUGER

        Alle dauerhaft laufenden Skripte suchen automatisch nach:

        0_userdata.0.Sauger.*.deebotPrefix
        

        Dadurch wird nicht nur ein einzelner Sauger verarbeitet.

        Jeder Sauger besitzt:

        • eigene Map-ID
        • eigene Räume
        • eigene Raumflächen
        • eigene Position
        • eigene Fahrspur
        • eigenes VIS.html

        WICHTIGE GRENZEN

        Das Paket kann nur Daten verwenden, die der Ecovacs-Adapter für das jeweilige
        Modell bereitstellt.

        Wenn ein Modell:

        • keine deebotPosition liefert -> keine Live-Position/Fahrspur
        • keine spotAreas liefert -> keine automatische Raumauswahl
        • keine Geometrie für spotAreas liefert -> keine gezeichneten Raumflächen

        Das Paket versucht diese Funktionen automatisch zu erkennen und ist nicht
        auf einen bestimmten Sauger oder Grundriss fest programmiert.

        Die Buttons muss man selbst erstellen wie hier:
        c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg

        Viel Spass beim ausprobieren !!

        Intel NUC mit Iobroker

        sigi234S H 3 Antworten Letzte Antwort
        1
        • H helfi9999

          ECOVACS ioBroker UNIVERSAL – KOMPLETTPAKET v6.4

          Dieses Paket ist universell aufgebaut.

          Es ist NICHT fest auf Namen wie Luna oder Sky programmiert.
          Es erkennt alle eingebundenen Instanzen nach dem Muster:

          ecovacs-deebot.x
          

          und legt pro erkanntem Sauger eine eigene Struktur an:

          0_userdata.0.Sauger.<Gerätename>
          

          Beispiel:

          0_userdata.0.Sauger.Luna
          0_userdata.0.Sauger.Sky
          

          Bei einem anderen Benutzer entstehen automatisch dessen eigene Gerätenamen.
          ENTHALTENE SKRIPTE

          1. 01_Ecovacs_Universal_Setup_v6.0.js
            Aufgabe:
          • erkennt alle ecovacs-deebot.* Instanzen
          • versucht den Gerätenamen automatisch zu lesen
          • erkennt die Map-ID
          • erkennt alle spotAreas / Räume
          • legt die benötigten 0_userdata-Datenpunkte an

          Betrieb:

          • EINMAL starten
          • danach wieder AUS

          Wenn später ein neuer Sauger hinzukommt:

          • Setup erneut kurz starten
          • danach wieder AUS
          1. 02_Ecovacs_Universal_MapSource_v6.1.js
            Aufgabe:
          • sucht pro Sauger automatisch nach einer Kartenquelle

          • bevorzugt:
            ecovacs-deebot.x.map.<mapId>.map64

          • versucht bei vorhandenem loadMapImage die Karte regelmäßig neu anzufordern

          • schreibt die erkannte Quelle nach:

            0_userdata.0.Sauger.<Name>.Map.Source.type
            0_userdata.0.Sauger.<Name>.Map.Source.state
            0_userdata.0.Sauger.<Name>.Map.Source.image
            0_userdata.0.Sauger.<Name>.Map.Source.status

          Betrieb:

          • dauerhaft AN

          Hinweis:
          Die aktuelle VIS v6.4 verwendet das Kartenbild nicht mehr als Hintergrund.
          Der MapSource kann trotzdem aktiv bleiben, weil er weiterhin universell die
          Kartenquelle ermittelt und für Diagnose / spätere Erweiterungen verfügbar hält.

          1. 03_Ecovacs_Universal_Position_Fahrspur_v6.0.js

          Aufgabe:

          • liest pro Sauger:

            ecovacs-deebot.x.map.deebotPosition

          • berechnet daraus die Position auf der Karte

          • aktualisiert:
            Map.pixelXcss
            Map.pixelYcss
            Map.rotateCss

          • zeichnet eine Fahrspur

          • setzt die Fahrspur bei einem neuen Reinigungsvorgang zurück

          • übernimmt Status und Positionsname, sofern vorhanden

          Betrieb:

          • dauerhaft AN
          1. 04_Ecovacs_Universal_RaumSync_v6.0.js

          Aufgabe:

          • synchronisiert pro Raum:

            markForNextSpotAreaCleaning

          mit:

          0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
          

          Dadurch weiß die VIS, ob ein Raum ausgewählt ist.

          Betrieb:

          • dauerhaft AN
          1. 05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
            Aufgabe:
          • durchsucht jeden automatisch erkannten Raum nach Geometriedaten

          • sucht unter anderem nach:
            polygon
            boundary
            boundaries
            coordinates
            coords
            points
            vertices
            outline
            contour
            rect
            bounds

          • wandelt gefundene Raumgeometrien in SVG-Punkte um

          • speichert pro Raum:

            Rooms.<id>.geometryState
            Rooms.<id>.geometryType
            Rooms.<id>.polygonPoints
            Rooms.<id>.geometryStatus

          Betrieb:

          • dauerhaft AN

          Wenn ein Sauger keine Raumgeometrie bereitstellt:

          • polygonPoints bleibt leer
          • die VIS kann dann keine Raumfläche zeichnen
          1. 06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js

          Aufgabe:

          • erzeugt die komplette VIS-Darstellung
          • dunkler Hintergrund
          • automatisch erkannte farbige Raumflächen
          • Raumflächen direkt anklickbar
          • Fahrspur
          • Roboterposition
          • KEINE zusätzlichen Raum-Buttons mehr

          Betrieb:

          • dauerhaft AN

          INSTALLATION:

          1. In ioBroker unter JavaScript für jede .js-Datei ein eigenes Skript anlegen.

          2. Den Inhalt der jeweiligen .js-Datei komplett in das entsprechende
            JavaScript-Skript kopieren.

          EMPFOHLENE REIHENFOLGE
          Schritt 1:
          01_Ecovacs_Universal_Setup_v6.0.js
          kurz AN

          Warten, bis die Struktur unter:

          0_userdata.0.Sauger
          

          angelegt wurde.

          Danach:
          01 Setup AUS

          Schritt 2:
          Folgende Skripte dauerhaft AN:

          02_Ecovacs_Universal_MapSource_v6.1.js
          03_Ecovacs_Universal_Position_Fahrspur_v6.0.js
          04_Ecovacs_Universal_RaumSync_v6.0.js
          05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
          06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js
          

          WELCHE SKRIPTE SIND AN / AUS?
          01 Setup:
          AUS
          Nur bei Erstinstallation oder neuem Sauger kurz AN.

          02 MapSource:
          AN

          03 Position/Fahrspur:
          AN

          04 RaumSync:
          AN

          05 Raumflächen-Detektor:
          AN

          06 VIS:
          AN

          VIS EINRICHTEN

          In VIS ein Widget verwenden:

          Basic -> String (unescaped)
          

          Als Objekt-ID:

          0_userdata.0.Sauger.<Gerätename>.VIS.html
          

          Beispiel:

          0_userdata.0.Sauger.Luna.VIS.html
          

          Keine geschweiften Klammern verwenden.

          Also NICHT:

          {0_userdata.0.Sauger.Luna.VIS.html}
          

          sondern direkt:

          0_userdata.0.Sauger.Luna.VIS.html
          

          BEDIENUNG

          RAUM AUSWÄHLEN

          Einfach direkt auf die farbige Raumfläche in der Karte klicken.

          Beim Klick wird der zugehörige Adapter-State geschaltet:

          ecovacs-deebot.x.map.<mapId>.spotAreas.<id>.markForNextSpotAreaCleaning
          

          Der RaumSync übernimmt den Zustand anschließend nach:

          0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
          

          Ein ausgewählter Raum wird in der VIS stärker hervorgehoben.

          MEHRERE RÄUME

          Mehrere Raumflächen können nacheinander angeklickt werden.

          Die Auswahl wird direkt über die Adapter-States vorgenommen.

          FAHRSPUR

          Die Fahrspur wird aus deebotPosition erzeugt.

          Sie liegt in:

          0_userdata.0.Sauger.<Name>.Map.trailPoints
          

          Beim Beginn eines neuen Reinigungsvorgangs wird sie zurückgesetzt.

          ROBOTERPOSITION

          Die Position wird aus:

          ecovacs-deebot.x.map.deebotPosition
          

          ermittelt.

          Die VIS verwendet:

          Map.pixelXcss
          Map.pixelYcss
          Map.rotateCss
          

          RAUMFLÄCHEN-DIAGNOSE

          Wenn ein Raum nicht gezeichnet wird, prüfen:

          0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryStatus
          

          und:

          0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryState
          

          und:

          0_userdata.0.Sauger.<Name>.Rooms.<id>.polygonPoints
          

          Wenn geometryStatus meldet:

          Keine Raumgeometrie im Adapter gefunden
          

          dann liefert dieses Modell / diese Adapterversion für diesen Raum keine
          verwertbaren Polygon- oder Boundary-Daten.

          KARTENQUELLEN-DIAGNOSE

          Prüfen:

          0_userdata.0.Sauger.<Name>.Map.Source.status
          

          und:

          0_userdata.0.Sauger.<Name>.Map.Source.state
          

          POSITION PASST NICHT ZUR KARTE

          Die Koordinatenumrechnung verwendet pro Sauger:

          Map.Config.minX
          Map.Config.maxX
          Map.Config.minY
          Map.Config.maxY
          Map.Config.imgW
          Map.Config.imgH
          

          Standard:

          minX = -250
          maxX = 10700
          minY = -4350
          maxY = 400
          imgW = 540
          imgH = 231
          

          Diese Werte können pro Sauger angepasst werden.

          Wichtig:
          Die Raumflächen-Erkennung versucht selbst zu unterscheiden, ob Raumkoordinaten
          bereits Pixelwerte sind oder Weltkoordinaten umgerechnet werden müssen.

          NEUER SAUGER

          Wenn später eine weitere ecovacs-deebot.* Instanz installiert wird:

          1. Setup v6.0 kurz AN.
          2. Warten, bis der neue Sauger unter 0_userdata.0.Sauger erscheint.
          3. Setup wieder AUS.
          4. Die dauerhaft laufenden Skripte einmal neu starten.

          Danach wird der neue Sauger automatisch mit verarbeitet.

          MEHRERE SAUGER

          Alle dauerhaft laufenden Skripte suchen automatisch nach:

          0_userdata.0.Sauger.*.deebotPrefix
          

          Dadurch wird nicht nur ein einzelner Sauger verarbeitet.

          Jeder Sauger besitzt:

          • eigene Map-ID
          • eigene Räume
          • eigene Raumflächen
          • eigene Position
          • eigene Fahrspur
          • eigenes VIS.html

          WICHTIGE GRENZEN

          Das Paket kann nur Daten verwenden, die der Ecovacs-Adapter für das jeweilige
          Modell bereitstellt.

          Wenn ein Modell:

          • keine deebotPosition liefert -> keine Live-Position/Fahrspur
          • keine spotAreas liefert -> keine automatische Raumauswahl
          • keine Geometrie für spotAreas liefert -> keine gezeichneten Raumflächen

          Das Paket versucht diese Funktionen automatisch zu erkennen und ist nicht
          auf einen bestimmten Sauger oder Grundriss fest programmiert.

          Die Buttons muss man selbst erstellen wie hier:
          c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg

          Viel Spass beim ausprobieren !!

          sigi234S Online
          sigi234S Online
          sigi234
          Forum Testing Most Active
          schrieb am zuletzt editiert von
          #4

          @helfi9999
          So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?

          Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
          Immer Daten sichern!

          H 2 Antworten Letzte Antwort
          0
          • sigi234S sigi234

            @helfi9999
            So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?

            H Online
            H Online
            helfi9999
            schrieb am zuletzt editiert von
            #5

            @sigi234 Hallo werde am Wochenende es umändern zum vereinfachen.

            Intel NUC mit Iobroker

            1 Antwort Letzte Antwort
            1
            • sigi234S sigi234

              @helfi9999
              So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?

              H Online
              H Online
              helfi9999
              schrieb zuletzt editiert von
              #6

              @sigi234 das Ecovacs Skript habe ich geändert so das man nichts ersetzen muss.

              Intel NUC mit Iobroker

              sigi234S 1 Antwort Letzte Antwort
              1
              • H helfi9999

                ECOVACS ioBroker UNIVERSAL – KOMPLETTPAKET v6.4

                Dieses Paket ist universell aufgebaut.

                Es ist NICHT fest auf Namen wie Luna oder Sky programmiert.
                Es erkennt alle eingebundenen Instanzen nach dem Muster:

                ecovacs-deebot.x
                

                und legt pro erkanntem Sauger eine eigene Struktur an:

                0_userdata.0.Sauger.<Gerätename>
                

                Beispiel:

                0_userdata.0.Sauger.Luna
                0_userdata.0.Sauger.Sky
                

                Bei einem anderen Benutzer entstehen automatisch dessen eigene Gerätenamen.
                ENTHALTENE SKRIPTE

                1. 01_Ecovacs_Universal_Setup_v6.0.js
                  Aufgabe:
                • erkennt alle ecovacs-deebot.* Instanzen
                • versucht den Gerätenamen automatisch zu lesen
                • erkennt die Map-ID
                • erkennt alle spotAreas / Räume
                • legt die benötigten 0_userdata-Datenpunkte an

                Betrieb:

                • EINMAL starten
                • danach wieder AUS

                Wenn später ein neuer Sauger hinzukommt:

                • Setup erneut kurz starten
                • danach wieder AUS
                1. 02_Ecovacs_Universal_MapSource_v6.1.js
                  Aufgabe:
                • sucht pro Sauger automatisch nach einer Kartenquelle

                • bevorzugt:
                  ecovacs-deebot.x.map.<mapId>.map64

                • versucht bei vorhandenem loadMapImage die Karte regelmäßig neu anzufordern

                • schreibt die erkannte Quelle nach:

                  0_userdata.0.Sauger.<Name>.Map.Source.type
                  0_userdata.0.Sauger.<Name>.Map.Source.state
                  0_userdata.0.Sauger.<Name>.Map.Source.image
                  0_userdata.0.Sauger.<Name>.Map.Source.status

                Betrieb:

                • dauerhaft AN

                Hinweis:
                Die aktuelle VIS v6.4 verwendet das Kartenbild nicht mehr als Hintergrund.
                Der MapSource kann trotzdem aktiv bleiben, weil er weiterhin universell die
                Kartenquelle ermittelt und für Diagnose / spätere Erweiterungen verfügbar hält.

                1. 03_Ecovacs_Universal_Position_Fahrspur_v6.0.js

                Aufgabe:

                • liest pro Sauger:

                  ecovacs-deebot.x.map.deebotPosition

                • berechnet daraus die Position auf der Karte

                • aktualisiert:
                  Map.pixelXcss
                  Map.pixelYcss
                  Map.rotateCss

                • zeichnet eine Fahrspur

                • setzt die Fahrspur bei einem neuen Reinigungsvorgang zurück

                • übernimmt Status und Positionsname, sofern vorhanden

                Betrieb:

                • dauerhaft AN
                1. 04_Ecovacs_Universal_RaumSync_v6.0.js

                Aufgabe:

                • synchronisiert pro Raum:

                  markForNextSpotAreaCleaning

                mit:

                0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
                

                Dadurch weiß die VIS, ob ein Raum ausgewählt ist.

                Betrieb:

                • dauerhaft AN
                1. 05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
                  Aufgabe:
                • durchsucht jeden automatisch erkannten Raum nach Geometriedaten

                • sucht unter anderem nach:
                  polygon
                  boundary
                  boundaries
                  coordinates
                  coords
                  points
                  vertices
                  outline
                  contour
                  rect
                  bounds

                • wandelt gefundene Raumgeometrien in SVG-Punkte um

                • speichert pro Raum:

                  Rooms.<id>.geometryState
                  Rooms.<id>.geometryType
                  Rooms.<id>.polygonPoints
                  Rooms.<id>.geometryStatus

                Betrieb:

                • dauerhaft AN

                Wenn ein Sauger keine Raumgeometrie bereitstellt:

                • polygonPoints bleibt leer
                • die VIS kann dann keine Raumfläche zeichnen
                1. 06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js

                Aufgabe:

                • erzeugt die komplette VIS-Darstellung
                • dunkler Hintergrund
                • automatisch erkannte farbige Raumflächen
                • Raumflächen direkt anklickbar
                • Fahrspur
                • Roboterposition
                • KEINE zusätzlichen Raum-Buttons mehr

                Betrieb:

                • dauerhaft AN

                INSTALLATION:

                1. In ioBroker unter JavaScript für jede .js-Datei ein eigenes Skript anlegen.

                2. Den Inhalt der jeweiligen .js-Datei komplett in das entsprechende
                  JavaScript-Skript kopieren.

                EMPFOHLENE REIHENFOLGE
                Schritt 1:
                01_Ecovacs_Universal_Setup_v6.0.js
                kurz AN

                Warten, bis die Struktur unter:

                0_userdata.0.Sauger
                

                angelegt wurde.

                Danach:
                01 Setup AUS

                Schritt 2:
                Folgende Skripte dauerhaft AN:

                02_Ecovacs_Universal_MapSource_v6.1.js
                03_Ecovacs_Universal_Position_Fahrspur_v6.0.js
                04_Ecovacs_Universal_RaumSync_v6.0.js
                05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
                06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js
                

                WELCHE SKRIPTE SIND AN / AUS?
                01 Setup:
                AUS
                Nur bei Erstinstallation oder neuem Sauger kurz AN.

                02 MapSource:
                AN

                03 Position/Fahrspur:
                AN

                04 RaumSync:
                AN

                05 Raumflächen-Detektor:
                AN

                06 VIS:
                AN

                VIS EINRICHTEN

                In VIS ein Widget verwenden:

                Basic -> String (unescaped)
                

                Als Objekt-ID:

                0_userdata.0.Sauger.<Gerätename>.VIS.html
                

                Beispiel:

                0_userdata.0.Sauger.Luna.VIS.html
                

                Keine geschweiften Klammern verwenden.

                Also NICHT:

                {0_userdata.0.Sauger.Luna.VIS.html}
                

                sondern direkt:

                0_userdata.0.Sauger.Luna.VIS.html
                

                BEDIENUNG

                RAUM AUSWÄHLEN

                Einfach direkt auf die farbige Raumfläche in der Karte klicken.

                Beim Klick wird der zugehörige Adapter-State geschaltet:

                ecovacs-deebot.x.map.<mapId>.spotAreas.<id>.markForNextSpotAreaCleaning
                

                Der RaumSync übernimmt den Zustand anschließend nach:

                0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
                

                Ein ausgewählter Raum wird in der VIS stärker hervorgehoben.

                MEHRERE RÄUME

                Mehrere Raumflächen können nacheinander angeklickt werden.

                Die Auswahl wird direkt über die Adapter-States vorgenommen.

                FAHRSPUR

                Die Fahrspur wird aus deebotPosition erzeugt.

                Sie liegt in:

                0_userdata.0.Sauger.<Name>.Map.trailPoints
                

                Beim Beginn eines neuen Reinigungsvorgangs wird sie zurückgesetzt.

                ROBOTERPOSITION

                Die Position wird aus:

                ecovacs-deebot.x.map.deebotPosition
                

                ermittelt.

                Die VIS verwendet:

                Map.pixelXcss
                Map.pixelYcss
                Map.rotateCss
                

                RAUMFLÄCHEN-DIAGNOSE

                Wenn ein Raum nicht gezeichnet wird, prüfen:

                0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryStatus
                

                und:

                0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryState
                

                und:

                0_userdata.0.Sauger.<Name>.Rooms.<id>.polygonPoints
                

                Wenn geometryStatus meldet:

                Keine Raumgeometrie im Adapter gefunden
                

                dann liefert dieses Modell / diese Adapterversion für diesen Raum keine
                verwertbaren Polygon- oder Boundary-Daten.

                KARTENQUELLEN-DIAGNOSE

                Prüfen:

                0_userdata.0.Sauger.<Name>.Map.Source.status
                

                und:

                0_userdata.0.Sauger.<Name>.Map.Source.state
                

                POSITION PASST NICHT ZUR KARTE

                Die Koordinatenumrechnung verwendet pro Sauger:

                Map.Config.minX
                Map.Config.maxX
                Map.Config.minY
                Map.Config.maxY
                Map.Config.imgW
                Map.Config.imgH
                

                Standard:

                minX = -250
                maxX = 10700
                minY = -4350
                maxY = 400
                imgW = 540
                imgH = 231
                

                Diese Werte können pro Sauger angepasst werden.

                Wichtig:
                Die Raumflächen-Erkennung versucht selbst zu unterscheiden, ob Raumkoordinaten
                bereits Pixelwerte sind oder Weltkoordinaten umgerechnet werden müssen.

                NEUER SAUGER

                Wenn später eine weitere ecovacs-deebot.* Instanz installiert wird:

                1. Setup v6.0 kurz AN.
                2. Warten, bis der neue Sauger unter 0_userdata.0.Sauger erscheint.
                3. Setup wieder AUS.
                4. Die dauerhaft laufenden Skripte einmal neu starten.

                Danach wird der neue Sauger automatisch mit verarbeitet.

                MEHRERE SAUGER

                Alle dauerhaft laufenden Skripte suchen automatisch nach:

                0_userdata.0.Sauger.*.deebotPrefix
                

                Dadurch wird nicht nur ein einzelner Sauger verarbeitet.

                Jeder Sauger besitzt:

                • eigene Map-ID
                • eigene Räume
                • eigene Raumflächen
                • eigene Position
                • eigene Fahrspur
                • eigenes VIS.html

                WICHTIGE GRENZEN

                Das Paket kann nur Daten verwenden, die der Ecovacs-Adapter für das jeweilige
                Modell bereitstellt.

                Wenn ein Modell:

                • keine deebotPosition liefert -> keine Live-Position/Fahrspur
                • keine spotAreas liefert -> keine automatische Raumauswahl
                • keine Geometrie für spotAreas liefert -> keine gezeichneten Raumflächen

                Das Paket versucht diese Funktionen automatisch zu erkennen und ist nicht
                auf einen bestimmten Sauger oder Grundriss fest programmiert.

                Die Buttons muss man selbst erstellen wie hier:
                c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg

                Viel Spass beim ausprobieren !!

                sigi234S Online
                sigi234S Online
                sigi234
                Forum Testing Most Active
                schrieb zuletzt editiert von sigi234
                #7

                @helfi9999 sagte:

                01_Ecovacs_Universal_Setup_v6.0.js

                javascript.0
                2026-08-28 20:08:38.599	info	script.js.Ecovacs.Skript_1: Stopping script
                
                javascript.0
                2026-08-28 20:08:10.334	info	script.js.Ecovacs.Skript_1: registered 0 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:08:10.334	info	script.js.Ecovacs.Skript_1: Universal Setup 6.0.0Name: ChepettoBasis: 0_userdata.0.Sauger.ChepettoAdapter: ecovacs-deebot.0Map-ID: 88846xxxxRäume: 50: A1: B2: C3: D4: E
                
                javascript.0
                2026-08-28 20:08:10.334	warn	at script.js.Ecovacs.Skript_1:368:3
                
                javascript.0
                2026-08-28 20:08:10.334	warn	at script.js.Ecovacs.Skript_1:359:9
                
                javascript.0
                2026-08-28 20:08:10.334	warn	at setState (C:\SmartHome\node_modules\iobroker.javascript\build\lib\sandbox.js:2240:20)
                
                javascript.0
                2026-08-28 20:08:10.333	warn	State "0_userdata.0.Sauger.Chepetto.lastSetupReport" not found
                
                javascript.0
                2026-08-28 20:08:10.324	info	script.js.Ecovacs.Skript_1: start JavaScript (Javascript/js)
                

                EDIT:

                javascript.0
                2026-08-28 20:17:08.182	info	script.js.Ecovacs.Skript_6: registered 14 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:17:08.182	info	script.js.Ecovacs.Skript_6: Universal VIS Raumflächen v6.4: 1 Sauger geladen.
                
                javascript.0
                2026-08-28 20:17:08.177	info	script.js.Ecovacs.Skript_6: start JavaScript (Javascript/js)
                
                javascript.0
                2026-08-28 20:16:23.593	info	script.js.Ecovacs.Skript_5: registered 5 subscriptions, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:16:23.593	info	script.js.Ecovacs.Skript_5: Universal Raumflächen-Detektor v6.2: 1 Sauger geladen.
                
                javascript.0
                2026-08-28 20:16:23.567	info	script.js.Ecovacs.Skript_5: start JavaScript (Javascript/js)
                
                javascript.0
                2026-08-28 20:15:39.803	info	script.js.Ecovacs.Skript_4: registered 5 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:15:39.803	info	script.js.Ecovacs.Skript_4: Chepetto: Raum-Sync aktiv
                
                javascript.0
                2026-08-28 20:15:39.794	info	script.js.Ecovacs.Skript_4: start JavaScript (Javascript/js)
                
                javascript.0
                2026-08-28 20:14:46.691	info	script.js.Ecovacs.Skript_3: registered 3 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:14:46.690	info	script.js.Ecovacs.Skript_3: Chepetto: Position/Fahrspur aktiv (ecovacs-deebot.0.map.deebotPosition)
                
                javascript.0
                2026-08-28 20:14:46.685	info	script.js.Ecovacs.Skript_3: start JavaScript (Javascript/js)
                
                javascript.0
                2026-08-28 20:13:51.524	info	script.js.Ecovacs.Skript_2: registered 1 subscription, 1 schedule, 0 messages, 0 logs and 0 file subscriptions
                
                javascript.0
                2026-08-28 20:13:51.524	info	script.js.Ecovacs.Skript_2: Universal Map Source v6.1: 1 Sauger geladen.
                
                javascript.0
                2026-08-28 20:13:51.516	info	script.js.Ecovacs.Skript_2: start JavaScript (Javascript/js)
                

                Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                Immer Daten sichern!

                1 Antwort Letzte Antwort
                0
                • H helfi9999

                  @sigi234 das Ecovacs Skript habe ich geändert so das man nichts ersetzen muss.

                  sigi234S Online
                  sigi234S Online
                  sigi234
                  Forum Testing Most Active
                  schrieb zuletzt editiert von sigi234
                  #8

                  @helfi9999

                  Schaut nicht schlecht aus, läuft , Karte wirkt ein wenig abgeschnitten.
                  Hier die der App und vom Adapter:

                  3da0df37-1795-40e3-902f-9f4fc1aad65d-image.jpeg

                  Screenshot_20260828_205410_ECOVACS HOME.jpg

                  Hier die vom Skript:

                  94942ee2-859c-4412-9fe7-34212487f98f-image.jpeg

                  94345895-3a3f-4f78-9880-6ea8523a22fe-image.jpeg

                  Edit2:

                  Dein DP:

                  fa9b1c51-fc62-46a8-a9cd-46ed9d15169f-image.jpeg

                  Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                  Immer Daten sichern!

                  H 1 Antwort Letzte Antwort
                  0
                  • sigi234S sigi234

                    @helfi9999

                    Schaut nicht schlecht aus, läuft , Karte wirkt ein wenig abgeschnitten.
                    Hier die der App und vom Adapter:

                    3da0df37-1795-40e3-902f-9f4fc1aad65d-image.jpeg

                    Screenshot_20260828_205410_ECOVACS HOME.jpg

                    Hier die vom Skript:

                    94942ee2-859c-4412-9fe7-34212487f98f-image.jpeg

                    94345895-3a3f-4f78-9880-6ea8523a22fe-image.jpeg

                    Edit2:

                    Dein DP:

                    fa9b1c51-fc62-46a8-a9cd-46ed9d15169f-image.jpeg

                    H Online
                    H Online
                    helfi9999
                    schrieb zuletzt editiert von
                    #9

                    @sigi234 das abgeschittene hängt damit zusammen das es die Außenkante des Raumes ist wo der Sauger läuft. Das heißt er nimmt die Daten des Saugers. Wenn es keine Verbindung zum nächsten Raum gibt steht da irgendetwas wie z.B. ein Schrank wo der Sauger nicht drunter herfahren kann. Ich weiß noch nicht wie ich dieses ändern kann.

                    Intel NUC mit Iobroker

                    1 Antwort Letzte Antwort
                    0
                    • H helfi9999

                      ECOVACS ioBroker UNIVERSAL – KOMPLETTPAKET v6.4

                      Dieses Paket ist universell aufgebaut.

                      Es ist NICHT fest auf Namen wie Luna oder Sky programmiert.
                      Es erkennt alle eingebundenen Instanzen nach dem Muster:

                      ecovacs-deebot.x
                      

                      und legt pro erkanntem Sauger eine eigene Struktur an:

                      0_userdata.0.Sauger.<Gerätename>
                      

                      Beispiel:

                      0_userdata.0.Sauger.Luna
                      0_userdata.0.Sauger.Sky
                      

                      Bei einem anderen Benutzer entstehen automatisch dessen eigene Gerätenamen.
                      ENTHALTENE SKRIPTE

                      1. 01_Ecovacs_Universal_Setup_v6.0.js
                        Aufgabe:
                      • erkennt alle ecovacs-deebot.* Instanzen
                      • versucht den Gerätenamen automatisch zu lesen
                      • erkennt die Map-ID
                      • erkennt alle spotAreas / Räume
                      • legt die benötigten 0_userdata-Datenpunkte an

                      Betrieb:

                      • EINMAL starten
                      • danach wieder AUS

                      Wenn später ein neuer Sauger hinzukommt:

                      • Setup erneut kurz starten
                      • danach wieder AUS
                      1. 02_Ecovacs_Universal_MapSource_v6.1.js
                        Aufgabe:
                      • sucht pro Sauger automatisch nach einer Kartenquelle

                      • bevorzugt:
                        ecovacs-deebot.x.map.<mapId>.map64

                      • versucht bei vorhandenem loadMapImage die Karte regelmäßig neu anzufordern

                      • schreibt die erkannte Quelle nach:

                        0_userdata.0.Sauger.<Name>.Map.Source.type
                        0_userdata.0.Sauger.<Name>.Map.Source.state
                        0_userdata.0.Sauger.<Name>.Map.Source.image
                        0_userdata.0.Sauger.<Name>.Map.Source.status

                      Betrieb:

                      • dauerhaft AN

                      Hinweis:
                      Die aktuelle VIS v6.4 verwendet das Kartenbild nicht mehr als Hintergrund.
                      Der MapSource kann trotzdem aktiv bleiben, weil er weiterhin universell die
                      Kartenquelle ermittelt und für Diagnose / spätere Erweiterungen verfügbar hält.

                      1. 03_Ecovacs_Universal_Position_Fahrspur_v6.0.js

                      Aufgabe:

                      • liest pro Sauger:

                        ecovacs-deebot.x.map.deebotPosition

                      • berechnet daraus die Position auf der Karte

                      • aktualisiert:
                        Map.pixelXcss
                        Map.pixelYcss
                        Map.rotateCss

                      • zeichnet eine Fahrspur

                      • setzt die Fahrspur bei einem neuen Reinigungsvorgang zurück

                      • übernimmt Status und Positionsname, sofern vorhanden

                      Betrieb:

                      • dauerhaft AN
                      1. 04_Ecovacs_Universal_RaumSync_v6.0.js

                      Aufgabe:

                      • synchronisiert pro Raum:

                        markForNextSpotAreaCleaning

                      mit:

                      0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
                      

                      Dadurch weiß die VIS, ob ein Raum ausgewählt ist.

                      Betrieb:

                      • dauerhaft AN
                      1. 05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
                        Aufgabe:
                      • durchsucht jeden automatisch erkannten Raum nach Geometriedaten

                      • sucht unter anderem nach:
                        polygon
                        boundary
                        boundaries
                        coordinates
                        coords
                        points
                        vertices
                        outline
                        contour
                        rect
                        bounds

                      • wandelt gefundene Raumgeometrien in SVG-Punkte um

                      • speichert pro Raum:

                        Rooms.<id>.geometryState
                        Rooms.<id>.geometryType
                        Rooms.<id>.polygonPoints
                        Rooms.<id>.geometryStatus

                      Betrieb:

                      • dauerhaft AN

                      Wenn ein Sauger keine Raumgeometrie bereitstellt:

                      • polygonPoints bleibt leer
                      • die VIS kann dann keine Raumfläche zeichnen
                      1. 06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js

                      Aufgabe:

                      • erzeugt die komplette VIS-Darstellung
                      • dunkler Hintergrund
                      • automatisch erkannte farbige Raumflächen
                      • Raumflächen direkt anklickbar
                      • Fahrspur
                      • Roboterposition
                      • KEINE zusätzlichen Raum-Buttons mehr

                      Betrieb:

                      • dauerhaft AN

                      INSTALLATION:

                      1. In ioBroker unter JavaScript für jede .js-Datei ein eigenes Skript anlegen.

                      2. Den Inhalt der jeweiligen .js-Datei komplett in das entsprechende
                        JavaScript-Skript kopieren.

                      EMPFOHLENE REIHENFOLGE
                      Schritt 1:
                      01_Ecovacs_Universal_Setup_v6.0.js
                      kurz AN

                      Warten, bis die Struktur unter:

                      0_userdata.0.Sauger
                      

                      angelegt wurde.

                      Danach:
                      01 Setup AUS

                      Schritt 2:
                      Folgende Skripte dauerhaft AN:

                      02_Ecovacs_Universal_MapSource_v6.1.js
                      03_Ecovacs_Universal_Position_Fahrspur_v6.0.js
                      04_Ecovacs_Universal_RaumSync_v6.0.js
                      05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js
                      06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js
                      

                      WELCHE SKRIPTE SIND AN / AUS?
                      01 Setup:
                      AUS
                      Nur bei Erstinstallation oder neuem Sauger kurz AN.

                      02 MapSource:
                      AN

                      03 Position/Fahrspur:
                      AN

                      04 RaumSync:
                      AN

                      05 Raumflächen-Detektor:
                      AN

                      06 VIS:
                      AN

                      VIS EINRICHTEN

                      In VIS ein Widget verwenden:

                      Basic -> String (unescaped)
                      

                      Als Objekt-ID:

                      0_userdata.0.Sauger.<Gerätename>.VIS.html
                      

                      Beispiel:

                      0_userdata.0.Sauger.Luna.VIS.html
                      

                      Keine geschweiften Klammern verwenden.

                      Also NICHT:

                      {0_userdata.0.Sauger.Luna.VIS.html}
                      

                      sondern direkt:

                      0_userdata.0.Sauger.Luna.VIS.html
                      

                      BEDIENUNG

                      RAUM AUSWÄHLEN

                      Einfach direkt auf die farbige Raumfläche in der Karte klicken.

                      Beim Klick wird der zugehörige Adapter-State geschaltet:

                      ecovacs-deebot.x.map.<mapId>.spotAreas.<id>.markForNextSpotAreaCleaning
                      

                      Der RaumSync übernimmt den Zustand anschließend nach:

                      0_userdata.0.Sauger.<Name>.Rooms.<id>.selected
                      

                      Ein ausgewählter Raum wird in der VIS stärker hervorgehoben.

                      MEHRERE RÄUME

                      Mehrere Raumflächen können nacheinander angeklickt werden.

                      Die Auswahl wird direkt über die Adapter-States vorgenommen.

                      FAHRSPUR

                      Die Fahrspur wird aus deebotPosition erzeugt.

                      Sie liegt in:

                      0_userdata.0.Sauger.<Name>.Map.trailPoints
                      

                      Beim Beginn eines neuen Reinigungsvorgangs wird sie zurückgesetzt.

                      ROBOTERPOSITION

                      Die Position wird aus:

                      ecovacs-deebot.x.map.deebotPosition
                      

                      ermittelt.

                      Die VIS verwendet:

                      Map.pixelXcss
                      Map.pixelYcss
                      Map.rotateCss
                      

                      RAUMFLÄCHEN-DIAGNOSE

                      Wenn ein Raum nicht gezeichnet wird, prüfen:

                      0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryStatus
                      

                      und:

                      0_userdata.0.Sauger.<Name>.Rooms.<id>.geometryState
                      

                      und:

                      0_userdata.0.Sauger.<Name>.Rooms.<id>.polygonPoints
                      

                      Wenn geometryStatus meldet:

                      Keine Raumgeometrie im Adapter gefunden
                      

                      dann liefert dieses Modell / diese Adapterversion für diesen Raum keine
                      verwertbaren Polygon- oder Boundary-Daten.

                      KARTENQUELLEN-DIAGNOSE

                      Prüfen:

                      0_userdata.0.Sauger.<Name>.Map.Source.status
                      

                      und:

                      0_userdata.0.Sauger.<Name>.Map.Source.state
                      

                      POSITION PASST NICHT ZUR KARTE

                      Die Koordinatenumrechnung verwendet pro Sauger:

                      Map.Config.minX
                      Map.Config.maxX
                      Map.Config.minY
                      Map.Config.maxY
                      Map.Config.imgW
                      Map.Config.imgH
                      

                      Standard:

                      minX = -250
                      maxX = 10700
                      minY = -4350
                      maxY = 400
                      imgW = 540
                      imgH = 231
                      

                      Diese Werte können pro Sauger angepasst werden.

                      Wichtig:
                      Die Raumflächen-Erkennung versucht selbst zu unterscheiden, ob Raumkoordinaten
                      bereits Pixelwerte sind oder Weltkoordinaten umgerechnet werden müssen.

                      NEUER SAUGER

                      Wenn später eine weitere ecovacs-deebot.* Instanz installiert wird:

                      1. Setup v6.0 kurz AN.
                      2. Warten, bis der neue Sauger unter 0_userdata.0.Sauger erscheint.
                      3. Setup wieder AUS.
                      4. Die dauerhaft laufenden Skripte einmal neu starten.

                      Danach wird der neue Sauger automatisch mit verarbeitet.

                      MEHRERE SAUGER

                      Alle dauerhaft laufenden Skripte suchen automatisch nach:

                      0_userdata.0.Sauger.*.deebotPrefix
                      

                      Dadurch wird nicht nur ein einzelner Sauger verarbeitet.

                      Jeder Sauger besitzt:

                      • eigene Map-ID
                      • eigene Räume
                      • eigene Raumflächen
                      • eigene Position
                      • eigene Fahrspur
                      • eigenes VIS.html

                      WICHTIGE GRENZEN

                      Das Paket kann nur Daten verwenden, die der Ecovacs-Adapter für das jeweilige
                      Modell bereitstellt.

                      Wenn ein Modell:

                      • keine deebotPosition liefert -> keine Live-Position/Fahrspur
                      • keine spotAreas liefert -> keine automatische Raumauswahl
                      • keine Geometrie für spotAreas liefert -> keine gezeichneten Raumflächen

                      Das Paket versucht diese Funktionen automatisch zu erkennen und ist nicht
                      auf einen bestimmten Sauger oder Grundriss fest programmiert.

                      Die Buttons muss man selbst erstellen wie hier:
                      c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg

                      Viel Spass beim ausprobieren !!

                      H Online
                      H Online
                      helfi9999
                      schrieb zuletzt editiert von
                      #10

                      @mrbungle64 wäre das nicht etwas was man eventuell in deinen Adapter einbauen könnte

                      Intel NUC mit Iobroker

                      1 Antwort Letzte Antwort
                      0
                      • H Online
                        H Online
                        helfi9999
                        schrieb zuletzt editiert von
                        #11

                        TADO SKRIPT
                        Hallo zusammen ich habe die Tado Thermostate und verschiedene Fenstersensoren deshalb habe ich ein Skript das automatisch die Thermostate sucht und diese unter 0_userdata.0.Heizung erstellt dort kann man die Sensoren eintragen und div. Einstellungen machen:


                        c28847c1-58af-4a7f-b3e6-305ffe03cc81-image.jpeg

                        // ============================================================
                        // Heizung / Tado dynamisch
                        // Version: 2.3.0
                        //
                        // Ziele:
                        // - Tado-Heizräume automatisch finden
                        // - Warmwasser automatisch ausschließen
                        // - Fenster-/Türsensoren automatisch in ioBroker suchen
                        // - Sensoren anhand von Raum-/Objektnamen automatisch zuordnen
                        // - KEINE persönlichen Sensor-IDs im Skript
                        // - Bis zu 6 Sensoren pro Raum
                        // - Gleiche Gruppe = UND
                        // - Unterschiedliche Gruppen = ODER
                        //
                        // WICHTIG:
                        // Eine automatische Zuordnung ist nur möglich, wenn im Objektpfad
                        // oder common.name ein passender Raumname erkennbar ist.
                        // Nicht eindeutig erkennbare Sensoren bleiben leer und können
                        // unter 0_userdata.0.Heizung.<Raum>.Sensoren manuell ergänzt werden.
                        // ============================================================
                        
                        const VERSION = '2.3.0';
                        const ROOT = '0_userdata.0.Heizung';
                        const DEFAULT_DELAY_SECONDS = 10;
                        const SENSOR_SLOTS = 6;
                        
                        // Adapter-/Pfad-Hinweise für typische Fenster-/Türsensoren.
                        // Das sind KEINE persönlichen IDs, sondern nur Suchmuster.
                        const SENSOR_SELECTORS = [
                           'alias.0.*',
                           'deconz.0.*',
                           'zigbee.0.*',
                           'mqtt.0.*'
                        ];
                        
                        const rooms = {};
                        const subscriptions = [];
                        
                        function logInfo(msg) {
                           log(`[Heizung v${VERSION}] ${msg}`, 'info');
                        }
                        
                        function logWarn(msg) {
                           log(`[Heizung v${VERSION}] ${msg}`, 'warn');
                        }
                        
                        function logError(msg) {
                           log(`[Heizung v${VERSION}] ${msg}`, 'error');
                        }
                        
                        function safeName(name) {
                           return String(name || '')
                               .trim()
                               .replace(/[.\s]+/g, '_')
                               .replace(/[äÄ]/g, 'ae')
                               .replace(/[öÖ]/g, 'oe')
                               .replace(/[üÜ]/g, 'ue')
                               .replace(/ß/g, 'ss')
                               .replace(/[^a-zA-Z0-9_-]/g, '_');
                        }
                        
                        function normalizeName(name) {
                           return String(name || '')
                               .toLowerCase()
                               .replace(/ä/g, 'ae')
                               .replace(/ö/g, 'oe')
                               .replace(/ü/g, 'ue')
                               .replace(/ß/g, 'ss')
                               .replace(/[^a-z0-9]/g, '');
                        }
                        
                        function getCommonName(obj, fallback) {
                           if (!obj || !obj.common || obj.common.name === undefined) return fallback;
                        
                           if (typeof obj.common.name === 'string') return obj.common.name;
                        
                           if (typeof obj.common.name === 'object' && obj.common.name !== null) {
                               return obj.common.name.de ||
                                      obj.common.name.en ||
                                      Object.values(obj.common.name)[0] ||
                                      fallback;
                           }
                        
                           return fallback;
                        }
                        
                        function objectExists(id) {
                           return !!getObject(id);
                        }
                        
                        function ensureState(id, def, common) {
                           if (!objectExists(id)) {
                               createState(id, def, Object.assign({
                                   name: id.split('.').pop(),
                                   read: true,
                                   write: true
                               }, common || {}));
                           }
                        }
                        
                        function setAck(id, value) {
                           setState(id, value, true);
                        }
                        
                        function getVal(id, def) {
                           const state = getState(id);
                           if (!state || state.val === null || state.val === undefined) return def;
                           return state.val;
                        }
                        
                        function parseExpectedValue(raw) {
                           const text = String(raw ?? '').trim();
                        
                           if (text.toLowerCase() === 'true') return true;
                           if (text.toLowerCase() === 'false') return false;
                           if (text !== '' && !isNaN(Number(text))) return Number(text);
                        
                           return text;
                        }
                        
                        function valuesEqual(actual, expectedRaw) {
                           const expected = parseExpectedValue(expectedRaw);
                        
                           if (expected === true) {
                               return actual === true || actual === 1 || actual === '1' || actual === 'true';
                           }
                        
                           if (expected === false) {
                               return actual === false || actual === 0 || actual === '0' || actual === 'false';
                           }
                        
                           return actual == expected;
                        }
                        
                        function isLikelyOpenCloseState(id, obj) {
                           if (!obj || !obj.common) return false;
                        
                           const role = String(obj.common.role || '').toLowerCase();
                           const name = String(getCommonName(obj, '') || '').toLowerCase();
                           const idLower = String(id || '').toLowerCase();
                        
                           // Typische Rollen / Namen / IDs
                           if (
                               role.includes('sensor.open') ||
                               role.includes('state') && (
                                   name.includes('fenster') ||
                                   name.includes('window') ||
                                   name.includes('tür') ||
                                   name.includes('tuer') ||
                                   name.includes('door')
                               )
                           ) return true;
                        
                           if (
                               idLower.endsWith('.open') ||
                               idLower.includes('window') ||
                               idLower.includes('fenster') ||
                               idLower.includes('door') ||
                               idLower.includes('tuer')
                           ) return true;
                        
                           if (
                               name.includes('fenster') ||
                               name.includes('window') ||
                               name.includes('tür') ||
                               name.includes('tuer') ||
                               name.includes('door')
                           ) return true;
                        
                           return false;
                        }
                        
                        function getObjectSearchText(id, obj) {
                           const parts = [id];
                        
                           if (obj) {
                               parts.push(getCommonName(obj, ''));
                        
                               if (obj.common) {
                                   if (obj.common.desc) parts.push(obj.common.desc);
                                   if (obj.common.role) parts.push(obj.common.role);
                               }
                           }
                        
                           return normalizeName(parts.join(' '));
                        }
                        
                        function roomAliases(roomName) {
                           const normalized = normalizeName(roomName);
                           const aliases = new Set([normalized]);
                        
                           // Allgemeine sprachliche Varianten, keine persönlichen IDs
                           const generic = {
                               buero: ['arbeitszimmer', 'office'],
                               arbeitszimmer: ['buero', 'office'],
                               gaestewc: ['gaestetoilette', 'guestwc', 'guesttoilet'],
                               schlafzimmer: ['bedroom'],
                               wohnzimmer: ['livingroom'],
                               badezimmer: ['bad', 'bathroom'],
                               kinderzimmer: ['kidsroom', 'childroom'],
                               keller: ['basement'],
                               spielzimmer: ['playroom'],
                               flur: ['hall', 'hallway']
                           };
                        
                           if (generic[normalized]) {
                               for (const a of generic[normalized]) aliases.add(normalizeName(a));
                           }
                        
                           return Array.from(aliases);
                        }
                        
                        function scoreSensorForRoom(roomName, id, obj) {
                           const text = getObjectSearchText(id, obj);
                           const aliases = roomAliases(roomName);
                        
                           let score = 0;
                        
                           for (const alias of aliases) {
                               if (!alias) continue;
                               if (text.includes(alias)) {
                                   score = Math.max(score, alias === normalizeName(roomName) ? 100 : 80);
                               }
                           }
                        
                           if (score === 0) return 0;
                        
                           const name = String(getCommonName(obj, '') || '').toLowerCase();
                           const idLower = String(id).toLowerCase();
                        
                           if (
                               name.includes('fenster') ||
                               name.includes('window') ||
                               name.includes('tür') ||
                               name.includes('tuer') ||
                               name.includes('door')
                           ) score += 20;
                        
                           if (
                               idLower.endsWith('.open') ||
                               idLower.includes('open') ||
                               idLower.includes('door') ||
                               idLower.includes('window') ||
                               idLower.includes('fenster')
                           ) score += 10;
                        
                           return score;
                        }
                        
                        function discoverCandidateSensors() {
                           const ids = new Set();
                        
                           // Breite Suche über typische Adapter
                           for (const selector of SENSOR_SELECTORS) {
                               try {
                                   $(selector).each(id => ids.add(id));
                               } catch (e) {}
                           }
                        
                           const found = [];
                        
                           for (const id of ids) {
                               const obj = getObject(id);
                               if (!obj || obj.type !== 'state') continue;
                               if (!isLikelyOpenCloseState(id, obj)) continue;
                        
                               found.push({
                                   id,
                                   obj
                               });
                           }
                        
                           return found;
                        }
                        
                        function detectOpenValue(id) {
                           const obj = getObject(id);
                           const state = getState(id);
                        
                           // Wenn boolean, ist "true" normalerweise offen.
                           if (obj && obj.common && obj.common.type === 'boolean') {
                               return 'true';
                           }
                        
                           // Bei number/string ist eine sichere automatische Interpretation
                           // nicht immer möglich. Daher nur offensichtliche Fälle.
                           if (obj && obj.common && obj.common.states) {
                               const states = obj.common.states;
                        
                               for (const key of Object.keys(states)) {
                                   const label = String(states[key] || '').toLowerCase();
                                   if (
                                       label.includes('open') ||
                                       label.includes('opened') ||
                                       label.includes('offen')
                                   ) {
                                       return String(key);
                                   }
                               }
                           }
                        
                           // Fallback: bei .open ebenfalls true
                           if (String(id).toLowerCase().endsWith('.open')) {
                               return 'true';
                           }
                        
                           return '';
                        }
                        
                        function autoAssignSensorsToRoom(room, candidates) {
                           const matches = [];
                        
                           for (const candidate of candidates) {
                               const score = scoreSensorForRoom(room.name, candidate.id, candidate.obj);
                               if (score > 0) {
                                   matches.push({
                                       id: candidate.id,
                                       obj: candidate.obj,
                                       score,
                                       openValue: detectOpenValue(candidate.id)
                                   });
                               }
                           }
                        
                           matches.sort((a, b) => {
                               if (b.score !== a.score) return b.score - a.score;
                               return a.id.localeCompare(b.id);
                           });
                        
                           // Doppelte Objekt-IDs vermeiden und max. SENSOR_SLOTS
                           const unique = [];
                           const seen = new Set();
                        
                           for (const m of matches) {
                               if (seen.has(m.id)) continue;
                               seen.add(m.id);
                               unique.push(m);
                               if (unique.length >= SENSOR_SLOTS) break;
                           }
                        
                           return unique;
                        }
                        
                        function sensorIsOpen(sensor) {
                           if (!sensor.id || sensor.openValue === '') return false;
                        
                           const state = getState(sensor.id);
                           if (!state) return false;
                        
                           return valuesEqual(state.val, sensor.openValue);
                        }
                        
                        // Gleiche Gruppe = UND
                        // Unterschiedliche Gruppen = ODER
                        function roomShouldBeOff(room) {
                           const activeSensors = room.sensors.filter(s => s.id && s.openValue !== '');
                        
                           if (activeSensors.length === 0) return false;
                        
                           const groups = {};
                        
                           for (const sensor of activeSensors) {
                               const group = Math.max(1, Number(sensor.group) || 1);
                               if (!groups[group]) groups[group] = [];
                               groups[group].push(sensor);
                           }
                        
                           for (const groupId of Object.keys(groups)) {
                               const sensors = groups[groupId];
                               let allOpen = true;
                        
                               for (const sensor of sensors) {
                                   if (!sensorIsOpen(sensor)) {
                                       allOpen = false;
                                       break;
                                   }
                               }
                        
                               if (allOpen) return true;
                           }
                        
                           return false;
                        }
                        
                        function clearTimers(room) {
                           if (room.timer) {
                               clearTimeout(room.timer);
                               room.timer = null;
                           }
                        
                           if (room.followUpTimer) {
                               clearTimeout(room.followUpTimer);
                               room.followUpTimer = null;
                           }
                        }
                        
                        function switchRoomOff(room) {
                           const currentPower = getState(room.powerId);
                        
                           if (!currentPower || currentPower.val !== 'OFF') {
                               setState(room.powerId, 'OFF');
                               logInfo(`${room.name}: Heizung ausgeschaltet`);
                           }
                        
                           setAck(room.stateWindowOpen, true);
                           setAck(room.stateHeatingDisabled, true);
                        }
                        
                        function switchRoomOn(room) {
                           if (room.clearId && getObject(room.clearId)) {
                               setState(room.clearId, true);
                           } else {
                               logWarn(`${room.name}: overlayClearZone nicht gefunden: ${room.clearId}`);
                           }
                        
                           setAck(room.stateWindowOpen, false);
                           setAck(room.stateHeatingDisabled, false);
                           logInfo(`${room.name}: Overlay gelöscht / Heizung wieder freigegeben`);
                        }
                        
                        function evaluateRoom(room) {
                           const shouldOff = roomShouldBeOff(room);
                           setAck(room.stateWindowOpen, shouldOff);
                        
                           if (!room.enabled) {
                               clearTimers(room);
                               setAck(room.stateHeatingDisabled, false);
                               return;
                           }
                        
                           if (room.followUpTimer) {
                               clearTimeout(room.followUpTimer);
                               room.followUpTimer = null;
                           }
                        
                           if (shouldOff) {
                               if (room.timer) {
                                   clearTimeout(room.timer);
                                   room.timer = null;
                               }
                        
                               const delayMs = Math.max(0, Number(room.delaySeconds) || 0) * 1000;
                        
                               room.timer = setTimeout(() => {
                                   if (!roomShouldBeOff(room)) {
                                       room.timer = null;
                                       return;
                                   }
                        
                                   const extraMs = Math.max(0, Number(room.extraDelaySeconds) || 0) * 1000;
                        
                                   if (extraMs > 0) {
                                       room.followUpTimer = setTimeout(() => {
                                           if (roomShouldBeOff(room)) switchRoomOff(room);
                                           room.followUpTimer = null;
                                       }, extraMs);
                                   } else {
                                       switchRoomOff(room);
                                   }
                        
                                   room.timer = null;
                               }, delayMs);
                        
                               logInfo(`${room.name}: Fenster/Tür offen, Ausschalten in ${room.delaySeconds}s geplant`);
                               return;
                           }
                        
                           if (room.timer) {
                               clearTimeout(room.timer);
                               room.timer = null;
                           }
                        
                           const extraMs = Math.max(0, Number(room.extraDelaySeconds) || 0) * 1000;
                        
                           if (extraMs > 0) {
                               room.followUpTimer = setTimeout(() => {
                                   if (!roomShouldBeOff(room)) switchRoomOn(room);
                                   room.followUpTimer = null;
                               }, extraMs);
                           } else {
                               switchRoomOn(room);
                           }
                        }
                        
                        function removeSubscriptions() {
                           while (subscriptions.length) {
                               const sub = subscriptions.pop();
                               try {
                                   unsubscribe(sub);
                               } catch (e) {}
                           }
                        }
                        
                        function readRoomConfig(room) {
                           room.enabled = getVal(room.cfgEnabled, true) === true;
                           room.delaySeconds = Number(getVal(room.cfgDelay, DEFAULT_DELAY_SECONDS));
                           room.extraDelaySeconds = Number(getVal(room.cfgExtraDelay, 0));
                           room.sensors = [];
                        
                           for (let i = 1; i <= SENSOR_SLOTS; i++) {
                               const base = `${room.base}.Sensoren.Sensor${i}`;
                        
                               room.sensors.push({
                                   slot: i,
                                   id: String(getVal(`${base}.Id`, '') || '').trim(),
                                   openValue: String(getVal(`${base}.OffenWert`, '') ?? '').trim(),
                                   group: Number(getVal(`${base}.Gruppe`, i)) || i
                               });
                           }
                        }
                        
                        function subscribeRoom(room) {
                           for (const sensor of room.sensors) {
                               if (!sensor.id) continue;
                        
                               if (!getObject(sensor.id)) {
                                   logWarn(`${room.name}: Sensor${sensor.slot} nicht gefunden: ${sensor.id}`);
                                   continue;
                               }
                        
                               const sub = on({ id: sensor.id, change: 'ne' }, () => {
                                   logInfo(`${room.name}: Änderung an Sensor${sensor.slot} (${sensor.id})`);
                                   evaluateRoom(room);
                               });
                        
                               subscriptions.push(sub);
                           }
                        
                           const configIds = [
                               room.cfgEnabled,
                               room.cfgDelay,
                               room.cfgExtraDelay
                           ];
                        
                           for (let i = 1; i <= SENSOR_SLOTS; i++) {
                               const base = `${room.base}.Sensoren.Sensor${i}`;
                               configIds.push(`${base}.Id`);
                               configIds.push(`${base}.OffenWert`);
                               configIds.push(`${base}.Gruppe`);
                           }
                        
                           for (const cfgId of configIds) {
                               const sub = on({ id: cfgId, change: 'ne' }, () => {
                                   logInfo(`${room.name}: Konfiguration geändert`);
                                   rebuildRuntime();
                               });
                        
                               subscriptions.push(sub);
                           }
                        }
                        
                        function buildUserdataForRoom(room, autoMatches) {
                           const base = `${ROOT}.${safeName(room.name)}`;
                           room.base = base;
                        
                           room.cfgEnabled = `${base}.Aktiv`;
                           room.cfgDelay = `${base}.AusschaltverzoegerungSekunden`;
                           room.cfgExtraDelay = `${base}.ZusatzverzoegerungSekunden`;
                        
                           room.stateRoomName = `${base}.TadoRaumName`;
                           room.stateRoomId = `${base}.TadoRoomId`;
                           room.statePowerId = `${base}.TadoPowerId`;
                           room.stateClearId = `${base}.TadoClearId`;
                           room.stateWindowOpen = `${base}.FensterOffen`;
                           room.stateHeatingDisabled = `${base}.HeizungDurchFensterAus`;
                           room.stateAutoFound = `${base}.AutomatischGefundeneSensoren`;
                        
                           ensureState(room.cfgEnabled, true, {
                               name: `${room.name} - Fenstersteuerung aktiv`,
                               type: 'boolean',
                               role: 'switch.enable',
                               def: true
                           });
                        
                           ensureState(room.cfgDelay, DEFAULT_DELAY_SECONDS, {
                               name: `${room.name} - Ausschaltverzögerung Sekunden`,
                               type: 'number',
                               role: 'level',
                               min: 0,
                               def: DEFAULT_DELAY_SECONDS
                           });
                        
                           ensureState(room.cfgExtraDelay, 0, {
                               name: `${room.name} - Zusatzverzögerung Sekunden`,
                               type: 'number',
                               role: 'level',
                               min: 0,
                               def: 0
                           });
                        
                           for (let i = 1; i <= SENSOR_SLOTS; i++) {
                               const sensorBase = `${base}.Sensoren.Sensor${i}`;
                               const match = autoMatches[i - 1];
                        
                               ensureState(`${sensorBase}.Id`, match ? match.id : '', {
                                   name: `${room.name} - Sensor ${i} Objekt-ID`,
                                   type: 'string',
                                   role: 'text',
                                   def: match ? match.id : ''
                               });
                        
                               ensureState(`${sensorBase}.OffenWert`, match ? match.openValue : '', {
                                   name: `${room.name} - Sensor ${i} Offen-Wert`,
                                   type: 'string',
                                   role: 'text',
                                   def: match ? match.openValue : ''
                               });
                        
                               // Jeder automatisch gefundene Sensor bekommt zunächst eine eigene Gruppe
                               // => Sensor1 ODER Sensor2 ODER Sensor3 ...
                               ensureState(`${sensorBase}.Gruppe`, i, {
                                   name: `${room.name} - Sensor ${i} Gruppe`,
                                   type: 'number',
                                   role: 'value',
                                   min: 1,
                                   max: SENSOR_SLOTS,
                                   def: i
                               });
                           }
                        
                           ensureState(room.stateRoomName, room.name, {
                               name: `${room.name} - Tado Raumname`,
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: room.name
                           });
                        
                           ensureState(room.stateRoomId, String(room.roomId), {
                               name: `${room.name} - Tado Room ID`,
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: String(room.roomId)
                           });
                        
                           ensureState(room.statePowerId, room.powerId, {
                               name: `${room.name} - Tado Power Datenpunkt`,
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: room.powerId
                           });
                        
                           ensureState(room.stateClearId, room.clearId, {
                               name: `${room.name} - Tado Overlay-Clear Datenpunkt`,
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: room.clearId
                           });
                        
                           ensureState(room.stateWindowOpen, false, {
                               name: `${room.name} - Fenster/Tür offen`,
                               type: 'boolean',
                               role: 'indicator',
                               write: false,
                               def: false
                           });
                        
                           ensureState(room.stateHeatingDisabled, false, {
                               name: `${room.name} - Heizung wegen Fenster/Tür aus`,
                               type: 'boolean',
                               role: 'indicator.heating',
                               write: false,
                               def: false
                           });
                        
                           ensureState(room.stateAutoFound, autoMatches.length, {
                               name: `${room.name} - Anzahl automatisch gefundener Sensoren`,
                               type: 'number',
                               role: 'value',
                               write: false,
                               def: autoMatches.length
                           });
                        
                           setAck(room.stateRoomName, room.name);
                           setAck(room.stateRoomId, String(room.roomId));
                           setAck(room.statePowerId, room.powerId);
                           setAck(room.stateClearId, room.clearId);
                           setAck(room.stateAutoFound, autoMatches.length);
                        }
                        
                        function isExcludedTadoZone(roomName, roomId) {
                           const normalized = normalizeName(roomName);
                        
                           if (
                               normalized === 'warmwasser' ||
                               normalized === 'hotwater' ||
                               normalized === 'domestichotwater' ||
                               String(roomId) === '0'
                           ) {
                               return true;
                           }
                        
                           return false;
                        }
                        
                        function discoverTadoRooms() {
                           const found = [];
                           const ids = [];
                        
                           try {
                               $('tado.0.*.Rooms.*.setting.power').each(id => ids.push(id));
                           } catch (e) {
                               logError(`Tado-Suche fehlgeschlagen: ${e.message}`);
                               return found;
                           }
                        
                           for (const powerId of ids) {
                               const match = powerId.match(/^(tado\.0\.[^.]+\.Rooms\.([^.]+))\.setting\.power$/);
                               if (!match) continue;
                        
                               const roomPrefix = match[1];
                               const roomId = match[2];
                               const roomObj = getObject(roomPrefix);
                        
                               let roomName = getCommonName(roomObj, `Room_${roomId}`);
                        
                               if (!roomName || roomName === `Room_${roomId}`) {
                                   const settingObj = getObject(`${roomPrefix}.setting`);
                                   roomName = getCommonName(settingObj, roomName);
                               }
                        
                               if (isExcludedTadoZone(roomName, roomId)) {
                                   logInfo(`Tado-Zone übersprungen: ${roomName} (RoomId ${roomId})`);
                                   continue;
                               }
                        
                               found.push({
                                   key: roomPrefix,
                                   name: roomName,
                                   roomId,
                                   roomPrefix,
                                   powerId,
                                   clearId: `${roomPrefix}.overlayClearZone`,
                                   timer: null,
                                   followUpTimer: null,
                                   sensors: []
                               });
                           }
                        
                           return found;
                        }
                        
                        function rebuildRuntime() {
                           removeSubscriptions();
                        
                           for (const key of Object.keys(rooms)) {
                               clearTimers(rooms[key]);
                               readRoomConfig(rooms[key]);
                               subscribeRoom(rooms[key]);
                           }
                        
                           for (const key of Object.keys(rooms)) {
                               evaluateRoom(rooms[key]);
                           }
                        }
                        
                        function init() {
                           ensureState(`${ROOT}.Version`, VERSION, {
                               name: 'Heizung Script Version',
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: VERSION
                           });
                        
                           ensureState(`${ROOT}.GefundeneTadoRaeume`, 0, {
                               name: 'Anzahl gefundener Tado-Räume',
                               type: 'number',
                               role: 'value',
                               write: false,
                               def: 0
                           });
                        
                           ensureState(`${ROOT}.GefundeneSensorKandidaten`, 0, {
                               name: 'Anzahl gefundener Sensor-Kandidaten',
                               type: 'number',
                               role: 'value',
                               write: false,
                               def: 0
                           });
                        
                           ensureState(`${ROOT}.LetzterScan`, '', {
                               name: 'Letzter Tado/Sensor-Scan',
                               type: 'string',
                               role: 'text',
                               write: false,
                               def: ''
                           });
                        
                           setAck(`${ROOT}.Version`, VERSION);
                        
                           const foundRooms = discoverTadoRooms();
                           const candidates = discoverCandidateSensors();
                        
                           setAck(`${ROOT}.GefundeneSensorKandidaten`, candidates.length);
                        
                           if (foundRooms.length === 0) {
                               logWarn('Keine Tado-Heizräume gefunden.');
                               setAck(`${ROOT}.GefundeneTadoRaeume`, 0);
                               setAck(`${ROOT}.LetzterScan`, new Date().toISOString());
                               return;
                           }
                        
                           for (const room of foundRooms) {
                               const autoMatches = autoAssignSensorsToRoom(room, candidates);
                        
                               rooms[room.key] = room;
                               buildUserdataForRoom(room, autoMatches);
                        
                               logInfo(`Tado-Raum gefunden: ${room.name} (RoomId ${room.roomId})`);
                        
                               if (autoMatches.length > 0) {
                                   for (const match of autoMatches) {
                                       logInfo(`${room.name}: Sensor automatisch zugeordnet: ${match.id}`);
                                   }
                               } else {
                                   logWarn(`${room.name}: Kein Sensor eindeutig automatisch zugeordnet`);
                               }
                           }
                        
                           setAck(`${ROOT}.GefundeneTadoRaeume`, foundRooms.length);
                           setAck(`${ROOT}.LetzterScan`, new Date().toISOString());
                        
                           rebuildRuntime();
                        
                           logInfo(`${foundRooms.length} Tado-Heizraum/Räume gefunden`);
                           logInfo(`${candidates.length} mögliche Fenster-/Türsensoren gefunden`);
                           logInfo(`Konfiguration unter ${ROOT}`);
                           logInfo('Script gestartet');
                        }
                        
                        init();
                        
                        


                        und eine Anleitung:

                        HEIZUNG / TADO DYNAMISCH v2.2.0
                        Anleitung für die ERSTE Einrichtung in ioBroker
                        =================================================
                        
                        Diese Anleitung ist so geschrieben, als würdest du die Heizungssteuerung
                        zum ersten Mal einrichten und hättest bisher noch KEIN entsprechendes
                        JavaScript dafür laufen.
                        
                        -------------------------------------------------
                        1. WAS DU BRAUCHST
                        -------------------------------------------------
                        
                        Damit das Skript funktioniert, sollten folgende Dinge vorhanden sein:
                        
                        - ioBroker
                        - JavaScript-Adapter in ioBroker
                        - Tado-Adapter in ioBroker
                        - Deine Tado-Heizräume müssen im Tado-Adapter sichtbar sein
                        - Fenster- oder Türsensoren müssen bereits in ioBroker vorhanden sein
                        
                        Die Fenster-/Türsensoren können z. B. kommen aus:
                        
                        - deCONZ
                        - Zigbee
                        - MQTT
                        - Alias
                        - Nuki
                        - anderen Adaptern
                        
                        Wichtig ist nur:
                        Der Sensor muss in ioBroker einen Datenpunkt haben, der sich beim
                        Öffnen und Schließen verändert.
                        
                        
                        -------------------------------------------------
                        2. WAS DAS SKRIPT MACHT
                        -------------------------------------------------
                        
                        Das Skript sucht beim Start automatisch deine Tado-Heizräume.
                        
                        Für jeden gefundenen Tado-Heizraum wird unter
                        
                        0_userdata.0.Heizung
                        
                        automatisch ein eigener Bereich angelegt.
                        
                        Beispiel:
                        
                        0_userdata.0.Heizung.Schlafzimmer
                        0_userdata.0.Heizung.Wohnzimmer
                        0_userdata.0.Heizung.Buero
                        0_userdata.0.Heizung.Badezimmer
                        
                        Warmwasser wird NICHT als Heizraum verwendet.
                        
                        Du musst die Tado-Room-IDs also nicht selbst suchen und auch nicht
                        im Skript eintragen.
                        
                        
                        -------------------------------------------------
                        3. SKRIPT IN IOBROKER ANLEGEN
                        -------------------------------------------------
                        
                        1. Öffne in ioBroker den JavaScript-Adapter.
                        
                        2. Erstelle ein neues JavaScript.
                        
                        3. Öffne die Datei:
                        
                          Heizung_Tado_Dynamisch_v2.2.0.js
                        
                        4. Kopiere den kompletten Inhalt der Datei.
                        
                        5. Füge ihn in das neue ioBroker-JavaScript ein.
                        
                        6. Speichere das Skript.
                        
                        7. Starte das Skript.
                        
                        
                        -------------------------------------------------
                        4. PRÜFEN, OB TADO-RÄUME GEFUNDEN WURDEN
                        -------------------------------------------------
                        
                        Gehe in ioBroker zu:
                        
                        Objekte
                        -> 0_userdata.0
                        -> Heizung
                        
                        Dort sollten jetzt automatisch deine Tado-Heizräume erscheinen.
                        
                        Zum Beispiel:
                        
                        Heizung
                        ├── Badezimmer
                        ├── Buero
                        ├── Schlafzimmer
                        ├── Wohnzimmer
                        └── ...
                        
                        Zusätzlich gibt es:
                        
                        0_userdata.0.Heizung.Version
                        0_userdata.0.Heizung.GefundeneTadoRaeume
                        0_userdata.0.Heizung.LetzterScan
                        
                        Bei "GefundeneTadoRaeume" siehst du, wie viele Räume das Skript
                        gefunden hat.
                        
                        
                        -------------------------------------------------
                        5. WARMWASSER
                        -------------------------------------------------
                        
                        Warmwasser ist keine Heizungszone für Fensterkontakte.
                        
                        Darum wird Warmwasser von diesem Skript übersprungen.
                        
                        Du musst für Warmwasser keine Sensoren eintragen.
                        
                        
                        -------------------------------------------------
                        6. WO DU DIE FENSTERSENSOREN EINTRÄGST
                        -------------------------------------------------
                        
                        Für jeden Raum findest du den Bereich:
                        
                        0_userdata.0.Heizung.<Raum>.Sensoren
                        
                        Beispiel Schlafzimmer:
                        
                        0_userdata.0.Heizung.Schlafzimmer.Sensoren
                        
                        Darin gibt es:
                        
                        Sensor1
                        Sensor2
                        Sensor3
                        Sensor4
                        Sensor5
                        Sensor6
                        
                        Jeder Sensor hat drei Werte:
                        
                        Id
                        OffenWert
                        Gruppe
                        
                        
                        -------------------------------------------------
                        7. SENSOR-ID EINTRAGEN
                        -------------------------------------------------
                        
                        Bei:
                        
                        SensorX.Id
                        
                        trägst du die vollständige Objekt-ID deines Fenster- oder
                        Türsensors ein.
                        
                        Beispiel:
                        
                        0_userdata.0.Heizung.Schlafzimmer.Sensoren.Sensor1.Id
                        
                        Dort könnte stehen:
                        
                        alias.0.Schlafzimmer.Schlafzimmer_links.SCHLAFZIMMER LINKS
                        
                        oder:
                        
                        deconz.0.Sensors.7.open
                        
                        oder:
                        
                        mqtt.0.nuki.4939398F.doorsensorState
                        
                        
                        SO FINDEST DU DIE SENSOR-ID:
                        
                        1. Gehe in ioBroker auf "Objekte".
                        2. Suche deinen Fenster- oder Türsensor.
                        3. Suche den Datenpunkt, der sich beim Öffnen/Schließen verändert.
                        4. Kopiere die komplette Objekt-ID.
                        5. Trage diese ID bei SensorX.Id ein.
                        
                        
                        -------------------------------------------------
                        8. OFFENWERT EINTRAGEN
                        -------------------------------------------------
                        
                        Bei:
                        
                        SensorX.OffenWert
                        
                        trägst du den Wert ein, der bedeutet:
                        
                        "Fenster oder Tür ist offen"
                        
                        Bei vielen normalen Fensterkontakten ist das:
                        
                        true
                        
                        Beispiel:
                        
                        Sensor1.OffenWert = true
                        
                        Beim vorhandenen Nuki-Türsensor kann z. B. gelten:
                        
                        Sensor1.OffenWert = 3
                        
                        Wenn du nicht weißt, welcher Wert "offen" bedeutet:
                        
                        1. Öffne den Original-Sensor unter "Objekte".
                        2. Fenster/Tür schließen und Wert ansehen.
                        3. Fenster/Tür öffnen und Wert ansehen.
                        4. Den Wert bei geöffnetem Zustand bei "OffenWert" eintragen.
                        
                        
                        -------------------------------------------------
                        9. GRUPPE - SO FUNKTIONIERT UND / ODER
                        -------------------------------------------------
                        
                        Du musst nirgendwo extra "UND" oder "ODER" einstellen.
                        
                        Das macht das Skript automatisch über den Wert "Gruppe".
                        
                        MERKSATZ:
                        
                        Gleiche Gruppe = UND
                        Verschiedene Gruppen = ODER
                        
                        
                        BEISPIEL 1:
                        Heizung soll ausgehen, sobald EINES von zwei Fenstern offen ist.
                        
                        Sensor1.Gruppe = 1
                        Sensor2.Gruppe = 2
                        
                        Das bedeutet:
                        
                        Sensor1 ODER Sensor2
                        
                        
                        BEISPIEL 2:
                        Heizung soll erst ausgehen, wenn BEIDE Fenster offen sind.
                        
                        Sensor1.Gruppe = 1
                        Sensor2.Gruppe = 1
                        
                        Das bedeutet:
                        
                        Sensor1 UND Sensor2
                        
                        
                        -------------------------------------------------
                        10. NORMALE EMPFEHLUNG BEI MEHREREN FENSTERN
                        -------------------------------------------------
                        
                        Wenn die Heizung schon ausgehen soll, sobald EIN Fenster offen ist,
                        gib jedem Fenster eine eigene Gruppe.
                        
                        Beispiel mit 3 Fenstern:
                        
                        Sensor1.Gruppe = 1
                        Sensor2.Gruppe = 2
                        Sensor3.Gruppe = 3
                        
                        Dann gilt:
                        
                        Sensor1 ODER Sensor2 ODER Sensor3
                        
                        
                        -------------------------------------------------
                        11. BEISPIEL SCHLAFZIMMER
                        -------------------------------------------------
                        
                        Angenommen, du hast zwei Fenster.
                        
                        Sensor1:
                        
                        Id =
                        alias.0.Schlafzimmer.Schlafzimmer_links.SCHLAFZIMMER LINKS
                        
                        OffenWert =
                        true
                        
                        Gruppe =
                        1
                        
                        
                        Sensor2:
                        
                        Id =
                        alias.0.Schlafzimmer.Schlafzimmer_rechts.SCHLAFZIMMER RECHTS
                        
                        OffenWert =
                        true
                        
                        Gruppe =
                        2
                        
                        Ergebnis:
                        
                        Sobald EINES der beiden Fenster offen ist, startet die
                        Ausschaltverzögerung.
                        
                        
                        -------------------------------------------------
                        12. BEISPIEL BÜRO
                        -------------------------------------------------
                        
                        Wenn dein Büro diese Sensoren verwendet:
                        
                        deconz.0.Sensors.7.open
                        deconz.0.Sensors.8.open
                        
                        dann:
                        
                        Sensor1.Id = deconz.0.Sensors.7.open
                        Sensor1.OffenWert = true
                        Sensor1.Gruppe = 1
                        
                        Sensor2.Id = deconz.0.Sensors.8.open
                        Sensor2.OffenWert = true
                        Sensor2.Gruppe = 2
                        
                        Dann reicht EIN geöffnetes Fenster aus.
                        
                        
                        -------------------------------------------------
                        13. SONDERBEISPIEL MIT UND + ODER
                        -------------------------------------------------
                        
                        Beispiel:
                        
                        (Sensor1 UND Sensor2) ODER Sensor3
                        
                        Dann stellst du ein:
                        
                        Sensor1.Gruppe = 1
                        Sensor2.Gruppe = 1
                        Sensor3.Gruppe = 2
                        
                        Weil Sensor1 und Sensor2 in derselben Gruppe sind, müssen beide
                        offen sein.
                        
                        Sensor3 ist in einer anderen Gruppe und wirkt deshalb als ODER.
                        
                        
                        -------------------------------------------------
                        14. RAUM AKTIVIEREN ODER DEAKTIVIEREN
                        -------------------------------------------------
                        
                        Unter jedem Raum gibt es:
                        
                        0_userdata.0.Heizung.<Raum>.Aktiv
                        
                        true =
                        Fenstersteuerung ist für den Raum eingeschaltet
                        
                        false =
                        Fenstersteuerung ist für den Raum ausgeschaltet
                        
                        Normalerweise lässt du den Wert auf:
                        
                        true
                        
                        
                        -------------------------------------------------
                        15. AUSSCHALTVERZÖGERUNG EINSTELLEN
                        -------------------------------------------------
                        
                        Unter jedem Raum gibt es:
                        
                        0_userdata.0.Heizung.<Raum>.AusschaltverzoegerungSekunden
                        
                        Hier legst du fest, wie lange ein Fenster offen sein muss,
                        bevor die Heizung ausgeschaltet wird.
                        
                        Beispiele:
                        
                        0  = sofort
                        10 = nach 10 Sekunden
                        30 = nach 30 Sekunden
                        60 = nach 60 Sekunden
                        
                        Standardwert:
                        
                        10
                        
                        Beispiel Schlafzimmer:
                        
                        0_userdata.0.Heizung.Schlafzimmer.AusschaltverzoegerungSekunden = 30
                        
                        Dann muss das Fenster 30 Sekunden offen bleiben, bevor die
                        Heizung ausgeschaltet wird.
                        
                        Wenn du das Fenster vorher wieder schließt, wird nicht ausgeschaltet.
                        
                        
                        -------------------------------------------------
                        16. ZUSATZVERZÖGERUNG
                        -------------------------------------------------
                        
                        Es gibt außerdem:
                        
                        0_userdata.0.Heizung.<Raum>.ZusatzverzoegerungSekunden
                        
                        Normalerweise lässt du diesen Wert auf:
                        
                        0
                        
                        Er ist nur für besondere Fälle gedacht.
                        
                        
                        -------------------------------------------------
                        17. STATUS "FensterOffen"
                        -------------------------------------------------
                        
                        Unter jedem Raum gibt es:
                        
                        0_userdata.0.Heizung.<Raum>.FensterOffen
                        
                        Dieser Datenpunkt wird vom Skript automatisch gesetzt.
                        
                        false =
                        kein relevantes Fenster / keine relevante Tür offen
                        
                        true =
                        Fenster-/Türlogik meldet offen
                        
                        Diesen Datenpunkt NICHT selbst ändern.
                        
                        
                        -------------------------------------------------
                        18. STATUS "HeizungDurchFensterAus"
                        -------------------------------------------------
                        
                        Unter jedem Raum gibt es:
                        
                        0_userdata.0.Heizung.<Raum>.HeizungDurchFensterAus
                        
                        false =
                        Heizung wurde nicht durch die Fenstersteuerung ausgeschaltet
                        
                        true =
                        Heizung wurde wegen Fenster/Tür ausgeschaltet
                        
                        Diesen Datenpunkt NICHT selbst ändern.
                        
                        
                        -------------------------------------------------
                        19. TADO-DATENPUNKTE
                        -------------------------------------------------
                        
                        Unter jedem Raum findest du außerdem:
                        
                        TadoRaumName
                        TadoRoomId
                        TadoPowerId
                        TadoClearId
                        
                        Diese Werte findet das Skript automatisch.
                        
                        Dort musst du normalerweise nichts eintragen oder ändern.
                        
                        
                        -------------------------------------------------
                        20. NICHT BENUTZTE SENSORPLÄTZE
                        -------------------------------------------------
                        
                        Wenn du z. B. nur zwei Fenster hast, benutzt du:
                        
                        Sensor1
                        Sensor2
                        
                        Sensor3 bis Sensor6 lässt du einfach leer.
                        
                        Wichtig:
                        
                        SensorX.Id leer = Sensor wird ignoriert
                        
                        
                        -------------------------------------------------
                        21. TEST NACH DER EINRICHTUNG
                        -------------------------------------------------
                        
                        Nach dem Eintragen der Sensoren solltest du jeden Raum testen.
                        
                        Beispiel Schlafzimmer:
                        
                        1. Prüfen:
                          Aktiv = true
                        
                        2. Prüfen:
                          Sensor1.Id ist eingetragen
                        
                        3. Prüfen:
                          OffenWert stimmt
                        
                        4. Fenster öffnen
                        
                        5. Prüfen:
                          FensterOffen wechselt auf true
                        
                        6. Ausschaltverzögerung abwarten
                        
                        7. Prüfen:
                          HeizungDurchFensterAus wechselt auf true
                        
                        8. Fenster wieder schließen
                        
                        9. Prüfen:
                          FensterOffen wechselt auf false
                        
                        10. Die Heizung wird wieder für den normalen Tado-Zeitplan freigegeben.
                        
                        
                        -------------------------------------------------
                        22. WENN ES NICHT FUNKTIONIERT
                        -------------------------------------------------
                        
                        Prüfe zuerst:
                        
                        - Läuft das JavaScript?
                        - Läuft der Tado-Adapter?
                        - Wurden Tado-Räume gefunden?
                        - Ist "Aktiv" auf true?
                        - Ist die Sensor-ID vollständig und korrekt?
                        - Ändert sich der Original-Sensor beim Öffnen?
                        - Ist "OffenWert" richtig?
                        - Ist die Gruppe richtig eingestellt?
                        
                        Zusätzlich kannst du im ioBroker-JavaScript-Log schauen.
                        
                        Dort sollte bei einer Sensoränderung z. B. etwas erscheinen wie:
                        
                        Schlafzimmer: Änderung an Sensor1 (...)
                        
                        
                        -------------------------------------------------
                        23. KURZÜBERSICHT - WAS MUSS ICH SELBST EINTRAGEN?
                        -------------------------------------------------
                        
                        Für jeden Fenster-/Türsensor:
                        
                        1. Id
                          = vollständige ioBroker Objekt-ID
                        
                        2. OffenWert
                          = Wert, der "offen" bedeutet
                        
                        3. Gruppe
                          = bestimmt UND / ODER
                        
                        
                        Optional pro Raum:
                        
                        4. Aktiv
                          = true oder false
                        
                        5. AusschaltverzoegerungSekunden
                          = z. B. 10, 30 oder 60
                        
                        
                        NICHT selbst eintragen musst du:
                        
                        - TadoRaumName
                        - TadoRoomId
                        - TadoPowerId
                        - TadoClearId
                        - FensterOffen
                        - HeizungDurchFensterAus
                        
                        
                        -------------------------------------------------
                        24. WICHTIGSTER MERKSATZ
                        -------------------------------------------------
                        
                        Für normale Räume mit mehreren Fenstern, bei denen EIN offenes
                        Fenster reichen soll:
                        
                        Sensor1.Gruppe = 1
                        Sensor2.Gruppe = 2
                        Sensor3.Gruppe = 3
                        
                        Also:
                        
                        JEDER SENSOR EINE EIGENE GRUPPE.
                        
                        

                        Viel Spass beim ausprobieren

                        Intel NUC mit Iobroker

                        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

                        156

                        Online

                        33.0k

                        Benutzende

                        83.7k

                        Themen

                        1.3m

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

                        • Du hast noch kein Konto? Registrieren

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