Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • 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. ioBroker Allgemein
  4. Homelink - Garagentorantrieb Marantec

NEWS

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    23
    1
    1.3k

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    9.3k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    14
    1
    2.5k

Homelink - Garagentorantrieb Marantec

Geplant Angeheftet Gesperrt Verschoben ioBroker Allgemein
69 Beiträge 17 Kommentatoren 10.1k Aufrufe 14 Watching
  • Ä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.
  • T TomTom24

    Hi,
    ich habe mir ein Script gebastelt mit JS-Adapter. Das funktioniert sehr gut und man kann darüber einfach Schalter in Homekit anlegen und z.b. die Garage öffnen. Läuft hier seit über einem Jahr einwandfrei. Ab und zu muss der Adapater/Host neu gestartet werden, weil der Host aus irgendwelchen Gründen dann den Client nicht mehr kennt.

    1. Datenpunkte anlegen
    2. Dann braucht ihr nur noch ein kleines Script, dass die Werte open, close, stop, lighton, lightoff in den dp_cmd schreibt.
    3. DeviceName muss so sein, wie Eure Garage in Eurer App heißt!!

    Wie gesagt, läuft hier sehr zuverlässig.

    // wenns nicht geht - IP-Adresse Garage prüfen
    // Benötigt mqtt -wenns nicht geht - IP-Adresse Garage prüfen
    //
    //
    var net = require('net'); 
    
    var dp_door = "0_userdata.0.Garage.Garage_Status_Tor"; //door state
    var dp_light = "0_userdata.0.Garage.Garage_Status_Licht"; //light state
    var dp_cmd = "0_userdata.0.Garage.Garage_Command2"; //Befehlsdatenpunkt: Mögliche Werte: open, close, stop, lighton, lightoff. Nach Beschreiben wird der Datenpunkt wieder auf leer gesetzt
    
    var deviceName = "GARAGENNAME"; 
    var host = "IP_ADRESSE";
    var port = 2785;
    
    // random client, weil nach Stromausfall das Script nicht mehr lief, erst als anderer Client angegeben wurde.
    let x = Math.floor((Math.random() * 30) + 1);
    
    var client=x.toString; // Client mit übergeben
    
    function startClient() {
        client = new net.Socket();                 // Erzeugen eines neuen Verbindungsobjekts
        client.connect(port, host, function() {     // Gerät über host:port anwählen und Verbindung erzeugen
            console.log('cliend started');
        });
        client.on('data', function(data) {          // Wenn daten ankommen, dann verarbeiten
            console.log('client received: ' + data + " for device: " + deviceName);
            var dataS = data.toString().trim();              // Datenbuffer in lesbaren Text umwandeln
            switch(dataS) {                           // je nach rückgabewert unterschiedliche Bearbeitung
                case "S;"+deviceName+";open":         // wenn open
                    setState(dp_door,"open",false);             // dann Datenpunkt setzen
                    setState(dp_door,"open",true);             // dann Datenpunkt setzen
                    // Garagentür ebenfalls öffnen
                    setState('alias.0.Garage.Garagentuer'/*Garagentuer auf zu*/, true)
                    console.log("Script Garage: "+dataS,"info");
                    break;
                case "S;"+deviceName+";closed":
                    setState(dp_door,"closed",false);
                    setState(dp_door,"closed",true);
                    // Garagentür ebenfalls schließen
                    setState('alias.0.Garage.Garagentuer'/*Garagentuer auf zu*/, false) 
                    console.log("Script Garage: "+dataS,"info");
                    break;
                case "S;"+deviceName+";opening":
                    setState(dp_door,"opening",false);
                    setState(dp_door,"opening",true);
                    console.log("Script Garage: "+dataS,"info");
                    break;
                case "S;"+deviceName+";closing":
                    setState(dp_door,"closing",false);
                    setState(dp_door,"closing",true);
                    console.log("Script Garage: "+dataS,"info");
                    break;
                case "S;"+deviceName+";lightOn":
                    setState(dp_light,"lightOn",false);
                    setState(dp_light,"lightOn",true);
                    console.log("Script Garage: "+dataS,"info");
                    break;
                case "S;"+deviceName+";lightOff":
                    setState(dp_light,"lightOff",false);
                    setState(dp_light,"lightOff",true);
                    console.log("Script Garage: "+dataS,"info");
                    break;
            }
        });
        client.on("error",(err)=>{                  // wenn ein Fehler entsteht
            console.log("client error " + err.toString());
        });
        client.on("timeout",()=>{                   // wenn timeout entsteht
            console.log("client timeout");
        });
    }
    
    function sendMessage(host, port, message) {
        return new Promise((resolve, reject) => {     // Funktion ist asynchron, daher Rückgabe promise
            if (!client) {
                console.log("No Client available :"+x);
                resolve("Error");
                return;
            }
            client.write(message);
            resolve("OK");
        });
    }
    
    onStop (function(){
         /* do something when script is stopped */
         if (client) {
            console.log('client ended ');
            client.end();
         }
    }, 1000);
    
    startClient();
    
    on(dp_cmd, function (obj) {                     // trigger wenn datenpunkt beschrieben wird
     var cmd;
     if (obj.state.ack || !obj.state.ack) {                       // aber nur wenn es sich um einen unbestätigten wert handelt
         cmd = String(obj.state.val).toLowerCase();  // Datenpunktinhalt in Kleinbuchstaben umwandeln
         console.log('execute command: ' + cmd);
         switch(cmd) {                           // je nach Datenpunktinhalt verschiedene Befehle
             case "open":                        // wenn open
                 sendMessage(host, port, "C;"+deviceName+";open"); // dann richtigen Befehl an Gerät senden
                 
                 break;
             case "close":
                 sendMessage(host, port, "C;"+deviceName+";close");
                 break;
             case "stop":
                 sendMessage(host, port, "C;"+deviceName+";stop");
                 break;
             case "lighton":
                 sendMessage(host, port, "C;"+deviceName+";lightOn");
                 break;
             case "lightoff":
                 sendMessage(host, port, "C;"+deviceName+";lightOff");
                 break;
         }
         
         setState(obj.id, {val: "", ack: false}); // datenpunkt auf leer als bestätigt setzen
     }
    });
    
    LongbowL Offline
    LongbowL Offline
    Longbow
    schrieb am zuletzt editiert von
    #59

    @tomtom24

    Hallo, ich habe das Script gerade probiert.

    Das ist die Fehlermeldung: script.js.common.Scripte.Auffahrt.Marantec_Steuerung: client error Error: connect ECONNREFUSED 192.168.7.34:2785

    Also IP stimmt, Name von dem Stick stimmt. An was kann es noch liegen?

    R 1 Antwort Letzte Antwort
    0
    • LongbowL Longbow

      @tomtom24

      Hallo, ich habe das Script gerade probiert.

      Das ist die Fehlermeldung: script.js.common.Scripte.Auffahrt.Marantec_Steuerung: client error Error: connect ECONNREFUSED 192.168.7.34:2785

      Also IP stimmt, Name von dem Stick stimmt. An was kann es noch liegen?

      R Offline
      R Offline
      Ritschy2000
      schrieb am zuletzt editiert von Ritschy2000
      #60

      @longbow hast du den "neuen" WLAN-Stick oder noch die Variante Stick mit zusätzlichem Gateway?

      Mit den neuen WLAN-Sticks funktioniert es nicht (soweit mir bekannt ist), da bei diesen die Api nicht aktiviert werden kann.

      LongbowL 1 Antwort Letzte Antwort
      0
      • R Ritschy2000

        @longbow hast du den "neuen" WLAN-Stick oder noch die Variante Stick mit zusätzlichem Gateway?

        Mit den neuen WLAN-Sticks funktioniert es nicht (soweit mir bekannt ist), da bei diesen die Api nicht aktiviert werden kann.

        LongbowL Offline
        LongbowL Offline
        Longbow
        schrieb am zuletzt editiert von
        #61

        @ritschy2000 sagte in Homelink - Garagentorantrieb Marantec:

        Variante Stic

        den WLAN Stick, gibt es die andere Variante noch?

        R 1 Antwort Letzte Antwort
        0
        • LongbowL Longbow

          @ritschy2000 sagte in Homelink - Garagentorantrieb Marantec:

          Variante Stic

          den WLAN Stick, gibt es die andere Variante noch?

          R Offline
          R Offline
          Ritschy2000
          schrieb am zuletzt editiert von
          #62

          @longbow direkt bei Maveo nicht. Bei Ebay oder anderen Online- Händlern schon.

          Suche mal nach "maveo Starter Bundle" oder "Maveo box Starterpaket".
          Ist dann Gateway + "alter" Stick .... kostet auch "nur" günstige 190 € ;)

          LongbowL 1 Antwort Letzte Antwort
          0
          • R Ritschy2000

            @longbow direkt bei Maveo nicht. Bei Ebay oder anderen Online- Händlern schon.

            Suche mal nach "maveo Starter Bundle" oder "Maveo box Starterpaket".
            Ist dann Gateway + "alter" Stick .... kostet auch "nur" günstige 190 € ;)

            LongbowL Offline
            LongbowL Offline
            Longbow
            schrieb am zuletzt editiert von
            #63

            @ritschy2000

            Danke, habe ich gemacht. Nur leider verbindet sich der Stick nicht mit dem Torantrieb. Habe den X.82, der soll aber gehen, da dieses Bundel mit allen Torantrieben ab 2014 gehen soll... Geht aber nicht leider nicht....

            Wer kennst sich mit den Torantrieben Marantec aus und kann mir helfen?

            1 Antwort Letzte Antwort
            0
            • T TomTom24

              Hi,
              ich habe mir ein Script gebastelt mit JS-Adapter. Das funktioniert sehr gut und man kann darüber einfach Schalter in Homekit anlegen und z.b. die Garage öffnen. Läuft hier seit über einem Jahr einwandfrei. Ab und zu muss der Adapater/Host neu gestartet werden, weil der Host aus irgendwelchen Gründen dann den Client nicht mehr kennt.

              1. Datenpunkte anlegen
              2. Dann braucht ihr nur noch ein kleines Script, dass die Werte open, close, stop, lighton, lightoff in den dp_cmd schreibt.
              3. DeviceName muss so sein, wie Eure Garage in Eurer App heißt!!

              Wie gesagt, läuft hier sehr zuverlässig.

              // wenns nicht geht - IP-Adresse Garage prüfen
              // Benötigt mqtt -wenns nicht geht - IP-Adresse Garage prüfen
              //
              //
              var net = require('net'); 
              
              var dp_door = "0_userdata.0.Garage.Garage_Status_Tor"; //door state
              var dp_light = "0_userdata.0.Garage.Garage_Status_Licht"; //light state
              var dp_cmd = "0_userdata.0.Garage.Garage_Command2"; //Befehlsdatenpunkt: Mögliche Werte: open, close, stop, lighton, lightoff. Nach Beschreiben wird der Datenpunkt wieder auf leer gesetzt
              
              var deviceName = "GARAGENNAME"; 
              var host = "IP_ADRESSE";
              var port = 2785;
              
              // random client, weil nach Stromausfall das Script nicht mehr lief, erst als anderer Client angegeben wurde.
              let x = Math.floor((Math.random() * 30) + 1);
              
              var client=x.toString; // Client mit übergeben
              
              function startClient() {
                  client = new net.Socket();                 // Erzeugen eines neuen Verbindungsobjekts
                  client.connect(port, host, function() {     // Gerät über host:port anwählen und Verbindung erzeugen
                      console.log('cliend started');
                  });
                  client.on('data', function(data) {          // Wenn daten ankommen, dann verarbeiten
                      console.log('client received: ' + data + " for device: " + deviceName);
                      var dataS = data.toString().trim();              // Datenbuffer in lesbaren Text umwandeln
                      switch(dataS) {                           // je nach rückgabewert unterschiedliche Bearbeitung
                          case "S;"+deviceName+";open":         // wenn open
                              setState(dp_door,"open",false);             // dann Datenpunkt setzen
                              setState(dp_door,"open",true);             // dann Datenpunkt setzen
                              // Garagentür ebenfalls öffnen
                              setState('alias.0.Garage.Garagentuer'/*Garagentuer auf zu*/, true)
                              console.log("Script Garage: "+dataS,"info");
                              break;
                          case "S;"+deviceName+";closed":
                              setState(dp_door,"closed",false);
                              setState(dp_door,"closed",true);
                              // Garagentür ebenfalls schließen
                              setState('alias.0.Garage.Garagentuer'/*Garagentuer auf zu*/, false) 
                              console.log("Script Garage: "+dataS,"info");
                              break;
                          case "S;"+deviceName+";opening":
                              setState(dp_door,"opening",false);
                              setState(dp_door,"opening",true);
                              console.log("Script Garage: "+dataS,"info");
                              break;
                          case "S;"+deviceName+";closing":
                              setState(dp_door,"closing",false);
                              setState(dp_door,"closing",true);
                              console.log("Script Garage: "+dataS,"info");
                              break;
                          case "S;"+deviceName+";lightOn":
                              setState(dp_light,"lightOn",false);
                              setState(dp_light,"lightOn",true);
                              console.log("Script Garage: "+dataS,"info");
                              break;
                          case "S;"+deviceName+";lightOff":
                              setState(dp_light,"lightOff",false);
                              setState(dp_light,"lightOff",true);
                              console.log("Script Garage: "+dataS,"info");
                              break;
                      }
                  });
                  client.on("error",(err)=>{                  // wenn ein Fehler entsteht
                      console.log("client error " + err.toString());
                  });
                  client.on("timeout",()=>{                   // wenn timeout entsteht
                      console.log("client timeout");
                  });
              }
              
              function sendMessage(host, port, message) {
                  return new Promise((resolve, reject) => {     // Funktion ist asynchron, daher Rückgabe promise
                      if (!client) {
                          console.log("No Client available :"+x);
                          resolve("Error");
                          return;
                      }
                      client.write(message);
                      resolve("OK");
                  });
              }
              
              onStop (function(){
                   /* do something when script is stopped */
                   if (client) {
                      console.log('client ended ');
                      client.end();
                   }
              }, 1000);
              
              startClient();
              
              on(dp_cmd, function (obj) {                     // trigger wenn datenpunkt beschrieben wird
               var cmd;
               if (obj.state.ack || !obj.state.ack) {                       // aber nur wenn es sich um einen unbestätigten wert handelt
                   cmd = String(obj.state.val).toLowerCase();  // Datenpunktinhalt in Kleinbuchstaben umwandeln
                   console.log('execute command: ' + cmd);
                   switch(cmd) {                           // je nach Datenpunktinhalt verschiedene Befehle
                       case "open":                        // wenn open
                           sendMessage(host, port, "C;"+deviceName+";open"); // dann richtigen Befehl an Gerät senden
                           
                           break;
                       case "close":
                           sendMessage(host, port, "C;"+deviceName+";close");
                           break;
                       case "stop":
                           sendMessage(host, port, "C;"+deviceName+";stop");
                           break;
                       case "lighton":
                           sendMessage(host, port, "C;"+deviceName+";lightOn");
                           break;
                       case "lightoff":
                           sendMessage(host, port, "C;"+deviceName+";lightOff");
                           break;
                   }
                   
                   setState(obj.id, {val: "", ack: false}); // datenpunkt auf leer als bestätigt setzen
               }
              });
              
              D Offline
              D Offline
              drapo
              schrieb am zuletzt editiert von
              #64

              @tomtom24 ich hatte das auch über längere Zeit einwandfrei und ohne Probleme am Laufen. Seit ein paar Monaten gehts es bei mir nicht mehr. Geht es bei Dir noch?

              D 1 Antwort Letzte Antwort
              0
              • D drapo

                @tomtom24 ich hatte das auch über längere Zeit einwandfrei und ohne Probleme am Laufen. Seit ein paar Monaten gehts es bei mir nicht mehr. Geht es bei Dir noch?

                D Offline
                D Offline
                drapo
                schrieb am zuletzt editiert von
                #65

                @drapo hab die box geresettet neu verbunden extra auch ein 2,4ghz wlan netz erstellt und nun funktioniert wieder alles wie eh und je

                1 Antwort Letzte Antwort
                0
                • LongbowL Offline
                  LongbowL Offline
                  Longbow
                  schrieb am zuletzt editiert von
                  #66

                  Hallo in die Runde,

                  da ich auch so einen Antrieb habe und die Box, hatte ich das Script auch so übernommen.
                  Leider hing es hin und wieder bei mir, daher habe ich mich dran gesetzt und das nun gebaut.

                  Hoffe es ist selbst erklärend und gefällt euch, wenn ihr Fragen habt, melde Euch einfach bei mir.

                  // ===== TCP-Client Garagentor (ioBroker JavaScript) =====
                  // Robust: Reconnect + Backoff, KeepAlive, sauberes Line-Parsing (S;..., R;...),
                  // AutoClose nach X Minuten mit Rest-SECONDS-Datenpunkt, DP-Autoanlage,
                  // Licht-Status als boolean, shutdown-sicher (keine DB-closed-Fehler)
                  const net = require('net');
                  
                  // ==== KONFIG ====
                  const deviceName = "Auffahrt";
                  const host = "xxx.xxx.x.xxx";
                  const port = 2785;
                  const SEND_NEWLINE = true;
                  
                  // ==== BASIS-PFAD ====
                  const base = "0_userdata.0.Eigene_Datenpunkte.Garage.";
                  
                  // ==== DATENPUNKTE ====
                  const dp_door         = base + "Garage_Status_Tor";          // "open|closed|opening|closing"
                  const dp_light        = base + "Garage_Status_Licht";        // boolean
                  const dp_cmd          = base + "Garage_Command";             // "open|close|stop|lighton|lightoff"
                  const dp_connected    = base + "Garage_TcpConnected";        // boolean
                  const dp_lastReply    = base + "Garage_LastReply";           // string
                  const dp_lastError    = base + "Garage_LastError";           // string
                  const dp_ac_enabled   = base + "AutoClose_Enabled";          // boolean
                  const dp_ac_delay     = base + "AutoClose_DelayMin";         // number (1..120)
                  const dp_ac_left_s    = base + "AutoClose_RemainingSec";     // number (Sek., read-only)
                  const dp_lastOpened   = base + "Garage_LastOpened";          // ISO-Zeit
                  const dp_lastClosed   = base + "Garage_LastClosed";          // ISO-Zeit
                  
                  // ---- Dein vorhandener Notify-DP (nicht anlegen, nur schreiben) ----
                  const dp_notify = "hm-rega.0.31016"; // bestehender Homematic-ReGa Datenpunkt/Variable
                  
                  // ---- Notify-Logik: Frühwarnung & Hysterese ----
                  const EARLY_TRIGGER_SEC = 60;        // ab so vielen Sekunden vor Auto-Close -> notify = true
                  const NOTIFY_FALSE_DELAY_MS = 5000;  // Hysterese: erst nach 5s "closed" -> notify = false
                  let notifyFalseTimer = null;
                  
                  // ==== DP-AUTOANLAGE (nur eigene 0_userdata.*) ====
                  function ensureState(id, defVal, common) {
                    if (!existsState(id)) {
                      createState(id, defVal, common, () => log(`State angelegt: ${id}`, 'info'));
                    }
                  }
                  ensureState(dp_door,        '',    { name:'Torstatus',        type:'string',  role:'text',                 read:true, write:false });
                  ensureState(dp_light,       false, { name:'Lichtstatus',      type:'boolean', role:'indicator.light',      read:true, write:false });
                  ensureState(dp_cmd,         '',    { name:'Befehl',           type:'string',  role:'text',                 read:true, write:true  });
                  ensureState(dp_connected,   false, { name:'TCP verbunden',    type:'boolean', role:'indicator.reachable',  read:true, write:false });
                  ensureState(dp_lastReply,   '',    { name:'Letzte Antwort',   type:'string',  role:'text',                 read:true, write:false });
                  ensureState(dp_lastError,   '',    { name:'Letzter Fehler',   type:'string',  role:'text',                 read:true, write:false });
                  ensureState(dp_ac_enabled,  true,  { name:'AutoClose aktiv',  type:'boolean', role:'switch',               read:true, write:true  });
                  ensureState(dp_ac_delay,    5,     { name:'AutoClose Minuten',type:'number',  role:'level', unit:'min',    read:true, write:true  });
                  ensureState(dp_ac_left_s,   0,     { name:'Rest-Sekunden',    type:'number',  role:'value', unit:'s',      read:true, write:false });
                  ensureState(dp_lastOpened,  '',    { name:'Letzte Öffnung',   type:'string',  role:'date',                 read:true, write:false });
                  ensureState(dp_lastClosed,  '',    { name:'Letzter Verschluss',type:'string', role:'date',                 read:true, write:false });
                  
                  // ==== SHUTDOWN-SAFE ====
                  let shuttingDown = false;
                  function safeSetState(id, val, ack = true) {
                    if (shuttingDown) return;
                    try { setState(id, val, ack); } catch (_) {}
                  }
                  
                  // ==== TCP ====
                  let client = null;
                  let reconnectTimer = null;
                  let reconnectDelayMs = 2000;
                  const reconnectDelayMaxMs = 30000;
                  let recvBuffer = '';
                  
                  function startClient() {
                    clearTimeout(reconnectTimer);
                    client = new net.Socket();
                    client.setKeepAlive(true, 10000);
                    client.setEncoding('utf8');
                  
                    client.connect(port, host, () => {
                      log(`[${deviceName}] Verbunden mit ${host}:${port}`, 'info');
                      safeSetState(dp_connected, true);
                      reconnectDelayMs = 2000;
                    });
                  
                    client.on('data', (chunk) => {
                      recvBuffer += chunk;
                      const parts = recvBuffer.split(/\r?\n|\r/);
                      recvBuffer = parts.pop();
                      for (const raw of parts) {
                        const line = (raw || '').trim();
                        if (line) handleIncoming(line);
                      }
                    });
                  
                    client.on('error', (err) => {
                      log(`[${deviceName}] TCP Fehler: ${err.message}`, 'warn');
                      safeSetState(dp_connected, false);
                      safeSetState(dp_lastError, err.message);
                      scheduleReconnect();
                    });
                  
                    client.on('close', (hadError) => {
                      log(`[${deviceName}] TCP geschlossen${hadError ? ' (Fehler)' : ''}`, 'warn');
                      safeSetState(dp_connected, false);
                      scheduleReconnect();
                    });
                  }
                  
                  function scheduleReconnect() {
                    try { if (client) client.destroy(); } catch (_) {}
                    client = null;
                    reconnectTimer = setTimeout(startClient, reconnectDelayMs);
                    reconnectDelayMs = Math.min(Math.round(reconnectDelayMs * 1.5), reconnectDelayMaxMs);
                  }
                  
                  // ==== AUTO-CLOSE ====
                  let autoCloseTimer = null;
                  let autoCloseDeadline = 0;
                  let autoCloseTicker = null;
                  let acInternalTrigger = false;
                  
                  function getAcEnabled() {
                    const v = getState(dp_ac_enabled)?.val;
                    return v === true || v === 1 || v === 'true';
                  }
                  function getAcDelayMin() {
                    let m = Number(getState(dp_ac_delay)?.val);
                    if (!isFinite(m) || m <= 0) m = 5;
                    return Math.max(1, Math.min(120, Math.floor(m)));
                  }
                  
                  function scheduleAutoClose() {
                    if (!getAcEnabled()) { safeSetState(dp_ac_left_s, 0); return; }
                    const min = getAcDelayMin();
                    autoCloseDeadline = Date.now() + min * 60000;
                  
                    clearTimeout(autoCloseTimer);
                    autoCloseTimer = setTimeout(performAutoClose, min * 60000);
                  
                    startAutoCloseTicker();
                    updateRemainingSeconds();
                    log(`[${deviceName}] AutoClose geplant in ${min} Min`, 'info');
                  }
                  
                  function performAutoClose() {
                    if (shuttingDown) return;
                    autoCloseTimer = null;
                    const st = getState(dp_door)?.val;
                    if (st === 'open') {
                      log(`[${deviceName}] AutoClose ausgelöst -> schließe`, 'info');
                      acInternalTrigger = true;
                      try { setState(dp_cmd, { val: 'close', ack: false }); } catch (_) {}
                    } else {
                      log(`[${deviceName}] AutoClose übersprungen (Status: ${st})`, 'info');
                    }
                    stopAutoCloseTicker();
                    safeSetState(dp_ac_left_s, 0);
                    autoCloseDeadline = 0;
                    // dp_notify bleibt bis "closed" true (Hysterese regelt das Zurücksetzen)
                  }
                  
                  function cancelAutoClose(reason = '', suppressWrite = false) {
                    if (autoCloseTimer) clearTimeout(autoCloseTimer);
                    if (autoCloseTicker) clearInterval(autoCloseTicker);
                    autoCloseTimer = null;
                    autoCloseTicker = null;
                    autoCloseDeadline = 0;
                    if (!suppressWrite) safeSetState(dp_ac_left_s, 0);
                    if (reason) log(`[${deviceName}] AutoClose abgebrochen: ${reason}`, 'info');
                  }
                  
                  function updateRemainingSeconds() {
                    if (!autoCloseDeadline || shuttingDown) return;
                    const secLeft = Math.max(0, Math.ceil((autoCloseDeadline - Date.now()) / 1000));
                    safeSetState(dp_ac_left_s, secLeft);
                  
                    // Frühwarnung: ≤ EARLY_TRIGGER_SEC -> notify true
                    if (secLeft > 0 && secLeft <= EARLY_TRIGGER_SEC) {
                      clearTimeout(notifyFalseTimer);
                      safeSetState(dp_notify, true, /*ack*/ false); // bewusst ack=false, falls Triggers benötigt
                    }
                  }
                  
                  function startAutoCloseTicker() {
                    if (autoCloseTicker) return;
                    autoCloseTicker = setInterval(updateRemainingSeconds, 1000);
                  }
                  function stopAutoCloseTicker() {
                    if (!autoCloseTicker) return;
                    clearInterval(autoCloseTicker);
                    autoCloseTicker = null;
                  }
                  
                  // ==== EINGEHEND ====
                  function handleIncoming(line) {
                    const parts = line.split(';');
                    if (parts.length < 2) {
                      log(`[${deviceName}] Unbekanntes Paket: "${line}"`, 'warn');
                      return;
                    }
                    const prefix = parts[0];
                  
                    if (prefix === 'S') {
                      const dev = parts[1];
                      const value = parts.slice(2).join(';');
                      if (dev !== deviceName) return;
                  
                      switch (value) {
                        case 'open':
                          safeSetState(dp_door, 'open');
                          safeSetState(dp_lastOpened, new Date().toISOString());
                          clearTimeout(notifyFalseTimer);
                          safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                          scheduleAutoClose();
                          break;
                  
                        case 'opening':
                          safeSetState(dp_door, 'opening');
                          clearTimeout(notifyFalseTimer);
                          safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                          // kein Cancel hier
                          break;
                  
                        case 'closing':
                          safeSetState(dp_door, 'closing');
                          clearTimeout(notifyFalseTimer);
                          safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                          cancelAutoClose('Status=closing');
                          break;
                  
                        case 'closed':
                          safeSetState(dp_door, 'closed');
                          safeSetState(dp_lastClosed, new Date().toISOString());
                          cancelAutoClose('Status=closed');
                          // Hysterese: erst nach 5s false
                          clearTimeout(notifyFalseTimer);
                          notifyFalseTimer = setTimeout(() => {
                            safeSetState(dp_notify, false, /*ack*/ false);
                          }, NOTIFY_FALSE_DELAY_MS);
                          break;
                  
                        case 'lightOn':
                          safeSetState(dp_light, true);
                          break;
                  
                        case 'lightOff':
                          safeSetState(dp_light, false);
                          break;
                  
                        default:
                          log(`[${deviceName}] Status: ${value}`, 'info');
                          break;
                      }
                      return;
                    }
                  
                    if (prefix === 'R') {
                      const code = parts[1] || '';
                      safeSetState(dp_lastReply, code);
                      if (/ERR|UNKNOWN/i.test(code)) safeSetState(dp_lastError, code);
                      else log(`[${deviceName}] Antwort: ${code}`, 'info');
                      return;
                    }
                  
                    log(`[${deviceName}] Unbekanntes Paket: "${line}"`, 'warn');
                  }
                  
                  // ==== SENDEN ====
                  function sendMessage(msg) {
                    return new Promise(resolve => {
                      if (!client) { log(`[${deviceName}] Kein Client aktiv`, 'warn'); return resolve('Error'); }
                      try { client.write(SEND_NEWLINE ? (msg + '\n') : msg); resolve('OK'); }
                      catch (e) { log(`[${deviceName}] Sendefehler: ${e.message}`, 'error'); safeSetState(dp_lastError, e.message); resolve('Error'); }
                    });
                  }
                  
                  // ==== BEFEHL-TRIGGER ====
                  on({ id: dp_cmd, change: 'ne' }, async obj => {
                    if (obj.state.ack) return;
                    const cmd = String(obj.state.val || '').toLowerCase().trim();
                  
                    // Manuelle Befehle canceln AutoClose (interner Trigger nicht)
                    if (acInternalTrigger) {
                      log(`[${deviceName}] DP-Command (intern): ${cmd}`, 'info');
                    } else {
                      if (cmd === 'open' || cmd === 'close' || cmd === 'stop') {
                        cancelAutoClose(`Befehl: ${cmd}`);
                      }
                    }
                  
                    let payload = null;
                    switch (cmd) {
                      case 'open':     payload = `C;${deviceName};open`; break;
                      case 'close':    payload = `C;${deviceName};close`; break;
                      case 'stop':     payload = `C;${deviceName};stop`; break;
                      case 'lighton':  payload = `C;${deviceName};lightOn`; break;
                      case 'lightoff': payload = `C;${deviceName};lightOff`; break;
                      default:
                        log(`[${deviceName}] Unbekannter Befehl: ${cmd}`, 'warn');
                        safeSetState(dp_lastError, `UNKNOWN_CMD:${cmd}`);
                        break;
                    }
                  
                    if (payload) await sendMessage(payload);
                    safeSetState(dp_cmd, '', true);
                  
                    if (acInternalTrigger) acInternalTrigger = false;
                  });
                  
                  // ==== SETTINGS-ÄNDERUNGEN ====
                  on({ id: dp_ac_enabled, change: 'ne' }, () => {
                    const st = getState(dp_door)?.val;
                    if (getAcEnabled() && st === 'open') scheduleAutoClose();
                    else cancelAutoClose('AutoClose disabled');
                  });
                  on({ id: dp_ac_delay, change: 'ne' }, () => {
                    const st = getState(dp_door)?.val;
                    if (getAcEnabled() && st === 'open') scheduleAutoClose();
                  });
                  
                  // ==== START/STOP ====
                  onStop(() => {
                    shuttingDown = true;
                    clearTimeout(reconnectTimer);
                    clearTimeout(notifyFalseTimer);
                    if (client) { try { client.destroy(); } catch (_) {} }
                    cancelAutoClose('Script stop', true);
                  }, 1000);
                  
                  startClient();
                  
                  
                  D 1 Antwort Letzte Antwort
                  0
                  • LongbowL Longbow

                    Hallo in die Runde,

                    da ich auch so einen Antrieb habe und die Box, hatte ich das Script auch so übernommen.
                    Leider hing es hin und wieder bei mir, daher habe ich mich dran gesetzt und das nun gebaut.

                    Hoffe es ist selbst erklärend und gefällt euch, wenn ihr Fragen habt, melde Euch einfach bei mir.

                    // ===== TCP-Client Garagentor (ioBroker JavaScript) =====
                    // Robust: Reconnect + Backoff, KeepAlive, sauberes Line-Parsing (S;..., R;...),
                    // AutoClose nach X Minuten mit Rest-SECONDS-Datenpunkt, DP-Autoanlage,
                    // Licht-Status als boolean, shutdown-sicher (keine DB-closed-Fehler)
                    const net = require('net');
                    
                    // ==== KONFIG ====
                    const deviceName = "Auffahrt";
                    const host = "xxx.xxx.x.xxx";
                    const port = 2785;
                    const SEND_NEWLINE = true;
                    
                    // ==== BASIS-PFAD ====
                    const base = "0_userdata.0.Eigene_Datenpunkte.Garage.";
                    
                    // ==== DATENPUNKTE ====
                    const dp_door         = base + "Garage_Status_Tor";          // "open|closed|opening|closing"
                    const dp_light        = base + "Garage_Status_Licht";        // boolean
                    const dp_cmd          = base + "Garage_Command";             // "open|close|stop|lighton|lightoff"
                    const dp_connected    = base + "Garage_TcpConnected";        // boolean
                    const dp_lastReply    = base + "Garage_LastReply";           // string
                    const dp_lastError    = base + "Garage_LastError";           // string
                    const dp_ac_enabled   = base + "AutoClose_Enabled";          // boolean
                    const dp_ac_delay     = base + "AutoClose_DelayMin";         // number (1..120)
                    const dp_ac_left_s    = base + "AutoClose_RemainingSec";     // number (Sek., read-only)
                    const dp_lastOpened   = base + "Garage_LastOpened";          // ISO-Zeit
                    const dp_lastClosed   = base + "Garage_LastClosed";          // ISO-Zeit
                    
                    // ---- Dein vorhandener Notify-DP (nicht anlegen, nur schreiben) ----
                    const dp_notify = "hm-rega.0.31016"; // bestehender Homematic-ReGa Datenpunkt/Variable
                    
                    // ---- Notify-Logik: Frühwarnung & Hysterese ----
                    const EARLY_TRIGGER_SEC = 60;        // ab so vielen Sekunden vor Auto-Close -> notify = true
                    const NOTIFY_FALSE_DELAY_MS = 5000;  // Hysterese: erst nach 5s "closed" -> notify = false
                    let notifyFalseTimer = null;
                    
                    // ==== DP-AUTOANLAGE (nur eigene 0_userdata.*) ====
                    function ensureState(id, defVal, common) {
                      if (!existsState(id)) {
                        createState(id, defVal, common, () => log(`State angelegt: ${id}`, 'info'));
                      }
                    }
                    ensureState(dp_door,        '',    { name:'Torstatus',        type:'string',  role:'text',                 read:true, write:false });
                    ensureState(dp_light,       false, { name:'Lichtstatus',      type:'boolean', role:'indicator.light',      read:true, write:false });
                    ensureState(dp_cmd,         '',    { name:'Befehl',           type:'string',  role:'text',                 read:true, write:true  });
                    ensureState(dp_connected,   false, { name:'TCP verbunden',    type:'boolean', role:'indicator.reachable',  read:true, write:false });
                    ensureState(dp_lastReply,   '',    { name:'Letzte Antwort',   type:'string',  role:'text',                 read:true, write:false });
                    ensureState(dp_lastError,   '',    { name:'Letzter Fehler',   type:'string',  role:'text',                 read:true, write:false });
                    ensureState(dp_ac_enabled,  true,  { name:'AutoClose aktiv',  type:'boolean', role:'switch',               read:true, write:true  });
                    ensureState(dp_ac_delay,    5,     { name:'AutoClose Minuten',type:'number',  role:'level', unit:'min',    read:true, write:true  });
                    ensureState(dp_ac_left_s,   0,     { name:'Rest-Sekunden',    type:'number',  role:'value', unit:'s',      read:true, write:false });
                    ensureState(dp_lastOpened,  '',    { name:'Letzte Öffnung',   type:'string',  role:'date',                 read:true, write:false });
                    ensureState(dp_lastClosed,  '',    { name:'Letzter Verschluss',type:'string', role:'date',                 read:true, write:false });
                    
                    // ==== SHUTDOWN-SAFE ====
                    let shuttingDown = false;
                    function safeSetState(id, val, ack = true) {
                      if (shuttingDown) return;
                      try { setState(id, val, ack); } catch (_) {}
                    }
                    
                    // ==== TCP ====
                    let client = null;
                    let reconnectTimer = null;
                    let reconnectDelayMs = 2000;
                    const reconnectDelayMaxMs = 30000;
                    let recvBuffer = '';
                    
                    function startClient() {
                      clearTimeout(reconnectTimer);
                      client = new net.Socket();
                      client.setKeepAlive(true, 10000);
                      client.setEncoding('utf8');
                    
                      client.connect(port, host, () => {
                        log(`[${deviceName}] Verbunden mit ${host}:${port}`, 'info');
                        safeSetState(dp_connected, true);
                        reconnectDelayMs = 2000;
                      });
                    
                      client.on('data', (chunk) => {
                        recvBuffer += chunk;
                        const parts = recvBuffer.split(/\r?\n|\r/);
                        recvBuffer = parts.pop();
                        for (const raw of parts) {
                          const line = (raw || '').trim();
                          if (line) handleIncoming(line);
                        }
                      });
                    
                      client.on('error', (err) => {
                        log(`[${deviceName}] TCP Fehler: ${err.message}`, 'warn');
                        safeSetState(dp_connected, false);
                        safeSetState(dp_lastError, err.message);
                        scheduleReconnect();
                      });
                    
                      client.on('close', (hadError) => {
                        log(`[${deviceName}] TCP geschlossen${hadError ? ' (Fehler)' : ''}`, 'warn');
                        safeSetState(dp_connected, false);
                        scheduleReconnect();
                      });
                    }
                    
                    function scheduleReconnect() {
                      try { if (client) client.destroy(); } catch (_) {}
                      client = null;
                      reconnectTimer = setTimeout(startClient, reconnectDelayMs);
                      reconnectDelayMs = Math.min(Math.round(reconnectDelayMs * 1.5), reconnectDelayMaxMs);
                    }
                    
                    // ==== AUTO-CLOSE ====
                    let autoCloseTimer = null;
                    let autoCloseDeadline = 0;
                    let autoCloseTicker = null;
                    let acInternalTrigger = false;
                    
                    function getAcEnabled() {
                      const v = getState(dp_ac_enabled)?.val;
                      return v === true || v === 1 || v === 'true';
                    }
                    function getAcDelayMin() {
                      let m = Number(getState(dp_ac_delay)?.val);
                      if (!isFinite(m) || m <= 0) m = 5;
                      return Math.max(1, Math.min(120, Math.floor(m)));
                    }
                    
                    function scheduleAutoClose() {
                      if (!getAcEnabled()) { safeSetState(dp_ac_left_s, 0); return; }
                      const min = getAcDelayMin();
                      autoCloseDeadline = Date.now() + min * 60000;
                    
                      clearTimeout(autoCloseTimer);
                      autoCloseTimer = setTimeout(performAutoClose, min * 60000);
                    
                      startAutoCloseTicker();
                      updateRemainingSeconds();
                      log(`[${deviceName}] AutoClose geplant in ${min} Min`, 'info');
                    }
                    
                    function performAutoClose() {
                      if (shuttingDown) return;
                      autoCloseTimer = null;
                      const st = getState(dp_door)?.val;
                      if (st === 'open') {
                        log(`[${deviceName}] AutoClose ausgelöst -> schließe`, 'info');
                        acInternalTrigger = true;
                        try { setState(dp_cmd, { val: 'close', ack: false }); } catch (_) {}
                      } else {
                        log(`[${deviceName}] AutoClose übersprungen (Status: ${st})`, 'info');
                      }
                      stopAutoCloseTicker();
                      safeSetState(dp_ac_left_s, 0);
                      autoCloseDeadline = 0;
                      // dp_notify bleibt bis "closed" true (Hysterese regelt das Zurücksetzen)
                    }
                    
                    function cancelAutoClose(reason = '', suppressWrite = false) {
                      if (autoCloseTimer) clearTimeout(autoCloseTimer);
                      if (autoCloseTicker) clearInterval(autoCloseTicker);
                      autoCloseTimer = null;
                      autoCloseTicker = null;
                      autoCloseDeadline = 0;
                      if (!suppressWrite) safeSetState(dp_ac_left_s, 0);
                      if (reason) log(`[${deviceName}] AutoClose abgebrochen: ${reason}`, 'info');
                    }
                    
                    function updateRemainingSeconds() {
                      if (!autoCloseDeadline || shuttingDown) return;
                      const secLeft = Math.max(0, Math.ceil((autoCloseDeadline - Date.now()) / 1000));
                      safeSetState(dp_ac_left_s, secLeft);
                    
                      // Frühwarnung: ≤ EARLY_TRIGGER_SEC -> notify true
                      if (secLeft > 0 && secLeft <= EARLY_TRIGGER_SEC) {
                        clearTimeout(notifyFalseTimer);
                        safeSetState(dp_notify, true, /*ack*/ false); // bewusst ack=false, falls Triggers benötigt
                      }
                    }
                    
                    function startAutoCloseTicker() {
                      if (autoCloseTicker) return;
                      autoCloseTicker = setInterval(updateRemainingSeconds, 1000);
                    }
                    function stopAutoCloseTicker() {
                      if (!autoCloseTicker) return;
                      clearInterval(autoCloseTicker);
                      autoCloseTicker = null;
                    }
                    
                    // ==== EINGEHEND ====
                    function handleIncoming(line) {
                      const parts = line.split(';');
                      if (parts.length < 2) {
                        log(`[${deviceName}] Unbekanntes Paket: "${line}"`, 'warn');
                        return;
                      }
                      const prefix = parts[0];
                    
                      if (prefix === 'S') {
                        const dev = parts[1];
                        const value = parts.slice(2).join(';');
                        if (dev !== deviceName) return;
                    
                        switch (value) {
                          case 'open':
                            safeSetState(dp_door, 'open');
                            safeSetState(dp_lastOpened, new Date().toISOString());
                            clearTimeout(notifyFalseTimer);
                            safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                            scheduleAutoClose();
                            break;
                    
                          case 'opening':
                            safeSetState(dp_door, 'opening');
                            clearTimeout(notifyFalseTimer);
                            safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                            // kein Cancel hier
                            break;
                    
                          case 'closing':
                            safeSetState(dp_door, 'closing');
                            clearTimeout(notifyFalseTimer);
                            safeSetState(dp_notify, true, /*ack*/ false); // sofort true
                            cancelAutoClose('Status=closing');
                            break;
                    
                          case 'closed':
                            safeSetState(dp_door, 'closed');
                            safeSetState(dp_lastClosed, new Date().toISOString());
                            cancelAutoClose('Status=closed');
                            // Hysterese: erst nach 5s false
                            clearTimeout(notifyFalseTimer);
                            notifyFalseTimer = setTimeout(() => {
                              safeSetState(dp_notify, false, /*ack*/ false);
                            }, NOTIFY_FALSE_DELAY_MS);
                            break;
                    
                          case 'lightOn':
                            safeSetState(dp_light, true);
                            break;
                    
                          case 'lightOff':
                            safeSetState(dp_light, false);
                            break;
                    
                          default:
                            log(`[${deviceName}] Status: ${value}`, 'info');
                            break;
                        }
                        return;
                      }
                    
                      if (prefix === 'R') {
                        const code = parts[1] || '';
                        safeSetState(dp_lastReply, code);
                        if (/ERR|UNKNOWN/i.test(code)) safeSetState(dp_lastError, code);
                        else log(`[${deviceName}] Antwort: ${code}`, 'info');
                        return;
                      }
                    
                      log(`[${deviceName}] Unbekanntes Paket: "${line}"`, 'warn');
                    }
                    
                    // ==== SENDEN ====
                    function sendMessage(msg) {
                      return new Promise(resolve => {
                        if (!client) { log(`[${deviceName}] Kein Client aktiv`, 'warn'); return resolve('Error'); }
                        try { client.write(SEND_NEWLINE ? (msg + '\n') : msg); resolve('OK'); }
                        catch (e) { log(`[${deviceName}] Sendefehler: ${e.message}`, 'error'); safeSetState(dp_lastError, e.message); resolve('Error'); }
                      });
                    }
                    
                    // ==== BEFEHL-TRIGGER ====
                    on({ id: dp_cmd, change: 'ne' }, async obj => {
                      if (obj.state.ack) return;
                      const cmd = String(obj.state.val || '').toLowerCase().trim();
                    
                      // Manuelle Befehle canceln AutoClose (interner Trigger nicht)
                      if (acInternalTrigger) {
                        log(`[${deviceName}] DP-Command (intern): ${cmd}`, 'info');
                      } else {
                        if (cmd === 'open' || cmd === 'close' || cmd === 'stop') {
                          cancelAutoClose(`Befehl: ${cmd}`);
                        }
                      }
                    
                      let payload = null;
                      switch (cmd) {
                        case 'open':     payload = `C;${deviceName};open`; break;
                        case 'close':    payload = `C;${deviceName};close`; break;
                        case 'stop':     payload = `C;${deviceName};stop`; break;
                        case 'lighton':  payload = `C;${deviceName};lightOn`; break;
                        case 'lightoff': payload = `C;${deviceName};lightOff`; break;
                        default:
                          log(`[${deviceName}] Unbekannter Befehl: ${cmd}`, 'warn');
                          safeSetState(dp_lastError, `UNKNOWN_CMD:${cmd}`);
                          break;
                      }
                    
                      if (payload) await sendMessage(payload);
                      safeSetState(dp_cmd, '', true);
                    
                      if (acInternalTrigger) acInternalTrigger = false;
                    });
                    
                    // ==== SETTINGS-ÄNDERUNGEN ====
                    on({ id: dp_ac_enabled, change: 'ne' }, () => {
                      const st = getState(dp_door)?.val;
                      if (getAcEnabled() && st === 'open') scheduleAutoClose();
                      else cancelAutoClose('AutoClose disabled');
                    });
                    on({ id: dp_ac_delay, change: 'ne' }, () => {
                      const st = getState(dp_door)?.val;
                      if (getAcEnabled() && st === 'open') scheduleAutoClose();
                    });
                    
                    // ==== START/STOP ====
                    onStop(() => {
                      shuttingDown = true;
                      clearTimeout(reconnectTimer);
                      clearTimeout(notifyFalseTimer);
                      if (client) { try { client.destroy(); } catch (_) {} }
                      cancelAutoClose('Script stop', true);
                    }, 1000);
                    
                    startClient();
                    
                    
                    D Offline
                    D Offline
                    drapo
                    schrieb am zuletzt editiert von drapo
                    #67

                    @longbow vielen Dank fürs Teilen. Funktioniert Dein Script ohne hänger (von Zeit zu Zeit habe ich diese auch)? Resp. wie lange hast Du es bereits in Verwendung?

                    LongbowL 1 Antwort Letzte Antwort
                    0
                    • D drapo

                      @longbow vielen Dank fürs Teilen. Funktioniert Dein Script ohne hänger (von Zeit zu Zeit habe ich diese auch)? Resp. wie lange hast Du es bereits in Verwendung?

                      LongbowL Offline
                      LongbowL Offline
                      Longbow
                      schrieb am zuletzt editiert von
                      #68

                      @drapo Guten Morgen, also ich habe es seit der ersten Juni Woche am laufen und es ist sehr gut und stabil...

                      1 Antwort Letzte Antwort
                      0
                      • LongbowL Offline
                        LongbowL Offline
                        Longbow
                        schrieb am zuletzt editiert von
                        #69

                        Hallo, könntet Ihr es Testen und ausprobieren?

                        1 Antwort Letzte Antwort
                        0
                        Antworten
                        • In einem neuen Thema antworten
                        Anmelden zum Antworten
                        • Älteste zuerst
                        • Neuste zuerst
                        • Meiste Stimmen


                        Support us

                        ioBroker
                        Community Adapters
                        Donate
                        FAQ Cloud / IOT
                        HowTo: Node.js-Update
                        HowTo: Backup/Restore
                        Downloads
                        BLOG

                        364

                        Online

                        32.5k

                        Benutzer

                        81.7k

                        Themen

                        1.3m

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

                        • Du hast noch kein Konto? Registrieren

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