Autor Thema: Human Centric Lighting ( HCL )  (Gelesen 772 mal)

Offline freakadings

  • Jr. Member
  • **
  • Beiträge: 79
Human Centric Lighting ( HCL )
« am: 27 Dezember 2019, 13:35:59 »
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");
}
Gefällt mir Gefällt mir x 3 Informativ Informativ x 2 Hilfreich Hilfreich x 1 Liste anzeigen

Offline juppzupp

  • Full Member
  • ***
  • Beiträge: 490
  • Anti-Dev ;)
Antw:Human Centric Lighting ( HCL )
« Antwort #1 am: 29 Dezember 2019, 11:54:43 »
Ein schönes Weihnachtsgeschenk. Danke dafür !
Gefällt mir Gefällt mir x 1 Liste anzeigen

Offline Johnnyflash

  • Jr. Member
  • **
  • Beiträge: 90
Antw:Human Centric Lighting ( HCL )
« Antwort #2 am: 24 Februar 2020, 17:06:47 »
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)
« Letzte Änderung: 24 Februar 2020, 21:13:40 von Johnnyflash »