<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[verschiedene Skripte]]></title><description><![CDATA[<p dir="auto">Hallo zusammen,<br />
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:</p>
<p dir="auto">z.B.<br />
Musikstatus je Raum mit Alexa und/oder Sonos mit States Erstellung.Voraussetzung ist der Alexa Adapter und der Sonos Adapter:<br />
<img src="/assets/uploads/files/1777195984619-884f31a3-f1c5-4988-9e9c-7e4f0f68c5d3-image.jpeg" alt="884f31a3-f1c5-4988-9e9c-7e4f0f68c5d3-image.jpeg" class=" img-fluid img-markdown" width="1563" height="349" /><br />
Ihr müsst nur die Geräte im Skript ändern.<br />
</p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>
/*************************************************
* 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 &gt; 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 &amp;&amp; alexaProvider) {
       writeStatus(room, alexaProvider);
       return;
   }

   if (
       sonosPlaying &amp;&amp;
       room.config.sonosMap &amp;&amp;
       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" }, () =&gt; evalRoom(room));

   setTimeout(() =&gt; {
       evalRoom(room);
   }, 1000);
}

/*********************** START ***********************/

rooms.forEach(initRoom);
</code></pre>
<p dir="auto"></p></section></section><br />
Lautstärken Reset in Verbindung mit dem Skript Musikstatus<br />
Funktionen:<p></p>
<ul>
<li>
<ul>
<li>Reagiert auf Statusänderung eines Raums</li>
</ul>
</li>
<li>
<ul>
<li>Prüft auf Zielstatus "nichts"</li>
</ul>
</li>
<li>
<ul>
<li>Setzt Lautstärke definierter Geräte nach optionaler Verzögerung</li>
</ul>
</li>
<li>
<ul>
<li>Verhindert Mehrfach-Timer pro Raum</li>
</ul>
</li>
<li>
<ul>
<li>Optionales Debug-Logging</li>
</ul>
</li>
<li>
<ul>
<li>Alexa notificationVolume wird explizit über notificationId gesetzt</li>
</ul>
</li>
<li>
<ul>
<li>Sonderfall Kinderzimmer: nur notificationVolume, kein Player.volume</li>
</ul>
</li>
</ul>
<p dir="auto"></p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>/**
 * 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' &amp;&amp; isFinite(volume) &amp;&amp; volume &gt;= 0 &amp;&amp; volume &lt;= 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 -&gt; ${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 -&gt; ${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 &amp;&amp; room.name ? room.name : 'Unbekannter Raum'}: keine Geräte konfiguriert`);
        activeTimers[room &amp;&amp; room.name ? room.name : 'unknown'] = null;
        return;
    }

    room.devices.forEach(device =&gt; setDeviceVolume(device, room.name));
    activeTimers[room.name] = null;
}

function scheduleReset(room) {
    clearRoomTimer(room.name);

    const delayMs = typeof room.delayMs === 'number' &amp;&amp; room.delayMs &gt;= 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(() =&gt; {
        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 =&gt; {
        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');

</code></pre>
<p dir="auto"></p></section></section><br />
es werden noch weitere folgen<p></p>
]]></description><link>https://forum.iobroker.net/topic/84397/verschiedene-skripte</link><generator>RSS for Node</generator><lastBuildDate>Sat, 10 Oct 2026 05:16:52 GMT</lastBuildDate><atom:link href="https://forum.iobroker.net/topic/84397.rss" rel="self" type="application/rss+xml"/><pubDate>Sun, 26 Apr 2026 09:35:40 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to verschiedene Skripte on Sat, 29 Aug 2026 07:35:48 GMT]]></title><description><![CDATA[<p dir="auto">Habe die einzelnen Sachen getrennt:<br />
Musikstatus für Alexa und /oder Sonos:<br />
<a href="https://forum.iobroker.net/post/1352784">https://forum.iobroker.net/post/1352784</a></p>
<p dir="auto">Tado mit externen Sensoren:<br />
<a href="https://forum.iobroker.net/post/1352786">https://forum.iobroker.net/post/1352786</a></p>
<p dir="auto">Ecovacs Deebot dynamische Karte mit Fahrspur:<br />
<a href="https://forum.iobroker.net/post/1352789">https://forum.iobroker.net/post/1352789</a></p>
]]></description><link>https://forum.iobroker.net/post/1352785</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352785</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Sat, 29 Aug 2026 07:35:48 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Sat, 29 Aug 2026 05:30:16 GMT]]></title><description><![CDATA[<p dir="auto">TADO SKRIPT<br />
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:<br />
</p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><br />
<img src="/assets/uploads/files/1787981033391-c28847c1-58af-4a7f-b3e6-305ffe03cc81-image.jpeg" alt="c28847c1-58af-4a7f-b3e6-305ffe03cc81-image.jpeg" class=" img-fluid img-markdown" width="1603" height="721" /><br />
</section></section><p></p>
<p dir="auto"></p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>// ============================================================
// 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.&lt;Raum&gt;.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' &amp;&amp; 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 !== '' &amp;&amp; !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') &amp;&amp; (
           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 =&gt; 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 &amp;&amp; obj.common &amp;&amp; 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 &amp;&amp; obj.common &amp;&amp; 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 &gt; 0) {
           matches.push({
               id: candidate.id,
               obj: candidate.obj,
               score,
               openValue: detectOpenValue(candidate.id)
           });
       }
   }

   matches.sort((a, b) =&gt; {
       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 &gt;= 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 =&gt; s.id &amp;&amp; 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 &amp;&amp; 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(() =&gt; {
           if (!roomShouldBeOff(room)) {
               room.timer = null;
               return;
           }

           const extraMs = Math.max(0, Number(room.extraDelaySeconds) || 0) * 1000;

           if (extraMs &gt; 0) {
               room.followUpTimer = setTimeout(() =&gt; {
                   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 &gt; 0) {
       room.followUpTimer = setTimeout(() =&gt; {
           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 &lt;= 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' }, () =&gt; {
           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 &lt;= 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' }, () =&gt; {
           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 &lt;= 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
       // =&gt; 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 =&gt; 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 &gt; 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();

</code></pre>
<p dir="auto"></p></section></section><br />
und eine Anleitung:<br />
<section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>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
-&gt; 0_userdata.0
-&gt; 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.&lt;Raum&gt;.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.&lt;Raum&gt;.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.&lt;Raum&gt;.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.&lt;Raum&gt;.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.&lt;Raum&gt;.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.&lt;Raum&gt;.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.

</code></pre>
<p dir="auto"></p></section></section><p></p>
<p dir="auto">Viel Spass beim ausprobieren</p>
]]></description><link>https://forum.iobroker.net/post/1352782</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352782</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Sat, 29 Aug 2026 05:30:16 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Sat, 29 Aug 2026 07:36:01 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/mrbungle64" aria-label="Profile: mrbungle64">@<bdi>mrbungle64</bdi></a> wäre das nicht etwas was man eventuell in deinen Adapter einbauen könnte<br />
<a href="https://forum.iobroker.net/post/1352789">https://forum.iobroker.net/post/1352789</a></p>
]]></description><link>https://forum.iobroker.net/post/1352779</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352779</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Sat, 29 Aug 2026 07:36:01 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Sat, 29 Aug 2026 07:36:28 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> 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.</p>
<p dir="auto">Habe es jetzt meine Skripte getrennt:<br />
<a href="https://forum.iobroker.net/post/1352789">https://forum.iobroker.net/post/1352789</a></p>
]]></description><link>https://forum.iobroker.net/post/1352777</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352777</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Sat, 29 Aug 2026 07:36:28 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Fri, 28 Aug 2026 19:17:32 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/helfi9999" aria-label="Profile: helfi9999">@<bdi>helfi9999</bdi></a></p>
<p dir="auto">Schaut nicht schlecht aus, läuft , Karte wirkt ein wenig abgeschnitten.<br />
Hier die der App und vom Adapter:</p>
<p dir="auto"><img src="/assets/uploads/files/1787942954704-3da0df37-1795-40e3-902f-9f4fc1aad65d-image.jpeg" alt="3da0df37-1795-40e3-902f-9f4fc1aad65d-image.jpeg" class=" img-fluid img-markdown" width="1920" height="1080" /></p>
<p dir="auto"><img src="/assets/uploads/files/1787943344652-screenshot_20260828_205410_ecovacs-home.jpg" alt="Screenshot_20260828_205410_ECOVACS HOME.jpg" class=" img-fluid img-markdown" width="1080" height="2340" /></p>
<p dir="auto">Hier die vom Skript:</p>
<p dir="auto"><img src="/assets/uploads/files/1787943399592-94942ee2-859c-4412-9fe7-34212487f98f-image.jpeg" alt="94942ee2-859c-4412-9fe7-34212487f98f-image.jpeg" class=" img-fluid img-markdown" width="1920" height="1080" /></p>
<p dir="auto"><img src="/assets/uploads/files/1787943868457-94345895-3a3f-4f78-9880-6ea8523a22fe-image.jpeg" alt="94345895-3a3f-4f78-9880-6ea8523a22fe-image.jpeg" class=" img-fluid img-markdown" width="1920" height="1080" /></p>
<p dir="auto">Edit2:</p>
<p dir="auto">Dein DP:</p>
<p dir="auto"><img src="/assets/uploads/files/1787944650473-fa9b1c51-fc62-46a8-a9cd-46ed9d15169f-image.jpeg" alt="fa9b1c51-fc62-46a8-a9cd-46ed9d15169f-image.jpeg" class=" img-fluid img-markdown" width="1920" height="1080" /></p>
]]></description><link>https://forum.iobroker.net/post/1352760</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352760</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Fri, 28 Aug 2026 19:17:32 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Fri, 28 Aug 2026 18:21:24 GMT]]></title><description><![CDATA[<blockquote>
<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/helfi9999" aria-label="Profile: helfi9999">@<bdi>helfi9999</bdi></a> <a href="/post/1352255">sagte</a>:</p>
<p dir="auto">01_Ecovacs_Universal_Setup_v6.0.js</p>
</blockquote>
<pre><code>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)
</code></pre>
<p dir="auto">EDIT:</p>
<pre><code>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)
</code></pre>
]]></description><link>https://forum.iobroker.net/post/1352757</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352757</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Fri, 28 Aug 2026 18:21:24 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Fri, 28 Aug 2026 14:42:47 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> das Ecovacs Skript habe ich geändert so das man nichts ersetzen muss.</p>
]]></description><link>https://forum.iobroker.net/post/1352726</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352726</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Fri, 28 Aug 2026 14:42:47 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Wed, 26 Aug 2026 16:04:25 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/sigi234" aria-label="Profile: sigi234">@<bdi>sigi234</bdi></a> Hallo werde am Wochenende es umändern zum vereinfachen.</p>
]]></description><link>https://forum.iobroker.net/post/1352567</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352567</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Wed, 26 Aug 2026 16:04:25 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Sun, 23 Aug 2026 18:47:29 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/helfi9999" aria-label="Profile: helfi9999">@<bdi>helfi9999</bdi></a><br />
So ganz begreife ich nicht wo ich was ersetzen muss, werden die DP automatisch angelegt?</p>
]]></description><link>https://forum.iobroker.net/post/1352294</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352294</guid><dc:creator><![CDATA[sigi234]]></dc:creator><pubDate>Sun, 23 Aug 2026 18:47:29 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Fri, 28 Aug 2026 14:41:26 GMT]]></title><description><![CDATA[<p dir="auto">ECOVACS ioBroker UNIVERSAL – KOMPLETTPAKET v6.4</p>
<p dir="auto">Dieses Paket ist universell aufgebaut.</p>
<p dir="auto">Es ist NICHT fest auf Namen wie Luna oder Sky programmiert.<br />
Es erkennt alle eingebundenen Instanzen nach dem Muster:</p>
<pre><code>ecovacs-deebot.x
</code></pre>
<p dir="auto">und legt pro erkanntem Sauger eine eigene Struktur an:</p>
<pre><code>0_userdata.0.Sauger.&lt;Gerätename&gt;
</code></pre>
<p dir="auto">Beispiel:</p>
<pre><code>0_userdata.0.Sauger.Luna
0_userdata.0.Sauger.Sky
</code></pre>
<p dir="auto">Bei einem anderen Benutzer entstehen automatisch dessen eigene Gerätenamen.<br />
ENTHALTENE SKRIPTE</p>
<ol>
<li><a href="/assets/uploads/files/1787927394362-01_ecovacs_universal_setup_v6.0.js">01_Ecovacs_Universal_Setup_v6.0.js</a><br />
Aufgabe:</li>
</ol>
<ul>
<li>erkennt alle ecovacs-deebot.* Instanzen</li>
<li>versucht den Gerätenamen automatisch zu lesen</li>
<li>erkennt die Map-ID</li>
<li>erkennt alle spotAreas / Räume</li>
<li>legt die benötigten 0_userdata-Datenpunkte an</li>
</ul>
<p dir="auto">Betrieb:</p>
<ul>
<li>EINMAL starten</li>
<li>danach wieder AUS</li>
</ul>
<p dir="auto">Wenn später ein neuer Sauger hinzukommt:</p>
<ul>
<li>Setup erneut kurz starten</li>
<li>danach wieder AUS</li>
</ul>
<ol start="2">
<li><a href="/assets/uploads/files/1787927404364-02_ecovacs_universal_mapsource_v6.1.js">02_Ecovacs_Universal_MapSource_v6.1.js</a><br />
Aufgabe:</li>
</ol>
<ul>
<li>
<p dir="auto">sucht pro Sauger automatisch nach einer Kartenquelle</p>
</li>
<li>
<p dir="auto">bevorzugt:<br />
ecovacs-deebot.x.map.&lt;mapId&gt;.map64</p>
</li>
<li>
<p dir="auto">versucht bei vorhandenem loadMapImage die Karte regelmäßig neu anzufordern</p>
</li>
<li>
<p dir="auto">schreibt die erkannte Quelle nach:</p>
<p dir="auto">0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.type<br />
0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.state<br />
0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.image<br />
0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.status</p>
</li>
</ul>
<p dir="auto">Betrieb:</p>
<ul>
<li>dauerhaft AN</li>
</ul>
<p dir="auto">Hinweis:<br />
Die aktuelle VIS v6.4 verwendet das Kartenbild nicht mehr als Hintergrund.<br />
Der MapSource kann trotzdem aktiv bleiben, weil er weiterhin universell die<br />
Kartenquelle ermittelt und für Diagnose / spätere Erweiterungen verfügbar hält.</p>
<ol start="3">
<li><a href="/assets/uploads/files/1787927442541-03_ecovacs_universal_position_fahrspur_v6.0.js">03_Ecovacs_Universal_Position_Fahrspur_v6.0.js</a></li>
</ol>
<p dir="auto">Aufgabe:</p>
<ul>
<li>
<p dir="auto">liest pro Sauger:</p>
<p dir="auto">ecovacs-deebot.x.map.deebotPosition</p>
</li>
<li>
<p dir="auto">berechnet daraus die Position auf der Karte</p>
</li>
<li>
<p dir="auto">aktualisiert:<br />
Map.pixelXcss<br />
Map.pixelYcss<br />
Map.rotateCss</p>
</li>
<li>
<p dir="auto">zeichnet eine Fahrspur</p>
</li>
<li>
<p dir="auto">setzt die Fahrspur bei einem neuen Reinigungsvorgang zurück</p>
</li>
<li>
<p dir="auto">übernimmt Status und Positionsname, sofern vorhanden</p>
</li>
</ul>
<p dir="auto">Betrieb:</p>
<ul>
<li>dauerhaft AN</li>
</ul>
<ol start="4">
<li><a href="/assets/uploads/files/1787927456607-04_ecovacs_universal_raumsync_v6.0.js">04_Ecovacs_Universal_RaumSync_v6.0.js</a></li>
</ol>
<p dir="auto">Aufgabe:</p>
<ul>
<li>
<p dir="auto">synchronisiert pro Raum:</p>
<p dir="auto">markForNextSpotAreaCleaning</p>
</li>
</ul>
<p dir="auto">mit:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Rooms.&lt;id&gt;.selected
</code></pre>
<p dir="auto">Dadurch weiß die VIS, ob ein Raum ausgewählt ist.</p>
<p dir="auto">Betrieb:</p>
<ul>
<li>dauerhaft AN</li>
</ul>
<ol start="5">
<li><a href="/assets/uploads/files/1787927475568-05_ecovacs_universal_raumflaechen_detektor_v6.2.js">05_Ecovacs_Universal_Raumflaechen_Detektor_v6.2.js</a><br />
Aufgabe:</li>
</ol>
<ul>
<li>
<p dir="auto">durchsucht jeden automatisch erkannten Raum nach Geometriedaten</p>
</li>
<li>
<p dir="auto">sucht unter anderem nach:<br />
polygon<br />
boundary<br />
boundaries<br />
coordinates<br />
coords<br />
points<br />
vertices<br />
outline<br />
contour<br />
rect<br />
bounds</p>
</li>
<li>
<p dir="auto">wandelt gefundene Raumgeometrien in SVG-Punkte um</p>
</li>
<li>
<p dir="auto">speichert pro Raum:</p>
<p dir="auto">Rooms.&lt;id&gt;.geometryState<br />
Rooms.&lt;id&gt;.geometryType<br />
Rooms.&lt;id&gt;.polygonPoints<br />
Rooms.&lt;id&gt;.geometryStatus</p>
</li>
</ul>
<p dir="auto">Betrieb:</p>
<ul>
<li>dauerhaft AN</li>
</ul>
<p dir="auto">Wenn ein Sauger keine Raumgeometrie bereitstellt:</p>
<ul>
<li>polygonPoints bleibt leer</li>
<li>die VIS kann dann keine Raumfläche zeichnen</li>
</ul>
<ol start="6">
<li><a href="/assets/uploads/files/1787927544740-06_ecovacs_universal_vis_raumflaechen_v6.4.js">06_Ecovacs_Universal_VIS_Raumflaechen_v6.4.js</a></li>
</ol>
<p dir="auto">Aufgabe:</p>
<ul>
<li>erzeugt die komplette VIS-Darstellung</li>
<li>dunkler Hintergrund</li>
<li>automatisch erkannte farbige Raumflächen</li>
<li>Raumflächen direkt anklickbar</li>
<li>Fahrspur</li>
<li>Roboterposition</li>
<li>KEINE zusätzlichen Raum-Buttons mehr</li>
</ul>
<p dir="auto">Betrieb:</p>
<ul>
<li>dauerhaft AN</li>
</ul>
<p dir="auto">INSTALLATION:</p>
<ol>
<li>
<p dir="auto">In ioBroker unter JavaScript für jede .js-Datei ein eigenes Skript anlegen.</p>
</li>
<li>
<p dir="auto">Den Inhalt der jeweiligen .js-Datei komplett in das entsprechende<br />
JavaScript-Skript kopieren.</p>
</li>
</ol>
<p dir="auto">EMPFOHLENE REIHENFOLGE<br />
Schritt 1:<br />
01_Ecovacs_Universal_Setup_v6.0.js<br />
kurz AN</p>
<p dir="auto">Warten, bis die Struktur unter:</p>
<pre><code>0_userdata.0.Sauger
</code></pre>
<p dir="auto">angelegt wurde.</p>
<p dir="auto">Danach:<br />
01 Setup AUS</p>
<p dir="auto">Schritt 2:<br />
Folgende Skripte dauerhaft AN:</p>
<pre><code>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
</code></pre>
<p dir="auto">WELCHE SKRIPTE SIND AN / AUS?<br />
01 Setup:<br />
AUS<br />
Nur bei Erstinstallation oder neuem Sauger kurz AN.</p>
<p dir="auto">02 MapSource:<br />
AN</p>
<p dir="auto">03 Position/Fahrspur:<br />
AN</p>
<p dir="auto">04 RaumSync:<br />
AN</p>
<p dir="auto">05 Raumflächen-Detektor:<br />
AN</p>
<p dir="auto">06 VIS:<br />
AN</p>
<p dir="auto">VIS EINRICHTEN</p>
<p dir="auto">In VIS ein Widget verwenden:</p>
<pre><code>Basic -&gt; String (unescaped)
</code></pre>
<p dir="auto">Als Objekt-ID:</p>
<pre><code>0_userdata.0.Sauger.&lt;Gerätename&gt;.VIS.html
</code></pre>
<p dir="auto">Beispiel:</p>
<pre><code>0_userdata.0.Sauger.Luna.VIS.html
</code></pre>
<p dir="auto">Keine geschweiften Klammern verwenden.</p>
<p dir="auto">Also NICHT:</p>
<pre><code>{0_userdata.0.Sauger.Luna.VIS.html}
</code></pre>
<p dir="auto">sondern direkt:</p>
<pre><code>0_userdata.0.Sauger.Luna.VIS.html
</code></pre>
<p dir="auto">BEDIENUNG</p>
<p dir="auto">RAUM AUSWÄHLEN</p>
<p dir="auto">Einfach direkt auf die farbige Raumfläche in der Karte klicken.</p>
<p dir="auto">Beim Klick wird der zugehörige Adapter-State geschaltet:</p>
<pre><code>ecovacs-deebot.x.map.&lt;mapId&gt;.spotAreas.&lt;id&gt;.markForNextSpotAreaCleaning
</code></pre>
<p dir="auto">Der RaumSync übernimmt den Zustand anschließend nach:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Rooms.&lt;id&gt;.selected
</code></pre>
<p dir="auto">Ein ausgewählter Raum wird in der VIS stärker hervorgehoben.</p>
<p dir="auto">MEHRERE RÄUME</p>
<p dir="auto">Mehrere Raumflächen können nacheinander angeklickt werden.</p>
<p dir="auto">Die Auswahl wird direkt über die Adapter-States vorgenommen.</p>
<p dir="auto">FAHRSPUR</p>
<p dir="auto">Die Fahrspur wird aus deebotPosition erzeugt.</p>
<p dir="auto">Sie liegt in:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Map.trailPoints
</code></pre>
<p dir="auto">Beim Beginn eines neuen Reinigungsvorgangs wird sie zurückgesetzt.</p>
<p dir="auto">ROBOTERPOSITION</p>
<p dir="auto">Die Position wird aus:</p>
<pre><code>ecovacs-deebot.x.map.deebotPosition
</code></pre>
<p dir="auto">ermittelt.</p>
<p dir="auto">Die VIS verwendet:</p>
<pre><code>Map.pixelXcss
Map.pixelYcss
Map.rotateCss
</code></pre>
<p dir="auto">RAUMFLÄCHEN-DIAGNOSE</p>
<p dir="auto">Wenn ein Raum nicht gezeichnet wird, prüfen:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Rooms.&lt;id&gt;.geometryStatus
</code></pre>
<p dir="auto">und:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Rooms.&lt;id&gt;.geometryState
</code></pre>
<p dir="auto">und:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Rooms.&lt;id&gt;.polygonPoints
</code></pre>
<p dir="auto">Wenn geometryStatus meldet:</p>
<pre><code>Keine Raumgeometrie im Adapter gefunden
</code></pre>
<p dir="auto">dann liefert dieses Modell / diese Adapterversion für diesen Raum keine<br />
verwertbaren Polygon- oder Boundary-Daten.</p>
<p dir="auto">KARTENQUELLEN-DIAGNOSE</p>
<p dir="auto">Prüfen:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.status
</code></pre>
<p dir="auto">und:</p>
<pre><code>0_userdata.0.Sauger.&lt;Name&gt;.Map.Source.state
</code></pre>
<p dir="auto">POSITION PASST NICHT ZUR KARTE</p>
<p dir="auto">Die Koordinatenumrechnung verwendet pro Sauger:</p>
<pre><code>Map.Config.minX
Map.Config.maxX
Map.Config.minY
Map.Config.maxY
Map.Config.imgW
Map.Config.imgH
</code></pre>
<p dir="auto">Standard:</p>
<pre><code>minX = -250
maxX = 10700
minY = -4350
maxY = 400
imgW = 540
imgH = 231
</code></pre>
<p dir="auto">Diese Werte können pro Sauger angepasst werden.</p>
<p dir="auto">Wichtig:<br />
Die Raumflächen-Erkennung versucht selbst zu unterscheiden, ob Raumkoordinaten<br />
bereits Pixelwerte sind oder Weltkoordinaten umgerechnet werden müssen.</p>
<p dir="auto">NEUER SAUGER</p>
<p dir="auto">Wenn später eine weitere ecovacs-deebot.* Instanz installiert wird:</p>
<ol>
<li>Setup v6.0 kurz AN.</li>
<li>Warten, bis der neue Sauger unter 0_userdata.0.Sauger erscheint.</li>
<li>Setup wieder AUS.</li>
<li>Die dauerhaft laufenden Skripte einmal neu starten.</li>
</ol>
<p dir="auto">Danach wird der neue Sauger automatisch mit verarbeitet.</p>
<p dir="auto">MEHRERE SAUGER</p>
<p dir="auto">Alle dauerhaft laufenden Skripte suchen automatisch nach:</p>
<pre><code>0_userdata.0.Sauger.*.deebotPrefix
</code></pre>
<p dir="auto">Dadurch wird nicht nur ein einzelner Sauger verarbeitet.</p>
<p dir="auto">Jeder Sauger besitzt:</p>
<ul>
<li>eigene Map-ID</li>
<li>eigene Räume</li>
<li>eigene Raumflächen</li>
<li>eigene Position</li>
<li>eigene Fahrspur</li>
<li>eigenes VIS.html</li>
</ul>
<p dir="auto">WICHTIGE GRENZEN</p>
<p dir="auto">Das Paket kann nur Daten verwenden, die der Ecovacs-Adapter für das jeweilige<br />
Modell bereitstellt.</p>
<p dir="auto">Wenn ein Modell:</p>
<ul>
<li>keine deebotPosition liefert -&gt; keine Live-Position/Fahrspur</li>
<li>keine spotAreas liefert -&gt; keine automatische Raumauswahl</li>
<li>keine Geometrie für spotAreas liefert -&gt; keine gezeichneten Raumflächen</li>
</ul>
<p dir="auto">Das Paket versucht diese Funktionen automatisch zu erkennen und ist nicht<br />
auf einen bestimmten Sauger oder Grundriss fest programmiert.</p>
<p dir="auto">Die Buttons muss man selbst erstellen wie hier:<br />
<img src="/assets/uploads/files/1787928019433-c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg" alt="c42a3904-8b6b-4077-ba10-9b0379503363-image.jpeg" class=" img-fluid img-markdown" width="1056" height="546" /></p>
<p dir="auto">Viel Spass beim ausprobieren !!</p>
]]></description><link>https://forum.iobroker.net/post/1352255</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352255</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Fri, 28 Aug 2026 14:41:26 GMT</pubDate></item><item><title><![CDATA[Reply to verschiedene Skripte on Sun, 23 Aug 2026 11:03:35 GMT]]></title><description><![CDATA[<p dir="auto">Blink Kamera im Wechsel:</p>
<p dir="auto">Vorraussetzung ist der Blink Adapter und das von mir entworfene Skript: </p><section class="spoiler-wrapper"><button class="spoiler-control btn btn-default">Spoiler</button><section style="display:none" class="spoiler-content"><p></p>
<pre><code>// ===============================
// 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() &lt; 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' &amp;&amp; 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 &amp;&amp; !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 &amp;&amp; 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 &amp;&amp; neuerTimestampState.val ? String(neuerTimestampState.val) : '';

       if (neuerTimestamp &amp;&amp; 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 &gt;= 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);
</code></pre>
<p dir="auto"></p></section></section><br />
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.<p></p>
<p dir="auto"><img src="/assets/uploads/files/1787482857483-f0ad6a37-62ee-486d-86c0-a1ba28cb2530-image.jpeg" alt="f0ad6a37-62ee-486d-86c0-a1ba28cb2530-image.jpeg" class=" img-fluid img-markdown" width="399" height="356" /></p>
]]></description><link>https://forum.iobroker.net/post/1352254</link><guid isPermaLink="true">https://forum.iobroker.net/post/1352254</guid><dc:creator><![CDATA[helfi9999]]></dc:creator><pubDate>Sun, 23 Aug 2026 11:03:35 GMT</pubDate></item></channel></rss>