Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. sebastian.eberle

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    S
    • Profile
    • Following 0
    • Followers 0
    • Topics 1
    • Posts 27
    • Best 0
    • Groups 0

    sebastian.eberle

    @sebastian.eberle

    0
    Reputation
    134
    Profile views
    27
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    sebastian.eberle Follow

    Latest posts made by sebastian.eberle

    • RE: [Problem] Fibaro Roller Shutter mit Alexa nach % steuern

      @eumats:

      Moin. Ich haben den Fibaro Roller Shutter 2 im Einsatz und nutze ihn per Homekit (yahka Adapter) Alexa und manuell über die Tasten.

      Das Ganze habe ich mit Hilfe des Virtual Devices-Skript umgesetzt. Bei mir ist 0= Rollladen geschlossen und 100=Rollladen geöffnet.

      Das Skript unter common legt 4 Datenpunkte unter javascript.0.virtualDevice.Rolllaeden.EsszimmerOsten (Pfad kann konfiguriert werden) an.

      1. AlexaLevel -> Rollladen in cloud Adapter einbinden und mit Alexa steuern. Funktion: hoch, runter und angesagt %-Zahl

      2. LevelRollo -> Abbild (binding) mit Level_1 des zwave Aktors

      3. PowerRollo -> Abbild (binding) mit Power_1 des zwave Aktors

      4. TargetLevel -> Hier wird der Zielwert, auf den der Rollladen fahren soll, reingeschrieben.

      Hinweis: Diese Lösung ist recht yahka spezifisch. Sollte yahka nicht genutzt werden muss das Skript unter common sicher angepasst werden. Ggf. kann ich hier unterstützen.

      <u>Details zu 2. und 3.:</u>

      Zur korrekten Darstellung von öffnen und schließen in yahka ist es wichtig, dass ein Datenpunkt den Zielwert (TargetLevel, z.B. 70%) und ein Datenpunkt den IST-Wert anzeigt (TargetLevel, z.B. 0%).

      Daher die beiden Datenpunkte.

      <u>Details zu 4.:</u>

      Für die korrekte Anzeige des %-Werte in yahka bei manueller Betätigung über den Taster direkt am Fibaro Aktor werte ich PowerRollo aus. Also: Wenn der Rollladen still steht (PowerRollo < 1 für 5000ms) dann setze den Targetwert auf den IST-Wert.

      Da ich vielen Funktionen unter global ausgelagert habe, habe ich ich der Einfachheit halber alles in ein Skript zusammen kopiert. Ich hoffe mir ist da kein Fehler unterlaufen.

      Eigentlich müssen nur die Variablen LevelDevice und PowerDevice an den eigene Aktor angepasst werden, Skript Starten, erzeugte Datenpunkte in die Adapter einbinden, fertig.

      Ich hoffe ich habe nix vergessen. Ich habe das mal schnell auf der Arbeit zusammengeschrieben.

      Unter <u>global</u> einfügen und starten:

      ! ```
      /*VirtualDevice v0.3 ! // http://forum.iobroker.net/viewtopic.php?f=21&t=8192&hilit=virtuelle+Geräte ! var AdapterPfadVirtuelleGeraete = 'javascript.', virtualDevicePfad = 'virtualDevice.'; ! Structure of config: { name: 'name', //name of device namespace: '', //create device within this namespace (device ID will be namespace.name) common: {}, //(optional) native: {}, //(optional) copy: objectId, //(optional) ID of device or channel to copy common and native from onCreate: function(device, callback) {} //called once on device creation states: { 'stateA': { //State Id will be namespace.name.stateA common: {}, //(optional) native: {}, //(optional) copy: stateId, read: { //(optional) states which should write to "stateA" 'stateId1': { trigger: {ack: true, change: 'any'} //(optional) see https://github.com/ioBroker/ioBroker.javascript#on---subscribe-on-changes-or-updates-of-some-state convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written validFor: //(optional) ms, to ignore old data which did not change for a long time. }, ... }, readLogic: 'last' || 'max' || 'min' || 'average', //(optional) default: last (only last implemented) write: { //(optional) states which "stateA" should write to 'stateId1': { convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written }, 'stateId3': { convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written }, ... }, }, ... } } */ ! //generic virtual device function VirtualDevice(config) { //sanity check if (typeof config !== 'object' || typeof config.namespace !== 'string' || typeof config.name !== 'string' || typeof config.states !== 'object') { log('sanity check failed, no device created', 'warn'); return; } ! this.config = config; this.namespace = virtualDevicePfad + config.namespace + '.' + config.name; this.name = config.name; ! //create virtual device //log('creating virtual device ' + this.namespace) this.createDevice(function () { this.createStates(function () { //log('created virtual device ' + this.namespace) }.bind(this)); }.bind(this)); } ! VirtualDevice.prototype.createDevice = function (callback) { //log('creating object for device ' + this.namespace, 'debug'); //create device object var obj = this.config.copy ? getObject(this.config.copy) : {common: {}, native: {}}; delete obj.common.custom; if (typeof this.config.common === 'object') { obj.common = Object.assign(obj.common, this.config.common); } if (typeof this.config.native === 'object') { obj.native = Object.assign(obj.native, this.config.native); } extendObject(AdapterPfadVirtuelleGeraete + instance + '.' + this.namespace, { type: "device", common: obj.common, native: obj.native }, function (err) { if (err) { log('could not create virtual device: ' + this.namespace, 'warn'); return; } log('created object for device ' + this.namespace, 'debug'); if (typeof this.config.onCreate === 'function') { this.config.onCreate(this, callback); } else { callback(); } }.bind(this)); } ! VirtualDevice.prototype.createStates = function (callback) { "use strict"; log('creating states for device ' + this.namespace, 'debug'); var stateIds = Object.keys(this.config.states); log('creating states ' + JSON.stringify(stateIds), 'debug'); var countCreated = 0; for (var i = 0; i < stateIds.length; i++) { let stateId = stateIds[i]; this.normalizeState(stateId); var id = this.namespace + '.' + stateId; log('creating state ' + id, 'debug'); var obj = this.config.states[stateId].copy ? getObject(this.config.states[stateId].copy) : { common: {}, native: {} }; delete obj.common.custom; if (typeof this.config.states[stateId].common === 'object') { obj.common = Object.assign(obj.common, this.config.states[stateId].common); } if (typeof this.config.states[stateId].native === 'object') { obj.native = Object.assign(obj.native, this.config.states[stateId].native); } createState(id, obj.common, obj.native, function (err) { if (err) { log('skipping creation of state ' + id, 'debug'); } else { log('created state ' + id, 'debug'); } this.connectState(stateId); countCreated++; if (countCreated >= stateIds.length) { log('created ' + countCreated + ' states for device ' + this.namespace, 'debug'); callback(); } }.bind(this)); } } ! VirtualDevice.prototype.normalizeState = function (state) { log('normalizing state ' + state, 'debug'); if (typeof this.config.states[state].read !== 'object') { this.config.states[state].read = {}; } if (typeof this.config.states[state].write !== 'object') { this.config.states[state].write = {}; } ! var readIds = Object.keys(this.config.states[state].read); for (var i = 0; i < readIds.length; i++) { var readId = this.config.states[state].read[readIds[i]]; if (typeof readId.before !== 'function') { this.config.states[state].read[readIds[i]].before = function (device, value, callback) { callback() }; } if (typeof readId.after !== 'function') { this.config.states[state].read[readIds[i]].after = function (device, value) { }; } } var writeIds = Object.keys(this.config.states[state].write); for (i = 0; i < writeIds.length; i++) { var writeId = this.config.states[state].write[writeIds[i]]; if (typeof writeId.before !== 'function') { this.config.states[state].write[writeIds[i]].before = function (device, value, callback) { callback() }; } if (typeof writeId.after !== 'function') { this.config.states[state].write[writeIds[i]].after = function (device, value) { }; } } log('normalized state ' + state, 'debug'); } ! VirtualDevice.prototype.connectState = function (state) { log('connecting state ' + state, 'debug'); var id = this.namespace + '.' + state; ! //subscribe to read ids var readIds = Object.keys(this.config.states[state].read); for (var i = 0; i < readIds.length; i++) { if (getState(readIds[i]).notExist === true) { //check if state exists log('cannot connect to not existing state: ' + readIds[i], 'warn'); continue; } var readObj = this.config.states[state].read[readIds[i]]; var trigger = readObj.trigger || {change: 'any'}; trigger.ack = true; trigger.id = readIds[i]; this.subRead(trigger, readObj, state); log('connected ' + readIds[i] + ' to ' + id, 'debug'); } ! //subscribe to this state and write to write ids var writeIds = Object.keys(this.config.states[state].write); var trigger = {id: AdapterPfadVirtuelleGeraete + instance + '.' + this.namespace + '.' + state, change: 'any', ack: false}; on(trigger, function (obj) { "use strict"; log('detected change of ' + state, 'debug'); for (var i = 0; i < writeIds.length; i++) { let writeObj = this.config.states[state].write[writeIds[i]]; let val = this.convertValue(obj.state.val, writeObj.convert); let writeId = writeIds[i]; log('executing function before for ' + writeId, 'debug'); writeObj.before(this, val, function (newVal, newDelay) { if (newVal !== undefined && newVal !== null) val = newVal; var delay = writeObj.delay; if (newDelay !== undefined && newDelay !== null) delay = newDelay; log(newVal + 'writing value ' + val + ' to ' + writeId + ' with delay ' + delay, 'debug'); setStateDelayed(writeId, val, false, delay || 0, true, function () { log('executing function after for ' + writeId, 'debug'); writeObj.after(this, val); }.bind(this)); }.bind(this)); } }.bind(this)); log('connected ' + state + ' to ' + JSON.stringify(writeIds), 'debug'); } ! VirtualDevice.prototype.subRead = function (trigger, readObj, state) { var func = function (obj) { var val = this.convertValue(obj.state.val, readObj.convert); ! //@todo aggregations ! log('executing function before for ' + trigger.id, 'debug'); readObj.before(this, val, function (newVal, newDelay) { if (newVal !== undefined && newVal !== null) val = newVal; if (newDelay !== undefined && newDelay !== null) writeObj.delay = newDelay; log('reading value ' + val + ' to ' + this.namespace + '.' + state, 'debug'); setStateDelayed(this.namespace + '.' + state, val, true, readObj.delay || 0, true, function () { log('executing function after for ' + trigger.id, 'debug'); readObj.after(this, val); }.bind(this)); }.bind(this)); }.bind(this); func({state: getState(trigger.id)}); on(trigger, func); } ! VirtualDevice.prototype.convertValue = function (val, func) { if (typeof func !== 'function') { return val; } return func(val); }
      ! Unter **[b]<u>[u]common [/u]</u>[/b]**einfügen und starten (Es kann ggf. sein, dass das Skript 2x gestartet werden muss, da das Erzeugen der Datenpunkte und das zugehörige binding "sich nicht finden":
      ! >! [spoiler]`
      [code]
      // Alle Datenpunkte erzeugen, die dieses Gerät benötigt
      var LevelDevice = 'zwave.0.NODE10.SWITCH_MULTILEVEL.Level_1',
      PowerDevice = 'zwave.0.NODE10.SENSOR_MULTILEVEL.Power_1';

      var WriteTargetLevelTimeout = 5000, // Timeout für das Schreiben des Levelwerte in den Targetwert bei Fibaro Rollladenaktoren.
      LevelPowerRolloMeldewert = 1; // Kleiner diesen Wert wird der Rollladen als stillstehend erkannt.

      ! // Der Datenpunkt des Cloudadapters zur Alexa Rollladensteuerung hoch/runter
      var cloudAlexa = 'cloud.0.smart.lastCommand';
      var basisPfad = 'javascript.0.virtualDevice.';

      var ersteSubEbene = 'Rolllaeden',
      zweiteSubEbene = 'EsszimmerOsten',
      kompletterPfad = [basisPfad,ersteSubEbene,'.',zweiteSubEbene,'.'].join(''),
      Alexa = 'AlexaLevel',
      LevelRollo = 'LevelRollo',
      TargetLevel = 'TargetLevel';

      ! var Rollo_Alexa = [kompletterPfad,Alexa].join(''),
      TargetLevel = [kompletterPfad,TargetLevel].join(''),
      LevelRollo = [kompletterPfad,LevelRollo].join(''),
      Schreibverzoegerung;

      var Name = 'Rollladen WZ Osten ',
      AlexaTag = Name + 'Alexasteuerung',
      LevelTag = Name + 'Level Aktor',
      PowerTag = Name + 'Power Aktor',
      TargetTag = Name + 'Target-Level für Homekit';

      createState(Rollo_Alexa, {
      type: 'number',
      name: AlexaTag,
      min: 0,
      max: 100,
      def: 0,
      read: true,
      write: true
      });

      ! createState(TargetLevel, {
      type: 'number',
      name: TargetTag,
      min: 0,
      max: 100,
      def: 0,
      });
      ! // Hilfsfunktionen...
      ! // Konvertiert die Fibarowerte (0-99) in ein Wert von 0-100
      // Manchmal zeigt Level_1 der Aktoren nach einer manuellen Bedienung nicht 0/99 an, sondern 1 oder 96. Das wird hier korrigiert
      function convertFibaroAuf (wertFibaroAuf) {
      if (wertFibaroAuf >= 95) {
      return(100);
      } else if (wertFibaroAuf <= 2) {
      return(0);
      } else {
      return(wertFibaroAuf);
      }
      }
      ! function convertFibaroZu (wertFibaroZU) {
      if (wertFibaroZU == 100) {
      return (99);
      } else {
      return(wertFibaroZU);
      }
      }
      ! // Umsetzung von hoch und runter mit Alexa
      function steuereRolllaedenAlexa(Rollladen) {
      letztesAlexaKommando = getState(cloudAlexa).val;
      // if Abfrage, um hoch und runter von Alexa zu ermöglichen
      if (letztesAlexaKommando == '--25') {
      // fahre Rolladen runter
      return 'runter';
      } else if (letztesAlexaKommando == '+25') {
      // fahre Rolladen hoch
      return 'hoch';
      } else {
      // Mache das, was Alexa in den Datenpunkt schreibt
      return 'nativ';
      }
      }
      ! // Verarbeitung der virtuellen Geräte für die Fibaro Aktoren.
      // Level_1 und Power_1
      new VirtualDevice({
      // z.B. 'Rollaeden' für javascript.0.virtualDevice.Rollaeden... oder 'Rolllaeden.bla' javascript.0.virtualDevice.Rollaeden.bla...
      namespace: ersteSubEbene,
      name: zweiteSubEbene, //das Gerät wird unter javascript.0.virtualDevice.namespace.name erstellt
      states: {
      //welche States sollen erstellt werden?
      'LevelRollo': {
      //State Konfiguration
      common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, name: LevelTag},
      read: {
      //von welchen States sollen Werte eingelesen werden?
      [LevelDevice]: {
      convert: function (val) { //wert soll konvertiert werden
      // Konvertiere die Fibarowerte zu 0-100
      var wert = convertFibaroAuf(val);
      //console.log('### LevelRollo: ' + wert);
      return wert;
      },
      },
      },
      write: {
      //in welche States sollen Werte geschrieben werden?
      [LevelDevice]: {
      convert: function (val) { //wert soll konvertiert werden
      // Konvertiere die Fibarowerte zu 0-100
      var wert = convertFibaroZu(val);
      return wert;
      },
      },
      }
      },
      'PowerRollo': {
      //State Konfiguration
      common: {type: 'number', def: 0, min: 0, max: 300, read: true, write: false, name: PowerTag},
      read: {
      //von welchen States sollen Werte eingelesen werden?
      [PowerDevice]: {
      trigger: {ack: true, change: 'ne'},
      // Falls der Rolladen manuell bedient wurde, müssen wir nun alles für eine korrekte Anzeige in Homekit bereitstelen
      after: function(device, value) {
      // Mache etwas, wenn der Rollladen still steht, also der Verbrauch kleiner LevelPowerRolloMeldewert ist
      if (value < LevelPowerRolloMeldewert) {
      var level = getState(LevelRollo).val;
      var levelAck = getState(LevelRollo).ack;
      var TargetLevelAktuell = getState(TargetLevel).val;

                                  if (!levelAck) {
                                      console.log('### Level NICHT ACK: ' + level + ', levelAck: ' + levelAck);
                                  }
                                  
                                  // Alten Timer, falls vorhanden, löschen
                                  (function () {if (Schreibverzoegerung) {clearTimeout(Schreibverzoegerung); Schreibverzoegerung = null;}})();
                                  
                                  // Verzögerung beim Schreiben, da Level_1 machmal etwas länger zum Aktualisieren des Zielwertes braucht.
                                  //console.log('### Level vor TIMEOUT: ' + level + ', levelAck: ' + levelAck);
                                  //console.log('### TargetLevelAktuell vor TIMEOUT: ' + TargetLevelAktuell);
                                  Schreibverzoegerung = setTimeout(function () {
                                      level = getState(LevelRollo).val;
                                      levelAck = getState(LevelRollo).ack;
                                      TargetLevelAktuell =  getState(TargetLevel).val;
                                      
                                      //console.log('### Level nach TIMEOUT: ' + level + '#, levelAck: ' + levelAck);
                                      //console.log('### TargetLevelAktuell nach TIMEOUT: ' + TargetLevelAktuell);
                                      
                                      // Wenn Level_1 und Target nicht gleich sind, dann müssen wir etwas machen
                                      if (level != TargetLevelAktuell) {
                                          console.log('Setze Target (aktuell: ' + getState(TargetLevel).val + ') auf: ' + level);   
                                          setState(TargetLevel,level);
                                      } else {
                                          console.log('### TargetLevel muss nicht aktualisiert werden (aktuell: ' + getState(TargetLevel).val + ', Zielwert: ' + level + ').');
                                      }
                                  }, WriteTargetLevelTimeout);
                              }
                          },
                      },
                  },
          },
      },
      

      });

      ! // Verarbeitung des Tagetwertes zur korrekten Bedienung und Anzeige in Homekit
      on({id: TargetLevel, change: "ne"}, function (obj) {
      var value = obj.state.val;
      var oldValue = obj.oldState.val;
      if (value != oldValue) {
      var wert = getState(TargetLevel).val;

          setState(LevelRollo, wert);
      }
      

      });

      ! // Alexasteuerung
      on({id: Rollo_Alexa, change: "any"}, function (obj) {
      var AlexaAktion = steuereRolllaedenAlexa(Rollo_Alexa),
      wert;
      //console.log('### Alexa: ' + AlexaAktion);
      ! if (AlexaAktion == 'runter') {
      // fahre Rolladen runter
      wert = 0;
      } else if (AlexaAktion == 'hoch') {
      // fahre Rolladen hoch
      wert = 100;
      } else {
      // fahre Rolladen auf den übergebenen Wert
      wert = value;
      }

      // Über den TargetLevel wird dann auch der Frostschutz gesteuert
      setState(TargetLevel,wert);
      

      });[/code]`[/spoiler]

      ! ~~[b]~~23.05.2018: Die im Folgenden offenbarten, fehlenden Variablen wurden hinzugefügt.[/b]
      ! Kannst du mal nen Screenshot von deiner Yahka cofig eines rollos machen? wäre super :)[/i][/i][/i][/i][/i][/i]
      ``` `

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: Alexa sollte Farben unterstützen

      Hi dslraser,

      danke für den Screenshot. Ich hatte "ct" nicht im Smart-Device. Jetzt funzts. Danke dir!

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: [SKRIPT] Alexa über ioBroker Steuern -> Kommandos an Alexa

      @Erti1337:

      Das war aber nicht auf mich bezogen oder ? 🙂

      Also bei mir geht seit einem TAG kein remote Alexa mehr.

      Skript neustart , Alexa neustart, alles hinter mir.

      Wenn ich Objekt Play button nutze bekomme ich Fehler 400 ^^

      Gehts bei irgendwem aktuell ? `

      Hi Erti,

      habe dasselbe Problem. Konntest du es lösen? Ich kann zwar per Play, Pause etc. steuern, die Rückmeldung wie z.B. Lautstärke etc. funktioniert nicht. STatus auch 400

      Danke!

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: Alexa sollte Farben unterstützen

      @dslraser:

      Ich habe keinen Fehler drinn. Bei mir funktioniert es wie gewünscht. Alexa, stelle Lampe auf weiss, auf warmweis, dunkler, heller,…Farbe egal. `

      Musstest du dafür etwas anpassen? Habs eben nochmals getestet: Heller, dunkler geht. Weiss, warmweiss geht nicht…

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: [SKRIPT] Alexa über ioBroker Steuern -> Kommandos an Alexa

      Hi zusammen,

      ich verwende das Skript von hauke in version 1.0.6. Steuerung klappt soweit, aber alle Datenpunkte sind leer, d.h. es kommen keine Infos von den Echos. Weiss jemand Rat?

      Danke euch!

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: Alexa sollte Farben unterstützen

      Hi,

      Funzt bei mir super. Nur eine Frage habe ich: Wie kann ich wieder auf warmweiß zurückschalten wenn ich auf einer Farbe bin? Das unterstützt der Adapter noch nicht oder? Auch wärmer/kälter wäre cool.

      Gesendet von iPhone mit Tapatalk

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: [Vorlage] Spotify Skript

      @Lucky:

      vielen Dank ! 😄 freut mich wenn es dem einen oder anderen nützlich ist !

      ****Ich habe die letzte Version nochmal etwas bearbeitet:

      • Fehler behoben

      • es werden jetzt die Tracks der Playlist in den State ''Track_List" der jeweiligen Liste geschrieben, diese können direkt angewählt werden (maximal 100 Einträge)**** `

      HEy Lucky, vielen Dank. Wo gibts die letzte Version?

      posted in Skripten / Logik
      S
      sebastian.eberle
    • RE: Probleme mit Alexa Gruppen für Lichtsteuerung

      ok danke dir! wirklich cool, dass du dich so reinhängst 😉

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: Probleme mit Alexa Gruppen für Lichtsteuerung

      Ich bekomme immer einen "Fetch Failed"…

      ! $ ./iobroker url "https://github.com/WolfspiritM/ioBroker.cloud.git"
      ! install https://github.com/WolfspiritM/ioBroker … all/master
      ! npm install https://github.com/WolfspiritM/ioBroker ... all/master --production --prefix "/opt/iobroker" (System call)
      ! npm ERR! fetch failed https://github.com/WolfspiritM/ioBroker ... all/master
      ! npm WARN retry will retry, error on last attempt: Error: fetch failed with status code 404
      ! npm ERR! fetch failed https://github.com/WolfspiritM/ioBroker ... all/master
      ! npm WARN retry will retry, error on last attempt: Error: fetch failed with status code 404

      Kannst du bitte nochmal erklären wie ich installieren muss?

      posted in Cloud Dienste
      S
      sebastian.eberle
    • RE: Allgemeine Frage zu eigenen States

      @Homoran:

      @Micro1959:

      Wäre es nicht dann besser eine Objektgruppe namens States anzulegen und meine Trigger dort abzulegen? `
      Etwa so?
      filename="Screenshot_20180103-213600.jpg" index="0">~~

      Gruß Rainer `
      Zwar offtopic aber was ist n das fürn admin ui?

      Gesendet von iPhone mit Tapatalk

      posted in ioBroker Allgemein
      S
      sebastian.eberle
    Community
    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
    The ioBroker Community 2014-2023
    logo