"Erweitertes Night Shift" für Philips Hue bzw. Lampen mit Farbtemperatur

Begonnen von jojoja, 19 Januar 2020, 00:58:56

Vorheriges Thema - Nächstes Thema

jojoja

Hallo zusammen,

ich wollte eine Art Night Shift für die Philips Hue Lampen, bei dem
- morgens ist das Licht auf kalt gestellt (zum Wach werden)
- über den Tag neutral, um möglichst hell sein
- zum Sonnenuntergang auf wärmer/ambient
- beim Zubettgehen auf maximal warm
der Verlauf ist auch im Bild zu sehen (nach oben wird's wärmer).

Das Ganze soll abhängig vom Sonos Wecker sein, ist keiner gestellt wird auf die Zeitpunkte vom Sonnenaufgang/-untergang zurück gegriffen.

Dabei wird per wiederholtem at eine Funktion in den myUtils ausgeführt, in der der aktuelle Wert für ct berechnet wird. Der Funktion wird ein Device übergeben, in dem per setreading der ct Wert gesetzt wird. Auf dieses Reading wird per notify gelauscht und entsprechend die Lampen gesetzt. Damit ist Berechnung und Steuerung getrennt. Kurz:
at -> calculateLightTemperatureForTime(<Device>) -> notify <Device>:lightTemp:.* -> set ct
In meinem Fall hier ist <Device> = rr_Johannes

Hier die Funktion und die Abhängigkeiten, damit die Berechnung funktioniert müssen alle Zeitpunkte im Unix Timestamp Format vorliegen:
sub calculateLightTemperatureForTime($){
my ($resident) = @_;
my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime;
my ($ct, $tA, $tB, $tC, $tD) = (0,0,0,0,0);

# Zeitpunkte ermitteln
my $t  = gettimeofday();
my $tZ = int(gettimeofday() - ($sec + 60*$min + 3600*$hour));
my $tSunrise = $tZ + int(time2dec(ReadingsVal('astro', 'SunRise', '').":00") * 3600);
my $tSunset  = $tZ + int(time2dec(ReadingsVal('astro', 'SunSet', '').":00") * 3600);

my %alarms = getSonosAlarm("Schlafzimmer");
my $tAlarm = $alarms{dayToString($wday)}{StartTime};
if ($tAlarm){
$tA = $tZ + int(time2dec($tAlarm) * 3600);
$tB = $tA + 3600;
}
else{
$tA = $tSunrise;
$tB = $tSunrise + 3600;
}

my $tC = $tSunset;

my $tNextAlarm = $alarms{dayToString($wday + 1)}{StartTime};
if ($tNextAlarm){
$tD = $tZ + int(time2dec($tNextAlarm) * 3600) + ((24-9) * 3600); # 9h vor nächsem Wecker, 24 weil nächster Tag
}
else{
$tD = $tSunset + 7200;
}
if ($tC >= $tD){
$tC = $tD - 3610;
}

my $tA0 = $tA;
my $tA1 = $tA + 1800;
my $tC0 = $tC - 1800;
my $tC1 = $tC + 1800;
my $tD0 = $tD - 1800;
my $tD1 = $tD + 1800;

# Berechnung nach Zeitbereichen

if ($t < $tA){
$ct = 454;
}
elsif ($t >= $tA and $t < $tA1){
$ct = 224;
}
elsif ($t >= $tA1 and $t < $tB){
$ct = int(247 - 23*cos( (1/($tB-$tA1)) * (pi()* ($t-$tA1) )));
}
elsif ($t >= $tB and $t < $tC0){
$ct = 270;
}
elsif ($t >= $tC0 and $t < $tC1){
$ct = int(325 - 55*cos( (1/($tC1-$tC0)) * (pi()* ($t-$tC0) )));
}
elsif ($t >= $tC1 and $t < $tD0){
$ct = 380;
}
elsif ($t >= $tD0 and $t < $tD1){
$ct = int(417 - 37*cos( (1/($tD1-$tD0)) * (pi()* ($t-$tD0) )));
}
elsif($t >= $tD1){
$ct = 454;
}

my $ctOld = ReadingsVal($resident, 'lightTemp', '');
if ($ctOld != $ct){
fhem("setreading $resident lightTempOld ".$ctOld);
fhem("setreading $resident lightTemp ".$ct);
}

}

sub getSonosAlarm($){
my ($room) = makeSonosParameter (shift);
my @days = ("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday");
my @IDs = split(/\,/, ReadingsVal($room, "AlarmListIDs", 0));
my $AlarmList = eval(ReadingsVal($room, "AlarmList", "{}"));
my %Alarms = ();

foreach my $day (@days){
foreach my $ID (@IDs){
if (int($AlarmList->{$ID}{"Recurrence_".$day}) == 1){
$Alarms{$day}{StartTime} = $AlarmList->{$ID}{StartTime} if ($AlarmList->{$ID}{Enabled} eq '1');
}
}
}
return %Alarms;
}

sub makeSonosParameter($){
return makeDeviceParameter(ucfirst(shift), "sonos_");
}

sub makeDeviceParameter($$){
my ($device,$prefix) = @_;
if(index($device, $prefix) == -1){
$device = $prefix.$device;
}
return $device;
}

sub dayToString($){
use Switch;
my ($day) = @_;
my $erg;
while($day > 6){
$day -= 7;
}
switch ($day){
    case 0 {$erg = "Sunday"}
        case 1 {$erg = "Monday"}
        case 2 {$erg = "Tuesday"}
        case 3 {$erg = "Wednesday"}
        case 4 {$erg = "Thursday"}
        case 5 {$erg = "Friday"}
        case 6 {$erg = "Saturday"}
        else {return $day}
    }
return $erg;
}

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)
}


Damit diese regelmäßig ausgeführt wird brauchen wir folgendes at:
define rr_JohannesCalculateLightTemp at +*00:00:15 {calculateLightTemperatureForTime("rr_Johannes")}

Das notify wird ausgeführt, wenn der ct Wert sich ändert. Hier müssen in das Array @devices die Lampen aufgelistet sein, welche angesteuert werden sollen und <Device> einsetzen:
define hue_setLightTemp notify <Device>:lightTemp:.* {\
my (@devices) = ("hue_BadDeckenlampe", "hue_WohnzimmerDeckenlampe", "hue_BueroDeckenlampe");;\
my $device;;\
foreach $device (@devices){\
if (ReadingsVal($device, "reachable", 0) == 1){\
if (ReadingsVal("rr_Johannes", "lightTempOld",0) == ReadingsNum($device, "ct", 0)){\
fhem("set $device ct ".ReadingsVal("rr_Johannes", "lightTemp", 0)." 5");;\
}\
}\
}\
}


Wenn (wie in meinem Fall) die Lampen stromlos geschaltet werden, muss der aktuelle ct Wert gesetzt werden sobald die Lampe eingeschaltet wurde. Auch hier müssen die Lampennamen und <Device> angepasst werden:
define hue_LampReachable notify hue_(BueroDeckenlampe|BadDeckenlampe|WohnzimmerDeckenlampe):reachable:.1 {\
fhem("set $NAME ct ".ReadingsVal("<Device>", "lightTemp",0)." 5");;\
}
}


Momentan greife ich noch auf das Astro Modul zu:
define astro Astro
Das möchte ich aber gegebenfalls durch fhem-interne Mittel ersetzen.


Falls jemand Interesse hat, gerne testen :D ich möchte auch das Residents Modul mehr einbeziehen. Wer allgemein Vorschläge hat möge sie gerne nennen!

Vielen Dank und viel Spaß
Johannes
FHEM 6.0 @ IntelNUC6CAYH;  Unifi USG, Switch, AP AC Pro; HM-MOD-UART;  Sonos Play 1 & 3, One, Beam; Philips Hue

slor

Fhem auf Raspberry Pi 4
CCU3 mit RaspberryMatic mit HMCCU an FHEM
HMCCU, Telegram, Conbee2 und Hue/Tradfri/Osram Lampen AQARA Sensoren, HomeConnect