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
    391

  • 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 456 Aufrufe 4 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • H Online
    H Online
    helfi9999
    schrieb am zuletzt editiert von helfi9999
    #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 helfi9999
                #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.

                Habe es jetzt meine Skripte getrennt:
                https://forum.iobroker.net/post/1352789

                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 helfi9999
                  #10

                  @mrbungle64 wäre das nicht etwas was man eventuell in deinen Adapter einbauen könnte
                  https://forum.iobroker.net/post/1352789

                  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

                      Ecovacs Deebot dynamische Karte mit Fahrspur:
                      https://forum.iobroker.net/post/1352789

                      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

                      327

                      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