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
    383

  • 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
12 Beiträge 2 Kommentatoren 439 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
    #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
                      • H Online
                        H Online
                        helfi9999
                        schrieb zuletzt editiert von helfi9999
                        #12

                        Habe die einzelnen Sachen getrennt:
                        Musikstatus für Alexa und /oder Sonos:
                        https://forum.iobroker.net/post/1352784

                        Tado mit externen Sensoren:
                        https://forum.iobroker.net/post/1352786

                        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

                        198

                        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