IKEA Tradfri: Zigbee-Rollos und -Leuchten per Javaskript (z.B. ioBroker) mit HM-Wandtaster steuern

User stellen ihre Haussteuerung vor

Moderator: Co-Administratoren

Antworten
dtp
Beiträge: 10657
Registriert: 21.09.2012, 08:09
System: CCU
Wohnort: Stuttgart
Hat sich bedankt: 320 Mal
Danksagung erhalten: 501 Mal

IKEA Tradfri: Zigbee-Rollos und -Leuchten per Javaskript (z.B. ioBroker) mit HM-Wandtaster steuern

Beitrag von dtp » 21.01.2023, 15:15

Hallo,

ich nutze zwar in den meisten Räumen HMIP-Aktoren für meine Leuchtmittel, doch das geht leider nicht immer bzw. ist manchmal auch deutlich teurer als eine Fertiglösung. So hatte ich mir im Wohnzimmer meiner Mietwohnung z.B. eine LED-Leuchte von Paulmann für meinen Esstisch ausgesucht, die per Zigbee ansteuerbar ist, weil ich keine andere LED-Leuchte zu einem annehmbaren Preis gefunden hatte, die dimmbar ist. Die Leuchte konnte ich problemlos mit meinem Tradfri Gateway von IKEA verknüpfen, das ich bereits für die FYRTUR und KADRIJL Innenrollos von IKEA im Wohnzimmer, Schlafzimmer und Büro nutzte.
2023-01-21 12-21-04.jpg
Nachdem ich alles über den tradfri-Adapter in den ioBroker eingebunden hatte, war eine Sprachsteuerung per Siri mittels yahka-Adapter möglich. Das soll aber nicht Gegenstand dieser Projektvorstellung sein. Das Problem war nämlich, dass es zwar für die Leuchten und Rollos Fernbedienungen gab, die sind aber zum Einen außer Gefecht gesetzt, wenn man das Tradfri Gateway nutzt, und zum Anderen sind sie nicht gerade ein optischer Blickfang, insbesondere, wenn man sie an der Wand befestigen möchte. Daher stellte ich mir die Frage, ob ich nicht auch alles mit einem Wandtaster von HomeMatic steuern könnte, z.B. einem HMIP-WRC2 oder HmIP-WRC6 (bitte fragt mich nicht, warum eQ-3 das "m" mal groß und mal klein schreibt ;)).
2023-01-21 12-10-19.jpg
Im ioBroker werden die mit dem Tradfri-Gateway verknüpften Geräte wie folgt als Objekte angelegt.
2023-01-21_14h25_49.jpg
Wie man sieht, gibt es auch den Räumen zugeordnete Gruppen, die man bei Bedarf ansteuern kann. Will man dagegen nicht alle Geräte einer Gruppe ansteuern, dann muss man sich etwas anderes überlegen. Dazu später mehr.

Zunächst einmal zu den HomeMatic-Tastern. Nachfolgend ein Beispiel für einen HmIP-WRC6, wie er im ioBroker dargestellt wird.
2023-01-21_14h30_42.jpg
Ein Problem ist, dass die einzelnen Datenpunkte ihren Status nicht aktualisieren, wenn sie nicht mit einem Programm oder einem Aktor verknüpft sind. Man müsste sich also z.B. ein Dummy-Programm auf der CCU erstellen, um dies zu erreichen. Aber es gibt einen Weg, das zu umgehen. Nämlich das folgende Javaskript, das man einfach im javaskript-Adapter des ioBroker ein Mal für den Datenpunkt PRESS_SHORT jedes Kanals 1 bis 6 ausführen muss. Also einfach das Skript insgesamt sechs Mal aufrufen und ":1" am Ende von "channel" entsprechend bis ":6" ändern.

Code: Alles auswählen

// Dieses Skript nur ein Mal für jeden Taster-Kanal ausführen
const inst = 'hm-rpc.1'; // Instanz anpassen
const channel = '000B5D8995CA16:1'; // Kanal-Adresse anpassen
const channelType = 'PRESS_SHORT'; // Kanal-Typ anpassen

sendTo(inst, 'reportValueUsage', {ID: channel, paramType: channelType, params: 1}, res => {
    log(JSON.stringify(res));
});
Unmittelbar nach jedem Ausführen erscheint eine Servicemeldung in der CCU, wonach Konfigurationsdaten zur Übertragung anstehen. Diese Meldung jeweils kurz durch eine Tasterbetätigung quittieren. Fertig. Fortan aktualisiert der Taster seinen Status ohne extra Programm. Hat man den Taster auf diese Weise vorbereitet, kann man die nachfolgenden, im javascript-Adapter abgespeicherten Skripte direkt ausführen.

Mit dem ersten Skript steuert man eine oder mehrere Leuchten an. Ich wollte hier das Verhalten von HomeMatic nachempfinden. Sprich, ein kurzer Tastendruck schaltet die Leuchte(n) an oder aus, ein langer Tastendruck dimmt die Leuchte(n) hoch oder runter. Im Skript müssen dazu nur die ID des auslösenden HMIP-Tasters und die IDs der anzusteuernden Leuchten eingetragen werden. Außerdem muss man noch angeben, welcher Kanal des Tasters zum Einschalten bzw. Hochdimmen und welcher zum Ausschalten bzw. Runterdimmen genutzt werden soll.

Weiterhin können mit "step" die Schrittweite für das Dimmen in Prozent und mit "speed" die Dauer pro Dimmschritt in Sekunden angegeben werden. Mit den Parametern "rampUpShort" und "rampDownShort" kann man festlegen, ob die Leuchten bei kurzem Tastendruck sofort an- oder ausgehen sollen, oder ob sie mit den für "step" und "speed" vorgegeben Werten automatisch hoch- oder runterdimmen. Ein erneuter kurzer Tastendruck stoppt die Rampe beim gewünschten Wert.

Der mit Version 1.2 des Skripts implementierte Parameter "shutOffTime" dient zum automatischen Ausschalten der Leuchten N Minuten nach der letzten Tasterbetätigung. Mit dem Wert 0 kann man die Funktion außer Kraft setzen, so dass die Leuchten dauerhaft eingeschaltet bleiben.

Code: Alles auswählen

//Skript zum Steuern nicht HM-kompatibler Tradfri-Leuchten mittels HM-Tasterbetätigung
//Autor: dtp, Version: 1.3.1 
var triggerID = 'hm-rpc.1.000B5D8995CA16'; // ID des auslösenden Tasters
var channelUp = 2; // Kanal des auslösenden Tasters zum Einschalten/Hochdimmen
var channelDown = 1; // Kanal des auslösenden Tasters zum Ausschalten/Runterschalten
var bulbsIdList = ['tradfri.0.L-65547']; // ID-Liste der zu steuernden Leuchten (durch Komma trennen)

var step = 5; // Schrittweite beim Dimmen in Prozent
var speed = 0.5; // Dauer einen einzelnen Dimmschrittes in Sekunden
var rampUpShort = true; // Rampenfunktion bei kurzem Einschalt-Tastendruck (an: true, aus: false) 
var rampDownShort = false; // Rampenfunktion bei kurzem Ausschalt-Tastendruck (an: true, aus: false) 
var shutOffTime = 0; // Ausschaltzeit in Minuten (Werte <= 0 deaktivieren die Funktion)

var auxTriggerId = 'javascript.0.variables.bulbsAuxLivingroom'; // Hilfsvariable für die kurze Tasterbetätigung
var auxTriggerName = 'Hilfsvariable Wohnzimmer Wandtaster Licht'; // Name der Hilfsvariablen 

//weitere zu deklariende Variablen
var IntervalId, i, count = 1, timerId = null;

// Hilfsvariable für Taster anlegen 
if(!existsState(auxTriggerId)){
  createState(auxTriggerId, true, {name: auxTriggerName,  type: 'boolean', role: 'value'}, function () {}); 
} 
else{setState(auxTriggerId, true);}

// Funktion zum Ausschalten
function shutOff(){for(i = 0; i < bulbsIdList.length; i++) setState(bulbsIdList[i]+".lightbulb.brightness", 0);};

// Funktion für das Hochdimmen
function rampUp(count){
  IntervalId = setInterval(function(){
    count++;
    //log('ramp up'); log((count*step).toString());
    for(i = 0; i < bulbsIdList.length; i++) setState(bulbsIdList[i]+".lightbulb.brightness", count * step);
    if(count * step > 99) clearInterval(IntervalId);
  }, speed * 1000);
} 

// Funktion für das Runterdimmen
function rampDown(count){
  IntervalId = setInterval(function(){
    count++;
    //log('ramp down'); log((count*step).toString());
    for(i = 0; i < bulbsIdList.length; i++) setState(bulbsIdList[i]+".lightbulb.brightness", count * step);
    if(count * step > 99) clearInterval(IntervalId);
  }, speed * 1000);
}

// kurze Tasterbetätigung zum Einschalten (ggf. mit Rampe)
on({id: triggerID+"."+channelUp+".PRESS_SHORT", change: 'any', val: true}, async function (obj) {
  if(shutOffTime > 0){clearTimeout(timerId); timerId = setTimeout(shutOff, shutOffTime * 60000);} // automatisches Ausschalten
  if(!rampUpShort){
    for(i = 0; i < bulbsIdList.length; i++) setState(bulbsIdList[i]+".lightbulb.brightness", 100);
    count = 100 / step;
    clearInterval(IntervalId);
    setState(auxTriggerId, true);
  }
  else{
    if(getState(auxTriggerId).val){
      rampUp(count);
      setState(auxTriggerId, false);
    }
    else{
      clearInterval(IntervalId);
      setState(auxTriggerId, true);
    }
  }
});

// kurze Tasterbetätigung zum Ausschalten (ggf. mit Rampe)
on({id: triggerID+"."+channelDown+".PRESS_SHORT", change: 'any', val: true}, async function (obj) {
  if(shutOffTime > 0){clearTimeout(timerId); timerId = setTimeout(shutOff, shutOffTime * 60000);} // automatisches Ausschalten
  if(!rampDownShort){
    shutOff();
    count = 0;
    clearInterval(IntervalId);
    setState(auxTriggerId, true);
  }
  else{
    if(getState(auxTriggerId).val){
      rampDown(count);
      setState(auxTriggerId, false);
    }
    else{
      clearInterval(IntervalId);
      setState(auxTriggerId, true);
    }
  }
});

// lange Tasterbetätigung zum Hochdimmen
on({id: triggerID+"."+channelUp+".PRESS_LONG_START", change: 'any', val: true}, async function (obj) {rampUp(count);
});
on({id: triggerID+"."+channelUp+".PRESS_LONG_RELEASE", change: 'any', val: true}, async function (obj) {
  if(shutOffTime > 0){clearTimeout(timerId); timerId = setTimeout(shutOff, shutOffTime * 60000);} // automatisches Ausschalten
  clearInterval(IntervalId); 
});

// lange Tasterbetätigung zum Runterdimmen
on({id: triggerID+"."+channelDown+".PRESS_LONG_START", change: 'any', val: true}, async function (obj) {rampDown(count);
});
on({id: triggerID+"."+channelDown+".PRESS_LONG_RELEASE", change: 'any', val: true}, async function (obj) {
  if(shutOffTime > 0){clearTimeout(timerId); timerId = setTimeout(shutOff, shutOffTime * 60000);} // automatisches Ausschalten
  clearInterval(IntervalId); 
});
Das folgende Skript dient dann zum Ansteuern der Rollos. Die Eingaben enstprechen weitestgehend denen des Leuchten-Skripts. Bei der Schrittweite würde ich empfehlen, den Wert auf 1 zu belassen. Um den korrekten Wert für "speed" zu ermitteln, sollte man dann die Dauer messen, die das Rollo für eine vollständige Hoch- oder Runterfahrt benötigt und diesen Wert durch 100/"step" teilen. So ergibt sich z.B. für 30 Sekunden bei "step = 1" ein Wert von "speed = 0.3".

Code: Alles auswählen

//Skript zum Steuern nicht HM-kompatibler Tradfri-Rollos mittels HM-Tasterbetätigung
//Autor: dtp, Version: 1.0.2 
var triggerId = 'hm-rpc.1.000B5D8995CA16'; // ID des auslösenden Tasters
var channelUp = 5; // Kanal des auslösenden Tasters zum Hochfahren
var channelDown = 6; // Kanal des auslösenden Tasters zum Runterfahren
var blindsIdList = ['tradfri.0.B-65540','tradfri.0.B-65544']; // ID-Liste der zu steuernden Rollos (durch Komma trennen)

var step = 1; // Schrittweite für das Hoch- und Runterfahren in Prozent
var speed = 0.3; // Dauer eines einzelnen Schritts in Sekunden

var auxTriggerId = 'javascript.0.variables.blindsAuxLivingroom'; // Hilfsvariable für die kurze Tasterbetätigung
var auxTriggerName = 'Hilfsvariable Wohnzimmer Wandtaster Rollos'; // Name der Hilfsvariablen 

//weitere zu deklariende Variablen
var IntervalId; var i; var count = 1;

// Hilfsvariable für Taster anlegen 
if(!existsState(auxTriggerId)){
  createState(auxTriggerId, true, {name: auxTriggerName,  type: 'boolean', role: 'value'}, function () {}); 
} else{setState(auxTriggerId, true);}

// kurze Tasterbetätigung zum Hochfahren und Stoppen im Wechsel
on({id: triggerId+"."+channelUp+".PRESS_SHORT", change: 'any', val: true}, async function (obj) {
  if(getState(auxTriggerId).val){
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.position", 100);
    count = 100 / step;
    //log('go up');
    setState(auxTriggerId, false); setTimeout(function(){setState(auxTriggerId, true)}, 100000 * speed / step);
  }
  else{
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.stop", true);
    count = getState(blindsIdList[0]+".blind.position").val / step;
    //log('stopp');
    setState(auxTriggerId, true);
  }
});

// kurze Tasterbetätigung zum Runterfahren und Stoppen im Wechsel
on({id: triggerId+"."+channelDown+".PRESS_SHORT", change: 'any', val: true}, async function (obj) {
  if(getState(auxTriggerId).val){
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.position", 0);
    count = 0;
    //log('go down');
    setState(auxTriggerId, false); setTimeout(function(){setState(auxTriggerId, true)}, 100000 * speed / step);
  }
  else{
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.stop", true);
    count = getState(blindsIdList[0]+".blind.position").val / step;
    //log('stopp');
    setState(auxTriggerId, true);
  }
});

// lange Tasterbetätigung zum Hochfahren
on({id: triggerId+"."+channelUp+".PRESS_LONG_START", change: 'any', val: true}, async function (obj) {
  IntervalId = setInterval(function(){
    count++;
    //log((count * step).toString());
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.position", count * step);
    if(count * step > 99) clearInterval(IntervalId);
  }, speed * 1000);
});
on({id: triggerId+"."+channelUp+".PRESS_LONG_RELEASE", change: 'any', val: true}, async function (obj) {
  clearInterval(IntervalId);
});

// lange Tasterbetätigung zum Runterfahren
on({id: triggerId+"."+channelDown+".PRESS_LONG_START", change: 'any', val: true}, async function (obj) {
  IntervalId = setInterval(function(){
    count--;
    //log((count * step).toString());
    for(i = 0; i < blindsIdList.length; i++) setState(blindsIdList[i]+".blind.position", count * step);
    if(count * step < 1) clearInterval(IntervalId);
  }, speed * 1000);
});
on({id: triggerId+"."+channelDown+".PRESS_LONG_RELEASE", change: 'any', val: true}, async function (obj) {
  clearInterval(IntervalId); 
});
Wie ihr seht, habe ich im Rollo-Skirpt zwei Rollos eingetragen. Das funktioniert zwar soweit, allerdings kommt es dabei häufiger zum Ruckeln, wenn man die entsprechende Taste länger drückt. Vermutlich, weil nicht jeder Wert sauber an beide Rollos übergeben wird. Will man eh alle Rollos eines Raumes ansteuern, empfiehlt sich die Nutzung der zugehörigen Gruppe "G-xxxxxx". Dabei muss man dann aber beachten, überall im Skript ".blind.position" durch ".position" zu ersetzen. Das könnte ich aber noch in einer nächsten Version automatisieren.

Viel Spaß beim "Nachbauen".
Zuletzt geändert von dtp am 06.02.2023, 22:07, insgesamt 9-mal geändert.
CCU3 mit stets aktueller FW und den Addons "CUxD" und "Programmedrucken", ioBroker auf Synology DiskStation DS718+ im Docker-Container;
einige Projekte: zentrales Push-Nachrichten-Programm zPNP, DoorPi-Videotürsprechanlage, An- und Abwesenheitsdetektion per Haustürschloss, zentrales Programm zur Steuerung von Beschattungsgeräten zBSP.

dtp
Beiträge: 10657
Registriert: 21.09.2012, 08:09
System: CCU
Wohnort: Stuttgart
Hat sich bedankt: 320 Mal
Danksagung erhalten: 501 Mal

Re: IKEA Tradfri: Zigbee-Rollos und -Leuchten über ioBroker mit HM-Wandtaster steuern

Beitrag von dtp » 22.01.2023, 10:34

So,

nun habe ich das Skript für die Leuchten in der Version 1.1 noch um eine Rampenfunkion bei kurzem Tastendruck erweitert.
CCU3 mit stets aktueller FW und den Addons "CUxD" und "Programmedrucken", ioBroker auf Synology DiskStation DS718+ im Docker-Container;
einige Projekte: zentrales Push-Nachrichten-Programm zPNP, DoorPi-Videotürsprechanlage, An- und Abwesenheitsdetektion per Haustürschloss, zentrales Programm zur Steuerung von Beschattungsgeräten zBSP.

dtp
Beiträge: 10657
Registriert: 21.09.2012, 08:09
System: CCU
Wohnort: Stuttgart
Hat sich bedankt: 320 Mal
Danksagung erhalten: 501 Mal

Re: IKEA Tradfri: Zigbee-Rollos und -Leuchten über ioBroker mit HM-Wandtaster steuern

Beitrag von dtp » 22.01.2023, 11:53

Und schon kommt Version 1.2 des Leuchten-Skripts. ;)

Hab noch eine Timer-Funktion integriert, so dass sich die Leuchte nach der voreingestellten Zeit automatisch abschaltet. Man kann sich zwar darüber streiten, ob das sinnvoll ist, weil dieser Timer ja nur bei Tasterbetätigung wirkt und nicht, wenn man die Leuchte per Sprache oder App schaltet, aber schaden tut's auch nicht. ;)

Es wäre aber auch denkbar die Timer-Funktion in ein separates Skript zu implementieren, so dass die Leuchte auf jeden Schaltbefehl reagiert.

So ein Skript könnte z.B. wie folgt aussehen und muss für jede Leuchte angelegt werden.

Code: Alles auswählen

var bulbId = 'tradfri.0.L-65547'; // ID der zu steuernden Leuchte
var shutOffTime = 0; // Ausschaltzeit in Minuten (Werte <= 0 deaktivieren die Funktion)

//weitere zu deklariende Variablen
var timerId = null;

// Funktion zum Ausschalten
function shutOff(){setState(bulbId+".lightbulb.brightness", 0);}; 

// Aktion, wenn sich der Helligkeitswert verändert hat
on({id: bulbId+'.lightbulb.brightness', change: 'ne'}, async function (obj) {
  if(shutOffTime > 0){
    clearTimeout(timerId); 
    timerId = setTimeout(shutOff, shutOffTime * 60000);
  }
});
CCU3 mit stets aktueller FW und den Addons "CUxD" und "Programmedrucken", ioBroker auf Synology DiskStation DS718+ im Docker-Container;
einige Projekte: zentrales Push-Nachrichten-Programm zPNP, DoorPi-Videotürsprechanlage, An- und Abwesenheitsdetektion per Haustürschloss, zentrales Programm zur Steuerung von Beschattungsgeräten zBSP.

Antworten

Zurück zu „Projektvorstellungen“