Human Centric Lighting ( HCL )

Begonnen von freakadings, 27 Dezember 2019, 13:35:59

Vorheriges Thema - Nächstes Thema

freakadings

Hallo allerseits,

da mich das Thema Human Centric Lighting nicht mehr losgelassen hat, habe ich es mal im Freundeskreis zur Sprache gebracht und zusammen haben wir dann einen Algorithmus entwickelt der das ganze umsetzen kann.
Ein Freund hat das dann in Perl verfasst (Danke Andi!) und ich habe den FHEM-part übernommen.

Hiermit ist es jetzt möglich bei Lichtern die einen Farbtemperaturwert ("ct") annehmen können (wie bspw. die Philips Hue Leuchtmittel) diesen dynamisch über den Tag hinweg zu ändern, sodass auch in Räumen der Eindruck eines natürlichen
Tageslichtverlaufs entsteht.


Konkret sieht das derzeit so aus:
Ein AT ruft alle 5 Minuten eine Funktion in der 99_MyUtils auf, diese berechnet den Kelvin wert der aktuellen Uhrzeit und gibt diesen zurück.
Das AT setzt dann bei allen Lampen die gerade den state "on" haben (bzw. den state "nicht aus", also auch bei gedimmten Lampen "dim75%") diese Farbänderung um.
Weiterhin speichert das AT den Farbwert in ein Reading eines Dummys, sodass wenn Lampen eingeschaltet werden dieser aktuelle Wert dann direkt beim Einschalten gesetzt wird.
Der Dummy ist nicht nötig, wenn die Farbtemperatur nicht direkt beim Einschalten eines Gerätes gesetzt werden soll. Der Farbton wird dann nach max 4min59sek geändert.

Achtung: Manuelle Farbeinstellungen werden nach maximal 4min59sek Minuten wieder überschrieben.


But wait! There is more!

Da ein normaler linearer Verlauf ein wenig zu öde ist, haben wir noch einen linearen Verlauf mit einer Plateauphase gebastelt und den Herrn Gauß bemüht einen schönen Graphen für uns zu beschreiben.


calcNormalizedTriangle - Der Wert steigt linear, bis zum Mittelpunkt des Tages an und fällt ebenfalls wieder linear bis zum ende des Tages ab.

calcNormalizedTriangleWithPlateau - wie oben nur, dass er bis zum Beginn der Plateauphase linar steigt, dann bleibt über einen definierten Zeitraum der Höchste Wert bestehen, bis am Ende der Plateauphase dieser wieder linear abfällt.

calcNormalizedGaussian - Die Gaußsche Normalverteilungskurve: Siehe Google Bildersuche: https://tinyurl.com/wfjbhea


Sicherlich ist das ganze noch nicht nicht perfekt, aber es funktioniert bei mir derzeit wie es soll :)

=================================================================



HowTo:


Personalisieren:


Um einen anderen Graphen zu verwenden muss in der "hcl"-Funktion die entsprechende Zeile auskommentiert werden und die anderen durch ein vorangestelltes # kommentiert werden.
$iCT = $iCTMin + ($iCTDelta * calcNormalizedGaussian( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangleWithPlateau( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangle( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)

(so wäre calcNormalizedGaussian aktiviert)



In der "hcl"-Funktion könnt ihr folgende Werte anpassen:
my $iTimeMin = 6; #Startuhrzeit des dyn. Verlaufs (Uhrzeit dezimal zB 06:15Uhr -> 6.25)
my $iTimeMax = 20; #Enduhrzeit des dyn. Verlaufs

my $iCTMin   = 2000; #Waermster Farbton der LED zB für die Dämmerungszeit (zB: 2000 Kelvin oder 154 Mireds)
my $iCTMax   = 6493; #Kaeltester Farbton der LED zB für die Mittagszeit   (zB: 6493 Kelvin oder 500 Mireds)


Und wenn ihr die Plateuphase nutzen möchtet, könnt ihr diese in der "calcNormalizedTriangleWithPlateau"-Funktion anpassen:
my $plateau = 0.5; #Plateauphase in % auf die den gesamten dyn. Zeitraum gesehen. $plateau = dyn-zeitraum/plateaudauer.
# Bsp: Die Plateauphase soll 2h dauern, also eine Stunde vor Mittag beginnen und bis eine Stunde nach Mittag andauern.
# Der dyn. Bereich geht von 6:00 Uhr bis 18:00 Uhr =>   der dyn. Zeitraum beträgt 12 Stunden. => $plateau = 12/2 => $plateau = 0.17 (Gerundet da eigentlich 0.1666666666...)



Und im AT natürlich müsst ihr definieren welche Lampen geschaltet werden sollen. In meinem Beispiel sind es alle nicht-ausgeschalteten Hue Leuchtmittel.
Außerdem könnt ihr hier das Abfrageintervall ändern indem ihr in der Definition die "00:05:00" und im Attribut alignTime die "00:05" auf zB "00:06:00" und "00:06" ändert, wenn Intervall 6 Minuten betragen soll.






Die Funktionen und FHEM-Devices:


#======== in die 99_MyUtils einfügen:


sub calcNormalizedTriangle #Diese Kurve steigt linear von (iTimeMin) bis zum Mittelpunkt (center) an und faellt danach wieder linar ab bis (iTimeMax)
{
my $x = shift;

my $center = 0.5; #Zentrum in Prozent, wann der virt. Sonnenhoechststand erreicht ist zwischen 0 und 1. Fuer die genaue Mitte (zwischen Start- und Enduhrzeit) -> 0.5

if( $x < 0   ||   $x > 1 )
{
return 0;
}
elsif( $x < $center )
{
return (1/$center)*$x;
}
elsif( $x > $center )
{
return (-1/$center)*$x + (1/$center);
}
else
{
return 1;
}
}


sub calcNormalizedTriangleWithPlateau #Wie calcNormalizedTriangle, nur kann hier eine Plateauphase -ein Zeitraum in dem die Farbtemperatur sich nicht ändert- angegeben werden. zB für die Mittagszeit
{
my $x = shift;

my $plateau = 0.5; #Plateauphase in % auf die den gesamten dyn. Zeitraum gesehen. $plateau = dyn-zeitraum/plateaudauer.
# Bsp: Die Plateauphase soll 2h dauern, also eine Stunde vor Mittag beginnen und bis eine Stunde nach Mittag andauern.
# Der dyn. Bereich geht von 6:00 Uhr bis 18:00 Uhr =>   der dyn. Zeitraum beträgt 12 Stunden. => $plateau = 12/2 => $plateau = 0.17 (Gerundet da eigentlich 0.1666666666...)

my $center  = 0.5; #Relatives Zentrum der Plateauphase zwischen 0 und 1. Fuer die genaue Mitte (zwischen Start- und Enduhrzeit) -> 0.5

my $a = $center - ($plateau/2);
my $b = $center + ($plateau/2);

if( $x < 0   ||   $x > 1 )
{
return 0;
}
elsif( $x < $a )
{
return (1/$a)*$x;
}
elsif( $x > $b )
{
return (-1/$a)*$x + (1/$a);
}
else
{
return 1;
}
}


sub calcNormalizedGaussian #Diese Funktion beschreibt den Graph der Gaussschen Normalverteilung, siehe Google Bildersuche: https://tinyurl.com/wfjbhea
{
my $x = shift;

my $m = 0.5;
my $s = 0.125; #Oeffnungsfaktor des Graphen: Standard 0.125
return exp( -1 * (($x-$m)**2 / (2*($s**2))) );
}



sub hcl
{


my $iTimeMin = 6; #Startuhrzeit des dyn. Verlaufs (Uhrzeit dezimal zB 06:15Uhr -> 6.25)
my $iTimeMax = 20; #Enduhrzeit des dyn. Verlaufs

my $iCTMin   = 2000; #Waermster Farbton der LED zB für die Dämmerungszeit (zB: 2000 Kelvin oder 154 Mireds)
my $iCTMax   = 6493; #Kaeltester Farbton der LED zB für die Mittagszeit   (zB: 6493 Kelvin oder 500 Mireds)

my $iCTDelta = $iCTMax-$iCTMin; #Farbtonbereich des Übergangs

my $iTimeNow;
my $iCT      = 4500; #Farbtemperaturwert der im Falle eines Fehlers greift. (hier 4500 Kelvin)


my ($min, $hr) = (localtime())[1,2];          #Liest die aktuelle Systemzeit aus
$iTimeNow = ($hr + ($min/60));               

if( $iTimeMin < $iTimeNow  &&  $iTimeNow < $iTimeMax ) #Wenn im dynamischen Zeitraum
{
my $n = ($iTimeNow - $iTimeMin) / ($iTimeMax-$iTimeMin); #Liefert prozentualen Wert zwischen 0 (=$iTimeMin) und 1 (=$iTimeMax)
$iCT = $iCTMin + ($iCTDelta * calcNormalizedGaussian( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangleWithPlateau( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangle( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
}
else #Wenn ausserhalb der dyn. Zeit
{
$iCT = $iCTMin; #Wird der waermste Farbton gesetzt
}
return int($iCT); #Rueckgabe der berechneten Farbtemperatur zur aktuellen Uhrzeit
}




#======== Dummy anlegen um hcl Wert später abzugreifen:

defmod transferDummy dummy
attr transferDummy room hcl


#======== Das At das alle 5 Minuten (immer um XX:00 XX:05 XX:10 ... XX:55) Die Werte neu generiert und bei Devices die eingeschaltet sind (bzw. "nicht ausgeschaltet" sind) die Werte setzt

defmod hclAlleFuenfMinuten at +*00:05:00 {\
\
my $hclVal = hcl();;\
fhem("setreading transferDummy hcl_value $hclVal");;\
\
fhem("set HUEDevice.*:FILTER=STATE!=off ct $hclVal");;\
\
}
attr hclAlleFuenfMinuten alignTime 00:05
attr hclAlleFuenfMinuten room hcl
attr hclAlleFuenfMinuten verbose 2


#======== Hue Ersatz für toggle funktion (bei "on" wird der hcl wert aus dem Dummy gesetzt)

if (ReadingsVal("HUEDevice1", "state", "") eq "off")
{
my $ct = ReadingsNum("transferDummy", "hcl_value", "2000");
fhem("set HUEDevice1 ct $ct");
}
else
{
fhem("set HUEDevice1 off");
}

juppzupp

Ein schönes Weihnachtsgeschenk. Danke dafür !

Johnnyflash

#2
Hallo,
super Arbeit  :)

Ich habe das Script noch dahingehend erweitert, dass neben der Farbtemperatur auch noch die Helligkeit berechnet wird. Die Anfangs- und Endzeit wird durch Sonnenauf- und Sonnenuntergang bestimmt. Weiterhin schreibe ich die Werte direkt im Script in den Dummy. Daraus ließe sich bestimmt auch schnell ein eigenständiges Modul schreiben, dafür fehlt mir allerdings die Erfahrung und die Zeit.

Hier meine 99_HCLUtils.pm

package main;

use strict;
use warnings;
use POSIX;

sub
HCLUtils_Initialize($$)
{
  my ($hash) = @_;
}

# Enter you functions below _this_ line.

sub calcNormalizedTriangle #Diese Kurve steigt linear von (iTimeMin) bis zum Mittelpunkt (center) an und faellt danach wieder linar ab bis (iTimeMax)
{
my $x = shift;

my $center = 0.5; #Zentrum in Prozent, wann der virt. Sonnenhoechststand erreicht ist zwischen 0 und 1. Fuer die genaue Mitte (zwischen Start- und Enduhrzeit) -> 0.5

if( $x < 0   ||   $x > 1 )
{
return 0;
}
elsif( $x < $center )
{
return (1/$center)*$x;
}
elsif( $x > $center )
{
return (-1/$center)*$x + (1/$center);
}
else
{
return 1;
}
}


sub calcNormalizedTriangleWithPlateau #Wie calcNormalizedTriangle, nur kann hier eine Plateauphase -ein Zeitraum in dem die Farbtemperatur sich nicht ändert- angegeben werden. zB für die Mittagszeit
{
my $x = shift;

my $plateau = 0.5; #Plateauphase in % auf die den gesamten dyn. Zeitraum gesehen. $plateau = dyn-zeitraum/plateaudauer.
# Bsp: Die Plateauphase soll 2h dauern, also eine Stunde vor Mittag beginnen und bis eine Stunde nach Mittag andauern.
# Der dyn. Bereich geht von 6:00 Uhr bis 18:00 Uhr =>   der dyn. Zeitraum beträgt 12 Stunden. => $plateau = 12/2 => $plateau = 0.17 (Gerundet da eigentlich 0.1666666666...)

my $center  = 0.5; #Relatives Zentrum der Plateauphase zwischen 0 und 1. Fuer die genaue Mitte (zwischen Start- und Enduhrzeit) -> 0.5

my $a = $center - ($plateau/2);
my $b = $center + ($plateau/2);

if( $x < 0   ||   $x > 1 )
{
return 0;
}
elsif( $x < $a )
{
return (1/$a)*$x;
}
elsif( $x > $b )
{
return (-1/$a)*$x + (1/$a);
}
else
{
return 1;
}
}


sub calcNormalizedGaussian #Diese Funktion beschreibt den Graph der Gaussschen Normalverteilung, siehe Google Bildersuche: https://tinyurl.com/wfjbhea
{
my $x = shift;

my $m = 0.5;
my $s = 0.125; #Oeffnungsfaktor des Graphen: Standard 0.125
return exp( -1 * (($x-$m)**2 / (2*($s**2))) );
}



sub hcl
{


my $iTimeMin = time2dec(sunrise_abs()); #Startuhrzeit des dyn. Verlaufs (Uhrzeit dezimal zB 06:15Uhr -> 6.25)
my $iTimeMax = time2dec(sunset_abs()); #Enduhrzeit des dyn. Verlaufs

my $iCTMin   = 3000; #Waermster Farbton der LED zB für die Dämmerungszeit (zB: 2000 Kelvin oder 154 Mireds)
my $iCTMax   = 6000; #Kaeltester Farbton der LED zB für die Mittagszeit   (zB: 6493 Kelvin oder 500 Mireds)

my $iPCTMin   = 20; #Geringster Dimmwert
my $iPCTMax   = 80; #Maximaler Dimmwert


my $iCTDelta = $iCTMax-$iCTMin; #Farbtonbereich des Übergangs

my $iPCTDelta = $iPCTMax-$iPCTMin; #Helligkeitsbereich des Übergangs

my $iTimeNow;
my $iCT      = 3000; #Farbtemperaturwert der im Falle eines Fehlers greift. (hier 3000 Kelvin)

my $iPCT      = 50; #Helligkeitswert der im Falle eines Fehlers greift. (hier 50)


my ($min, $hr) = (localtime())[1,2];          #Liest die aktuelle Systemzeit aus
$iTimeNow = ($hr + ($min/60));               

if( $iTimeMin < $iTimeNow  &&  $iTimeNow < $iTimeMax ) #Wenn im dynamischen Zeitraum
{
my $n = ($iTimeNow - $iTimeMin) / ($iTimeMax-$iTimeMin); #Liefert prozentualen Wert zwischen 0 (=$iTimeMin) und 1 (=$iTimeMax)
my $curve = calcNormalizedGaussian( $n );
$iCT = $iCTMin + ($iCTDelta * $curve); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
$iPCT = $iPCTMin + ($iPCTDelta * $curve); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangleWithPlateau( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
#$iCT = $iCTMin + ($iCTDelta * calcNormalizedTriangle( $n )); #Moegliche Kurven: calcNormalizedTriangleWithPlateau; calcNormalizedTriangle; calcNormalizedGaussian (Einfach Funktionsnamen austauschen)
}
else #Wenn ausserhalb der dyn. Zeit
{
$iCT = $iCTMin; #Wird der waermste Farbton gesetzt
$iPCT = $iPCTMin; #und die geringste Helligkeit
}
fhem("setreading transferDummy hcl_value ".int($iCT)."");
fhem("setreading transferDummy hcl_percentage ".int($iPCT)."");
}

sub time2dec($){
  my ($h,$m,$s) = split(":", shift);
  $m = 0 if(!$m);
  $s = 0 if(!$s);
  my $t  = $m * 60;
     $t += $s;
     $t /= 3600;
     $t += $h;
  return ($t)
}
1;


Das AT sieht dann wie folgt aus:

define hclAlleFuenfMinuten at +*00:05:00 {\
\
hcl();;\
my $ct = ReadingsNum("transferDummy", "hcl_value", "3000");;\
my $pct = ReadingsNum("transferDummy", "hcl_percentage", "50");;\
fhem("set .*:FILTER=power!=off:FILTER=TYPE=YeeLight ct $ct");;\
fhem("set .*:FILTER=power!=off:FILTER=TYPE=YeeLight bright $pct");;\
}


(Bei mir triggert das alle YeeLights)

Screambear

Moin,
ich finde dieses Thema auch spannend, zudem ist es bestimmt auch eine nette Spielerei.

Nun finde ich es nicht so schön dass, wenn ich meine Lampen z.b. auf grün geschaltet habe sie dann nach 5 Minuten wieder ihre Farbe wechseln.
Nun hatte ich die Idee das man noch den colormode der Hue Lampen abfragen könnte. Steht diese auf "ct" wird das hcl verwendet, ansonsten wird dies ignoriert.

Ist dies leicht umzusetzen? Ich bin nun noch nicht so der Profi in der Programmierumgebung mit Fhem.

Danke und Gruß, Screambear

Johnnyflash

Moin,
das sollte einfach sein. Überall wo du durch einen Automatismus (Doif, AT oder was auch immer) ein "set" auf deine Lampen ausführst, kannst du ja Filter setzen. Ein FILTER=colormode=ct sollte deine Anforderung schon erfüllen. Mehrere Filter man man per Doppelpunkt verbinden (siehe bei meinem AT)

Screambear

Danke für den Tipp. Da hatte ich auch echt selber drauf kommen können...

Macht es eigentlich Sinn hcl zu verwenden bei Lampen die eh nur an gehen wenn es dunkel wird?

freakadings

Oh, da hatte ich wohl keine Benachrichtigungen aktiviert :D

@Johnnyflash: Sehr cool! Mit der Helligkeit wollte ich auch noch mal schauen.

@Screambear:
a) Bzgl. der Farbänderung/-überschreibung: Das stimmt, das ist nicht optimal. Aber spontan fielen mir 2 mögliche "Lösungen" ein:
1. Man führt HCL nur auf den Lampen aus, wo es egal ist. Bspw. hast du ein LED-Panel, das nur die Farbtemperatur ändern kann und lässt RGBW-Leuchtmittel raus.
2. Du richtest einen Override-Modus: Ich habe zB ja meinen transferDummy, der bekommt einfach ein reading wie zB "hcl_override" und der ist eben aktiv ("1") oder inaktiv ("0"). So könntest du in deinem at am Anfang checken ob dieser Modus eben aktiviert ist, dann wird kein ct geändert oder inaktiv und hcl verbessert dein Leben ;D

b) "Macht es eigentlich Sinn hcl zu verwenden bei Lampen die eh nur an gehen wenn es dunkel wird?"
Ich würde sagen nein, da brauch es mMn nicht mal spezielle LED Lampen die die Farbe/Farbtemperatur ändern können. Ich nutze HCL eher als unbewusste unsterützung im Haus, wenn's draußen tagsüber dunkel (=sehr bewölkt) ist oder in dunklen Ecken von Zimmern um da doch (fiktives) Tageslicht zu haben.



============

Da wir im Juni umziehen werden, werde ich dort mal was anderes Probieren bzgl HCL:
Es gibt ja RGB-Sensoren zu kaufen, die die Farbe wahrnehmen. Meine Idee ist es so einen Wetterfest draußen zu verbauen in eine weiße, lichtdurchlässige Box, sodass er quasi nur Weißtöne registrieren kann und das dann mal nach innen auf die Hue Lampen weiter zu geben. Natürlich muss er dazu fast nur Himmel sehen können. Ob ich in dem rgb sensor irgendwie die Farbtemperatur berechnen kann, oder ob meine Hue Lampen dann im RGB-Modus laufen wird man sehen müssen.
Ganz fancy wäre es, wenn man die Änderungen von draußen (nahezu) live nach innen spiegeln kann :D Falls da jemand vor mir aktiv werden kann, wäre ich an Ergebnissen/Meinungen dazu interessiert :)

Grüße!