diff --git a/fhem/CHANGED b/fhem/CHANGED index a0f87aac9..9a5feb422 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 59_Twilight: get weather-dependend data by NotifyFn. + Existent DEF may be changed automatically! - feature: 32_speedtest: added ookla attribute - feature: 31_HUEDevice: added lastseen attribute - change: 57_SSCal: integrate SMUtils lib, minor fixes diff --git a/fhem/FHEM/59_Twilight.pm b/fhem/FHEM/59_Twilight.pm index aa327e534..cb2c180a0 100644 --- a/fhem/FHEM/59_Twilight.pm +++ b/fhem/FHEM/59_Twilight.pm @@ -4,7 +4,7 @@ # 59_Twilight.pm # Copyright by Sebastian Stuecker # erweitert von Dietmar Ortmann -# Maintained by igami since 02-2018 +# Orphan module, maintained by Beta-User since 09-2020 # # used algorithm see: http://lexikon.astronomie.info/zeitgleichung/ # @@ -32,670 +32,935 @@ # ############################################################################## -package main; +package FHEM::Twilight; ## no critic 'Package declaration' + use strict; use warnings; -#use List::Util qw(max min); -use HttpUtils; + use Math::Trig; -use Time::Local 'timelocal_nocheck'; +use Time::Local qw(timelocal_nocheck); +use List::Util qw(max min); +use GPUtils qw(GP_Import GP_Export); +eval { use FHEM::Core::Timer::Helper qw(addTimer removeTimer); 1 }; +use FHEM::Meta; + +#-- Run before package compilation +BEGIN { + + # Import from main context + GP_Import( + qw( + defs + attr + init_done + DAYSECONDS + HOURSECONDS + MINUTESECONDS + sr_alt + CommandAttr + CommandModify + looks_like_number + devspec2array + notifyRegexpChanged + deviceEvents + readingFnAttributes + readingsSingleUpdate + readingsBulkUpdate + readingsBeginUpdate + readingsEndUpdate + AttrVal + ReadingsVal + ReadingsNum + InternalVal + IsDisabled + Log3 + InternalTimer + RemoveInternalTimer + hms2h + h2hms_fmt + FmtTime + FmtDateTime + strftime + stacktrace + ) + ); +} + +sub ::Twilight_Initialize { goto &Initialize } +sub ::twilight { goto &twilight } ################################################################################ -sub Twilight_Initialize { - my $hash = shift; +sub Initialize { + my $hash = shift // return; -# Consumer - $hash->{DefFn} = "Twilight_Define"; - $hash->{UndefFn} = "Twilight_Undef"; - $hash->{GetFn} = "Twilight_Get"; - $hash->{AttrList}= "$readingFnAttributes " ."useExtWeather"; - return; + # Consumer + $hash->{DefFn} = \&Twilight_Define; + $hash->{UndefFn} = \&Twilight_Undef; + $hash->{GetFn} = \&Twilight_Get; + $hash->{NotifyFn} = \&Twilight_Notify; + $hash->{AttrFn} = \&Twilight_Attr; + $hash->{AttrList} = "$readingFnAttributes " . "useExtWeather"; + $hash->{parseParams} = 1; + return FHEM::Meta::InitMod( __FILE__, $hash ); } + + ################################################################################ -sub Twilight_Get { - my ($hash, @a) = @_; - return "argument is missing" if(int(@a) != 2); +sub Twilight_Define { + my $hash = shift; + my $aref = shift; + my $href = shift // return if !defined $aref; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + + #my $def = shift // return; + #my @arr = split m{\s+}xms, $def; - my $reading= $a[1]; - my $value; + return "syntax: define Twilight [ ] [indoorHorizon=... ] [weatherDevice=]" + if ( int(@$aref) < 2 || int(@$aref) > 6 ); + + my $DEFmayChange = int(@$aref) == 6 ? 1 : 0; + + my $weather = "none"; + $weather = pop @$aref if int(@$aref) == 6 || int(@$aref) == 4 && !looks_like_number($$aref[3]); + $weather = $$href{weatherDevice} // $weather; ##hashes don't work yet... - if(defined($hash->{READINGS}{$reading})) { - $value= $hash->{READINGS}{$reading}{VAL}; - } else { - return "no such reading: $reading"; - } - return "$a[0] $reading => $value"; + my $indoor_horizon = "none"; + $indoor_horizon = pop @$aref if int(@$aref) == 5 || int(@$aref) == 3; + $hash->{STATE} = "0"; + my $name = shift @$aref; + my $type = shift @$aref; + my $latitude = shift @$aref // AttrVal( 'global', 'latitude', 50.112 ); + my $longitude = shift @$aref // AttrVal( 'global', 'longitude', 8.686 ); + + if ($indoor_horizon eq "none") { $indoor_horizon = $$href{indoorHorizon} // 3 }; ##hashes don't work yet... + + return "Argument Latitude is not a valid number" + if !looks_like_number($latitude); + return "Argument Longitude is not a valid number" + if !looks_like_number($longitude); + return "Argument Indoor_Horizon is not a valid number" + if !looks_like_number($indoor_horizon); + + $latitude = min( 90, max( -90, $latitude ) ); + $longitude = min( 180, max( -180, $longitude ) ); + $indoor_horizon = + min( 20, max( -6, $indoor_horizon ) ); + + $hash->{WEATHER_HORIZON} = $indoor_horizon; + $hash->{INDOOR_HORIZON} = $indoor_horizon; + #CommandAttr( undef, "$name indoorHorizon $indoor_horizon") if $indoor_horizon; + $hash->{helper}{'.LATITUDE'} = $latitude; + $hash->{helper}{'.LONGITUDE'} = $longitude; + $hash->{SUNPOS_OFFSET} = 5 * 60; + #$hash->{LOT} = $LOT; + + $attr{$name}{verbose} = 4 if ( $name =~ m/^tst.*$/x ); + + Log3( $hash, 1, "[$hash->{NAME}] Note: Twilight formerly used weather info from yahoo, but source is offline. Using a guessed Weather type device instead if available!" + ) if looks_like_number($weather); + + my $useTimer = looks_like_number($weather) || ( $DEFmayChange && $latitude == AttrVal( 'global', 'latitude', 50.112 ) && $longitude == AttrVal( 'global', 'longitude', 8.686 ) ) ? 1 : 0; + + $hash->{DEFINE} = $weather ? $weather : 1; + InternalTimer(time(), \&Twilight_Change_DEF,$hash,0) if $useTimer; + + return InternalTimer( time()+$useTimer, \&Twilight_Firstrun,$hash,0) if !$init_done || $useTimer; + return Twilight_Firstrun($hash); } -################################################################################ -sub Twilight_Define -{ - my $hash = shift; - my $def = shift // return; - my @arr = split m{\s+}xms, $def; - - return "syntax: define Twilight [ [indoor_horizon [Weather]]]" - if(int(@arr) < 1 && int(@arr) > 6); - $hash->{STATE} = "0"; - my $name = shift @arr; - my $type = shift @arr; - my $latitude = shift @arr // AttrVal('global','latitude',50.112); - my $longitude = shift @arr // AttrVal('global','longitude',8.686); - - my $indoor_horizon = shift @arr // 0; - my $weather = shift @arr // 0; - - return "Argument Latitude is not a valid number" if !looks_like_number($latitude); - return "Argument Longitude is not a valid number" if !looks_like_number($longitude); - return "Argument Indoor_Horizon is not a valid number" if !looks_like_number($indoor_horizon); - - $latitude = List::Util::min( 90, List::Util::max( -90, $latitude)); - $longitude = List::Util::min( 180, List::Util::max(-180, $longitude)); - $indoor_horizon = List::Util::min( 20, List::Util::max( -6, $indoor_horizon)); - - #CommandAttr(undef, "global longitude $longitude") if !AttrVal('global','longitude',0) && $longitude != 8.686; - #CommandAttr(undef, "global latitude $latitude") if !AttrVal('global','latitude',0) && $latitude != 50.112; - #Log3 ($hash, 3, "[$hash->{NAME}] check longitude/latitude settings, there's a missmatch between Twilight and global settings. Times will be calculated using global settings!") if $latitude != AttrVal('global','latitude',50.112) || $longitude != AttrVal('global','longitude',8.686); - - $hash->{WEATHER_HORIZON} = 0; - $hash->{INDOOR_HORIZON} = $indoor_horizon; - $hash->{LATITUDE} = $latitude; - $hash->{LONGITUDE} = $longitude; - $hash->{WEATHER} = $weather; - #$hash->{VERSUCHE} = 0; - $hash->{DEFINE} = 1; - $hash->{CONDITION} = 50; - $hash->{SUNPOS_OFFSET} = 5*60; - - $attr{$name}{verbose} = 4 if ($name =~ /^tst.*$/ ); - - my $mHash = { HASH=>$hash }; - Twilight_sunpos($mHash); - Twilight_Midnight($mHash); - - delete $hash->{DEFINE}; - Log3 ($hash, 2, "[$hash->{NAME}] Note: Twilight formerly used weather info from yahoo, but source is offline."); - - return; -} ################################################################################ sub Twilight_Undef { - my $hash = shift; - my $arg = shift // return; - - for my $key (keys %{$hash->{TW}}) { - myRemoveInternalTimer($key, $hash); - } - myRemoveInternalTimer ("Midnight", $hash); - myRemoveInternalTimer ("weather", $hash); - myRemoveInternalTimer ("sunpos", $hash); + my $hash = shift; + my $arg = shift // return; - return; -} -################################################################################ -sub myInternalTimer { - my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_; + for my $key ( keys %{ $hash->{TW} } ) { + Twilight_RemoveInternalTimer( $key, $hash ); + } + Twilight_RemoveInternalTimer( "Midnight", $hash ); + Twilight_RemoveInternalTimer( "weather", $hash ); + Twilight_RemoveInternalTimer( "sunpos", $hash ); + notifyRegexpChanged( $hash, "" ); + delete $hash->{helper}{extWeather}{regexp}; + delete $hash->{helper}{extWeather}{dispatch}; + delete $hash->{helper}{extWeather}{Device}; + delete $hash->{helper}{extWeather}{Reading}; + delete $hash->{helper}{extWeather}; - my $timerName = "$hash->{NAME}_$modifier"; - my $mHash = { HASH=>$hash, NAME=>"$hash->{NAME}_$modifier", MODIFIER=>$modifier}; - if (defined($hash->{TIMER}{$timerName})) { - Log3 ($hash, 1, "[$hash->{NAME}] possible overwriting of timer $timerName - please delete first"); - stacktrace(); - } else { - $hash->{TIMER}{$timerName} = $mHash; - } - - Log3 ($hash, 5, "[$hash->{NAME}] setting Timer: $timerName " . FmtDateTime($tim)); - InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone); - return $mHash; -} -################################################################################ -sub myRemoveInternalTimer { - my $modifier = shift; - my $hash = shift // return; - - my $timerName = "$hash->{NAME}_$modifier"; - my $myHash = $hash->{TIMER}{$timerName}; - if (defined($myHash)) { - delete $hash->{TIMER}{$timerName}; - Log3 ($hash, 5, "[$hash->{NAME}] removing Timer: $timerName"); - RemoveInternalTimer($myHash); - } - return; -} - -################################################################################ -sub myGetHashIndirekt { - my $myHash = shift; - my $function = shift // return; - - if (!defined($myHash->{HASH})) { - #Log3 ($hash, 5, "[$function] myHash not valid"); return; - }; - return $myHash->{HASH}; +} + +################################################################################ +sub Twilight_Change_DEF { + my $hash = shift // return; + my $name = $hash->{NAME}; + my $newdef = ""; + my $weather = $hash->{DEFINE}; + $weather = "" if $weather eq "none"; + if (looks_like_number($weather)) { + my @wd = devspec2array("TYPE=Weather"); + my ($err, $wreading) = Twilight_disp_ExtWeather($hash, $wd[0]) if $wd[0]; + $weather = $err ? "" : $wd[0] ; + } + $newdef = "$hash->{helper}{'.LATITUDE'} $hash->{helper}{'.LONGITUDE'}" if $hash->{helper}{'.LATITUDE'} != AttrVal( 'global', 'latitude', 50.112 ) || $hash->{helper}{'.LONGITUDE'} != AttrVal( 'global', 'longitude', 8.686 ); + $newdef .= " $hash->{INDOOR_HORIZON} $weather"; + + return CommandModify(undef, "$name $newdef"); +} + +################################################################################ +sub Twilight_Notify { + my $hash = shift; + my $whash = shift // return; + + return if !exists $hash->{helper}{extWeather}; + + my $name = $hash->{NAME}; + return if(IsDisabled($name)); + + my $wname = $whash->{NAME}; + my $events = deviceEvents( $whash, 1 ); + + my $re = $hash->{helper}{extWeather}{regexp} // "unknown"; + + return if(!$events); # Some previous notify deleted the array. + my $max = int(@{$events}); + my $ret = ""; + for (my $i = 0; $i < $max; $i++) { + my $s = $events->[$i]; + $s = "" if(!defined($s)); + my $found = ($wname =~ m/^$re$/x || "$wname:$s" =~ m/^$re$/sx); + + if($found) { + my $extWeather = ReadingsNum($hash->{helper}{extWeather}{Device}, $hash->{helper}{extWeather}{Reading},-1); + my $last = ReadingsNum($name, "cloudCover", -1); + return if abs ($last - $extWeather) < 6; + my ($cond, $condText) = [-1,"not known"]; + my $dispatch = defined $hash->{helper}{extWeather} ? 1 : 0; + $cond = ReadingsNum($hash->{helper}{extWeather}{Device}, $hash->{helper}{extWeather}{dispatch}{cond_code},-2) if $dispatch; + $condText = ReadingsVal($hash->{helper}{extWeather}{Device}, $hash->{helper}{extWeather}{dispatch}{cond_text},"unknown") if $dispatch; + + readingsBeginUpdate( $hash ); + readingsBulkUpdate( $hash, "cloudCover", $extWeather ); + readingsBulkUpdate( $hash, "condition_code", $cond ) if $dispatch; + readingsBulkUpdate( $hash, "condition_txt", $condText ) if $dispatch; + readingsEndUpdate( $hash, defined( $hash->{LOCAL} ? 0 : 1 ) ); + + Twilight_getWeatherHorizon( $hash, $extWeather ); + + #my $horizon = $hash->{HORIZON}; + #my ( $name, $deg ) = split( ":", $horizon ); + + my ($sr, $ss) = Twilight_calc( $hash, $hash->{WEATHER_HORIZON}, "7" ); ##these are numbers + my $now = time(); + #$hash->{SR_TEST} = $sr; + #$hash->{SS_TEST} = $ss; + + #done for today? + return if $now > min($ss , $hash->{TW}{ss_weather}{TIME}); + + #set potential dates in the past to now + $sr = max( $sr, $now - 0.01 ); + $ss = max( $ss, $now - 0.01 ); + + #renew dates and timers?, fire events? + my $nextevent = ReadingsVal($name,"nextEvent","none"); + readingsBeginUpdate($hash); + my $nextEventTime = FmtTime( $sr ); + if ($now < $sr ) { + $hash->{TW}{sr_weather}{TIME} = $sr; + Twilight_RemoveInternalTimer( "sr_weather", $hash ); + Twilight_InternalTimer( "sr_weather", $sr, \&Twilight_fireEvent, $hash, 0 ); + readingsBulkUpdate( $hash, "sr_weather", $nextEventTime ); + readingsBulkUpdate( $hash, "nextEventTime", $nextEventTime ) if $nextevent eq "sr_weather"; + } + if ($now < $ss ) { + $nextEventTime = FmtTime( $ss ); + $hash->{TW}{ss_weather}{TIME} = $ss; + Twilight_RemoveInternalTimer( "ss_weather", $hash ); + Twilight_InternalTimer( "ss_weather", $ss, \&Twilight_fireEvent, $hash, 0 ); + readingsBulkUpdate( $hash, "ss_weather", $nextEventTime ); + readingsBulkUpdate( $hash, "nextEventTime", $nextEventTime ) if $nextevent eq "ss_weather"; + } + + readingsEndUpdate( $hash, defined( $hash->{LOCAL} ? 0 : 1 ) ); + #my $swip = $hash->{SWIP}; + #$hash->{SWIP} = 1 if !$swip; + #Twilight_TwilightTimes( $hash, "weather", $extWeather ); + #$hash->{SWIP} = 0 if !$swip; + Twilight_RemoveInternalTimer ("sunpos", $hash); + Twilight_InternalTimer ("sunpos", time()+1, \&Twilight_sunpos, $hash, 0); + } + } + return; +} + +sub Twilight_Firstrun { + my $hash = shift // return; + my $name = $hash->{NAME}; + $hash->{SWIP} = 0; + + my $attrVal = AttrVal( $name,'useExtWeather', $hash->{DEFINE}); + $attrVal = "$hash->{helper}{extWeather}{Device}:$hash->{helper}{extWeather}{Reading}" if !$attrVal && defined $hash->{helper} && defined $hash->{helper}{extWeather}{Device} && defined $hash->{helper}{extWeather}{Reading}; + + if ($attrVal) { + Twilight_init_ExtWeather_usage( $hash, $attrVal ); + my $extWeatherVal = ReadingsVal($hash->{helper}{extWeather}{Device}, $hash->{helper}{extWeather}{Reading},"-1"); + readingsSingleUpdate ($hash, "cloudCover", $extWeatherVal, 0); + Twilight_getWeatherHorizon( $hash, $extWeatherVal ); + Twilight_TwilightTimes( $hash, "weather", $extWeatherVal ); + } + + my $mHash = { HASH => $hash }; + Twilight_sunpos($mHash); + Twilight_Midnight($mHash); + delete $hash->{DEFINE}; + + return; +} + +################################################################################ +sub Twilight_Attr { + my ( $cmd, $name, $attrName, $attrVal ) = @_; + return if (!$init_done); + my $hash = $defs{$name}; + + if ( $attrName eq 'useExtWeather' ) { + if ($cmd eq "set") { + return "External weather device already in use, most likely assigned by define" if $hash->{helper}{extWeather}{regexp} =~ m,$attrVal,; + return Twilight_init_ExtWeather_usage($hash, $attrVal); + } elsif ($cmd eq "del") { + notifyRegexpChanged( $hash, "" ); + delete $hash->{helper}{extWeather}{regexp}; + delete $hash->{helper}{extWeather}{Device}; + delete $hash->{helper}{extWeather}{Reading}; + delete $hash->{helper}{extWeather}; + } + } + return; +} +sub Twilight_init_ExtWeather_usage { + my $hash = shift // return; + my $devreading = shift // 1000; + my $useTimer = shift // 0; + my ($extWeather, $extWReading, $err) ; + if (!looks_like_number($devreading)) { + ($extWeather, $extWReading) = split( ":", $devreading ); + return "External weather device seems not to exist" if (!defined $defs{$extWeather} && $init_done); + } else { + #conversion code, try to guess the ext. weather device to replace yahoo + my @devices=devspec2array("TYPE=Weather"); + return "No Weather-Type device found if !$devices[0]"; + $extWeather = $devices[0]; + $extWReading = "cloudCover"; + } + + ($err, $extWReading) = Twilight_disp_ExtWeather($hash, $extWeather) if !$extWReading; + return $err if $err; + my $extWregex = qq($extWeather:$extWReading:.*); + notifyRegexpChanged($hash, $extWregex); + $hash->{helper}{extWeather}{regexp} = $extWregex; + $hash->{helper}{extWeather}{Device} = $extWeather; + $hash->{helper}{extWeather}{Reading} = $extWReading; + return InternalTimer( time(), \&Twilight_Firstrun,$hash,0) if $init_done && $useTimer; + return; +} + +sub Twilight_disp_ExtWeather { + my $hash = shift; + my $extWeather = shift //return; + my ( $err, $extWReading ); + my $wtype = InternalVal( $extWeather, 'TYPE', undef ); + return ("No type info about extWeather available!", "none") if !$wtype; + + my $dispatch = { + "Weather" => {"cloudCover" => "cloudCover", "cond_code" => "code", "cond_text" => "condition"}, + }; + if (ref $dispatch->{$wtype} eq 'HASH') { + $extWReading = $dispatch->{$wtype}{cloudCover}; + $hash->{helper}{extWeather}{dispatch} = $dispatch->{$wtype}; + } else { + $extWReading = "none"; + } + $err = $extWReading eq "none" ? "No cloudCover reading assigned to $wtype, has to be implemented first; use dedicated form in define or attribute" : undef ; + return $err, $extWReading; +} + +################################################################################ +sub Twilight_Get { + my ( $hash, $aref, $href ) = @_; + return "argument is missing" if ( int(@$aref) != 2 ); + + my $reading = @$aref[1]; + my $value; + + if ( defined( $hash->{READINGS}{$reading} ) ) { + $value = $hash->{READINGS}{$reading}{VAL}; + } + else { + return "no such reading: $reading"; + } + return "@$aref[0] $reading => $value"; +} + +################################################################################ +sub Twilight_InternalTimer { + my ( $modifier, $tim, $callback, $hash, $waitIfInitNotDone ) = @_; + + my $timerName = "$hash->{NAME}_$modifier"; + my $mHash = { + HASH => $hash, + NAME => "$hash->{NAME}_$modifier", + MODIFIER => $modifier + }; + if ( defined( $hash->{TIMER}{$timerName} ) ) { + Log3( $hash, 1, "[$hash->{NAME}] possible overwriting of timer $timerName - please delete first" ); + stacktrace(); + } + else { + $hash->{TIMER}{$timerName} = $mHash; + } + + Log3( $hash, 5, "[$hash->{NAME}] setting Timer: $timerName " . FmtDateTime($tim) ); + addTimer($timerName,$tim, $callback, $mHash, $waitIfInitNotDone ); + #InternalTimer( $tim, $callback, $mHash, $waitIfInitNotDone ); + return $mHash; +} + +################################################################################ +sub Twilight_RemoveInternalTimer { + my $modifier = shift; + my $hash = shift // return; + + my $timerName = "$hash->{NAME}_$modifier"; + my $myHash = $hash->{TIMER}{$timerName}; + if ( defined($myHash) ) { + delete $hash->{TIMER}{$timerName}; + Log3( $hash, 5, "[$hash->{NAME}] removing Timer: $timerName" ); + removeTimer($timerName); + #RemoveInternalTimer($myHash); + } + return; +} + +################################################################################ +sub Twilight_GetHashIndirekt { + my $myHash = shift; + my $function = shift // return; + + if ( !defined( $myHash->{HASH} ) ) { + + #Log3 ($hash, 5, "[$function] myHash not valid"); + return; + } + return $myHash->{HASH}; } ################################################################################ sub Twilight_midnight_seconds { - my $now = shift // return; - my @time = localtime($now); - my $secs = ($time[2] * 3600) + ($time[1] * 60) + $time[0]; - return $secs; + my $now = shift // return; + my @time = localtime($now); + my $secs = ( $time[2] * 3600 ) + ( $time[1] * 60 ) + $time[0]; + return $secs; } ################################################################################ sub Twilight_calc { - my $hash = shift; - my $deg = shift; - my $idx = shift // return; + my $hash = shift; + my $deg = shift; + my $idx = shift // return; - my $midnight = time() - Twilight_midnight_seconds(time()); - my $lat = $hash->{LATITUDE}; - my $long = $hash->{LONGITUDE}; + my $now = time(); + my $midnight = $now - Twilight_midnight_seconds( $now ); + my $lat = $hash->{helper}{'.LATITUDE'}; + my $long = $hash->{helper}{'.LONGITUDE'}; - #my $sr = sunrise_abs("Horizon=$deg"); - #sub sunrise_abs(@) { return sr_alt(time(),1,0,0,0,shift,shift,shift,shift); } - my $sr = sr_alt(time(),1,0,0,0,"Horizon=$deg",undef,undef,undef,$lat,$long); - #my $ss = sunset_abs ("Horizon=$deg"); - #sub sunset_abs (@) { return sr_alt(time(),0,0,0,0,shift,shift,shift,shift); } - my $ss = sr_alt(time(),0,0,0,0,"Horizon=$deg",undef,undef,undef,$lat,$long); - - my ($srhour, $srmin, $srsec) = split(":",$sr); $srhour -= 24 if($srhour>=24); - my ($sshour, $ssmin, $sssec) = split(":",$ss); $sshour -= 24 if($sshour>=24); + #my $sr = sunrise_abs("Horizon=$deg"); + my $sr = + sr_alt( $now, 1, 0, 0, 0, "Horizon=$deg", undef, undef, undef, $lat, + $long ); - my $sr1 = $midnight + 3600*$srhour+60*$srmin+$srsec; - my $ss1 = $midnight + 3600*$sshour+60*$ssmin+$sssec; + my $ss = + sr_alt( $now, 0, 0, 0, 0, "Horizon=$deg", undef, undef, undef, $lat, + $long ); - return (0,0) if (abs ($sr1 - $ss1) < 30); - return ($sr1 + 0.01*$idx), ($ss1 - 0.01*$idx); + my ( $srhour, $srmin, $srsec ) = split( ":", $sr ); + $srhour -= 24 if ( $srhour >= 24 ); + my ( $sshour, $ssmin, $sssec ) = split( ":", $ss ); + $sshour -= 24 if ( $sshour >= 24 ); + + my $sr1 = $midnight + 3600 * $srhour + 60 * $srmin + $srsec; + my $ss1 = $midnight + 3600 * $sshour + 60 * $ssmin + $sssec; + + return ( 0, 0 ) if ( abs( $sr1 - $ss1 ) < 30 ); + return ( $sr1 + 0.01 * $idx ), ( $ss1 - 0.01 * $idx ); } + ################################################################################ sub Twilight_TwilightTimes { - my ($hash, $whitchTimes, $xml) = @_; + my ( $hash, $whitchTimes, $xml ) = @_; - my $Name = $hash->{NAME}; + my $name = $hash->{NAME}; - my $horizon = $hash->{HORIZON}; - my $swip = $hash->{SWIP} ; + #my $horizon = $hash->{HORIZON}; + #my $horizon = ReadingsNum($name,"horizon",0); + my $swip = $hash->{SWIP}; + + my $lat = $hash->{helper}{'.LATITUDE'}; + my $long = $hash->{helper}{'.LONGITUDE'}; - my $lat = $hash->{LATITUDE}; - my $long = $hash->{LONGITUDE}; # ------------------------------------------------------------------------------ - my $idx = -1; - my @horizons = ("_astro:-18", "_naut:-12", "_civil:-6",":0", "_indoor:$hash->{INDOOR_HORIZON}", "_weather:$hash->{WEATHER_HORIZON}"); - for my $horizon (@horizons) { - $idx++; next if ($whitchTimes eq "weather" && !($horizon =~ m/weather/) ); + my $idx = -1; - my ($name, $deg) = split(":", $horizon); - my $sr = "sr$name"; my $ss = "ss$name"; - $hash->{TW}{$sr}{NAME} = $sr; $hash->{TW}{$ss}{NAME} = $ss; - $hash->{TW}{$sr}{DEG} = $deg; $hash->{TW}{$ss}{DEG} = $deg; - $hash->{TW}{$sr}{LIGHT} = $idx+1;$hash->{TW}{$ss}{LIGHT} = $idx; - $hash->{TW}{$sr}{STATE} = $idx+1;$hash->{TW}{$ss}{STATE} = 12 - $idx; - $hash->{TW}{$sr}{SWIP} = $swip; $hash->{TW}{$ss}{SWIP} = $swip; + my @horizons = ( + "_astro:-18", "_naut:-12", "_civil:-6", ":0", + "_indoor:$hash->{INDOOR_HORIZON}", + "_weather:$hash->{WEATHER_HORIZON}" + ); + for my $horizon (@horizons) { + $idx++; + next if ( $whitchTimes eq "weather" && !( $horizon =~ m/weather/ ) ); - ($hash->{TW}{$sr}{TIME}, $hash->{TW}{$ss}{TIME}) = Twilight_calc ($hash, $deg, $idx); + my ( $name, $deg ) = split( ":", $horizon ); + my $sr = "sr$name"; + my $ss = "ss$name"; + $hash->{TW}{$sr}{NAME} = $sr; + $hash->{TW}{$ss}{NAME} = $ss; + $hash->{TW}{$sr}{DEG} = $deg; + $hash->{TW}{$ss}{DEG} = $deg; + $hash->{TW}{$sr}{LIGHT} = $idx + 1; + $hash->{TW}{$ss}{LIGHT} = $idx; + $hash->{TW}{$sr}{STATE} = $idx + 1; + $hash->{TW}{$ss}{STATE} = 12 - $idx; + $hash->{TW}{$sr}{SWIP} = $swip; + $hash->{TW}{$ss}{SWIP} = $swip; - if ($hash->{TW}{$sr}{TIME} == 0) { - Log3 ($hash, 4, "[$Name] hint: $hash->{TW}{$sr}{NAME}, $hash->{TW}{$ss}{NAME} are not defined(HORIZON=$deg)"); + ( $hash->{TW}{$sr}{TIME}, $hash->{TW}{$ss}{TIME} ) = + Twilight_calc( $hash, $deg, $idx ); + + if ( $hash->{TW}{$sr}{TIME} == 0 ) { + Log3( $hash, 4, "[$name] hint: $hash->{TW}{$sr}{NAME}, $hash->{TW}{$ss}{NAME} are not defined(HORIZON=$deg)" ); + } } - } - #$attr{global}{latitude} = $lat; - #$attr{global}{longitude} = $long; -# ------------------------------------------------------------------------------ - readingsBeginUpdate ($hash); - for my $ereignis (keys %{$hash->{TW}}) { - next if ($whitchTimes eq "weather" && !($ereignis =~ m/weather/) ); - readingsBulkUpdate($hash, $ereignis, $hash->{TW}{$ereignis}{TIME} == 0 ? "undefined" : FmtTime($hash->{TW}{$ereignis}{TIME})); - } - if ($hash->{CONDITION} != 50 ) { - readingsBulkUpdate ($hash,"condition", $hash->{CONDITION}); - readingsBulkUpdate ($hash,"condition_txt",$hash->{CONDITION_TXT}); - } - readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1)); -# ------------------------------------------------------------------------------ - my @horizonsOhneDeg = map {my($e, $deg)=split(":",$_); "$e"} @horizons; - my @ereignisse = ((map {"sr$_"}@horizonsOhneDeg),(map {"ss$_"} reverse @horizonsOhneDeg),"sr$horizonsOhneDeg[0]"); - map { $hash->{TW}{$ereignisse[$_]}{NAMENEXT} = $ereignisse[$_+1] } 0..$#ereignisse-1; -# ------------------------------------------------------------------------------ - my $myHash; - my $now = time(); - my $secSinceMidnight = Twilight_midnight_seconds($now); - my $lastMitternacht = $now-$secSinceMidnight; - my $nextMitternacht = ($secSinceMidnight > 12*3600) ? $lastMitternacht+24*3600 : $lastMitternacht; - my $jetztIstMitternacht = abs($now+5-$nextMitternacht)<=10; - my @keyListe = qw "DEG LIGHT STATE SWIP TIME NAMENEXT"; - for my $ereignis (sort keys %{$hash->{TW}}) { - next if ($whitchTimes eq "weather" && !($ereignis =~ m/weather/) ); - - myRemoveInternalTimer($ereignis, $hash); # if(!$jetztIstMitternacht); - if($hash->{TW}{$ereignis}{TIME} > 0) { - $myHash = myInternalTimer($ereignis, $hash->{TW}{$ereignis}{TIME}, "Twilight_fireEvent", $hash, 0); - map {$myHash->{$_} = $hash->{TW}{$ereignis}{$_} } @keyListe; +# ------------------------------------------------------------------------------ + readingsBeginUpdate($hash); + for my $ereignis ( keys %{ $hash->{TW} } ) { + next if ( $whitchTimes eq "weather" && !( $ereignis =~ m/weather/ ) ); + readingsBulkUpdate( $hash, $ereignis, + $hash->{TW}{$ereignis}{TIME} == 0 + ? "undefined" + : FmtTime( $hash->{TW}{$ereignis}{TIME} ) ); } - } + + readingsEndUpdate( $hash, defined( $hash->{LOCAL} ? 0 : 1 ) ); + # ------------------------------------------------------------------------------ - return 1; + my @horizonsOhneDeg = + map { my ( $e, $deg ) = split( ":", $_ ); "$e" } @horizons; + my @ereignisse = ( + ( map { "sr$_" } @horizonsOhneDeg ), + ( map { "ss$_" } reverse @horizonsOhneDeg ), + "sr$horizonsOhneDeg[0]" + ); + map { $hash->{TW}{ $ereignisse[$_] }{NAMENEXT} = $ereignisse[ $_ + 1 ] } + 0 .. $#ereignisse - 1; + +# ------------------------------------------------------------------------------ + my $myHash; + my $now = time(); + my $secSinceMidnight = Twilight_midnight_seconds($now); + my $lastMitternacht = $now - $secSinceMidnight; + my $nextMitternacht = + ( $secSinceMidnight > 12 * 3600 ) + ? $lastMitternacht + 24 * 3600 + : $lastMitternacht; + my $jetztIstMitternacht = abs( $now + 5 - $nextMitternacht ) <= 10; + + my @keyListe = qw "DEG LIGHT STATE SWIP TIME NAMENEXT"; + for my $ereignis ( sort keys %{ $hash->{TW} } ) { + next if ( $whitchTimes eq "weather" && !( $ereignis =~ m/weather/ ) ); + + Twilight_RemoveInternalTimer( $ereignis, $hash ); # if(!$jetztIstMitternacht); + if ( $hash->{TW}{$ereignis}{TIME} > 0 ) { + $myHash = Twilight_InternalTimer( $ereignis, $hash->{TW}{$ereignis}{TIME}, + \&Twilight_fireEvent, $hash, 0 ); + map { $myHash->{$_} = $hash->{TW}{$ereignis}{$_} } @keyListe; + } + } + +# ------------------------------------------------------------------------------ + return 1; } ################################################################################ sub Twilight_fireEvent { - my $myHash = shift // return; + my $myHash = shift // return; - my $hash = myGetHashIndirekt($myHash, (caller(0))[3]); - return if (!defined($hash)); + my $hash = Twilight_GetHashIndirekt( $myHash, ( caller(0) )[3] ); + return if ( !defined($hash) ); - my $name = $hash->{NAME}; + my $name = $hash->{NAME}; - my $event = $myHash->{MODIFIER}; - my $deg = $myHash->{DEG}; - my $light = $myHash->{LIGHT}; - my $state = $myHash->{STATE}; - my $swip = $myHash->{SWIP}; + my $event = $myHash->{MODIFIER}; + my $deg = $myHash->{DEG}; + my $light = $myHash->{LIGHT}; + my $state = $myHash->{STATE}; + my $swip = $myHash->{SWIP}; - my $eventTime = $myHash->{TIME}; - my $nextEvent = $myHash->{NAMENEXT}; + my $eventTime = $myHash->{TIME}; + my $nextEvent = $myHash->{NAMENEXT}; - my $delta = int($eventTime - time()); - my $oldState = ReadingsVal($name,"state","0"); + my $delta = int( $eventTime - time() ); + my $oldState = ReadingsVal( $name, "state", "0" ); - my $nextEventTime = ($hash->{TW}{$nextEvent}{TIME} > 0) ? FmtTime($hash->{TW}{$nextEvent}{TIME}) : "undefined"; + my $nextEventTime = + ( $hash->{TW}{$nextEvent}{TIME} > 0 ) + ? FmtTime( $hash->{TW}{$nextEvent}{TIME} ) + : "undefined"; - my $doTrigger = !(defined($hash->{LOCAL})) && ( abs($delta)<6 || $swip && $state gt $oldState); - #Log3 $hash, 3, "[$hash->{NAME}] swip-delta-oldState-doTrigger===>$swip/$delta/$oldState/$doTrigger"; + my $doTrigger = !( defined( $hash->{LOCAL} ) ) + && ( abs($delta) < 6 || $swip && $state gt $oldState ); - Log3 ($hash, 4, - sprintf ("[$hash->{NAME}] %-10s %-19s ", $event, FmtDateTime($eventTime)). - sprintf ("(%2d/$light/%+5.1f°/$doTrigger) ", $state, $deg). - sprintf ("===> %-10s %-19s ", $nextEvent, $nextEventTime)); + Log3( + $hash, 4, + sprintf( "[$hash->{NAME}] %-10s %-19s ", + $event, FmtDateTime($eventTime) ) + . sprintf( "(%2d/$light/%+5.1f°/$doTrigger) ", $state, $deg ) + . sprintf( "===> %-10s %-19s ", $nextEvent, $nextEventTime ) + ); + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "state", $state ); + readingsBulkUpdate( $hash, "light", $light ); + readingsBulkUpdate( $hash, "horizon", $deg ); + readingsBulkUpdate( $hash, "aktEvent", $event ); + readingsBulkUpdate( $hash, "nextEvent", $nextEvent ); + readingsBulkUpdate( $hash, "nextEventTime", $nextEventTime ); - readingsBeginUpdate($hash); - readingsBulkUpdate ($hash, "state", $state); - readingsBulkUpdate ($hash, "light", $light); - readingsBulkUpdate ($hash, "horizon", $deg); - readingsBulkUpdate ($hash, "aktEvent", $event); - readingsBulkUpdate ($hash, "nextEvent", $nextEvent); - readingsBulkUpdate ($hash, "nextEventTime", $nextEventTime); - - return readingsEndUpdate ($hash, $doTrigger); + return readingsEndUpdate( $hash, $doTrigger ); } + ################################################################################ sub Twilight_Midnight { - my $myHash = shift // return; - my $hash = myGetHashIndirekt($myHash, (caller(0))[3]); - return if (!defined($hash)); + my $myHash = shift // return; + my $firstrun = shift // 0; + + my $hash = Twilight_GetHashIndirekt( $myHash, ( caller(0) )[3] ); + return if ( !defined($hash) ); - $hash->{SWIP} = 0; - my $param = Twilight_CreateHttpParameterAndGetData($myHash, "Mid"); - return; + return Twilight_HandleWeatherData( $hash, "Mid", $firstrun ); } + ################################################################################ -# {Twilight_WeatherTimerUpdate( {HASH=$defs{"Twilight"}} ) } sub Twilight_WeatherTimerUpdate { - my $myHash = shift // return; - my $hash = myGetHashIndirekt($myHash, (caller(0))[3]); - return if (!defined($hash)); + my $myHash = shift // return; + my $swip = shift // 1; + + my $hash = Twilight_GetHashIndirekt( $myHash, ( caller(0) )[3] ); + return if ( !defined($hash) ); - $hash->{SWIP} = 1; - my $param = Twilight_CreateHttpParameterAndGetData($myHash, "weather"); - return; + return Twilight_HandleWeatherData( $hash, "weather", $swip ); } + ################################################################################ -sub Twilight_CreateHttpParameterAndGetData { - my $myHash = shift; - my $mode = shift // return; - my $hash = myGetHashIndirekt($myHash, (caller(0))[3]); - return if (!defined($hash)); - my $location = $hash->{WEATHER}; - my $verbose = AttrVal($hash->{NAME}, "verbose", 3 ); +sub Twilight_HandleWeatherData { + my $hash = shift; + my $mode = shift; + my $swip = shift // return; + my $cloudCover = shift; - my $URL = "http://query.yahooapis.com/v1/public/yql?q=select%%20*%%20from%%20weather.forecast%%20where%%20woeid=%s%%20and%%20u=%%27c%%27&format=%s&env=store%%3A%%2F%%2Fdatatables.org%%2Falltableswithkeys"; - my $url = sprintf($URL, $location, "json"); - Log3 ($hash, 4, "[$hash->{NAME}] url=$url"); + $hash->{SWIP} = $swip; - my $param = { - url => $url, - timeout => defined($hash->{DEFINE}) ? 10 :10, - hash => $hash, - method => "GET", - loglevel => 4-($verbose-3), - header => "User-Agent: Mozilla/5.0\r\nAccept: application/xml", - callback => \&Twilight_WeatherCallback, - mode => $mode }; - - if (defined($hash->{DEFINE})) { - delete $param->{callback}; - my ($err, $result) = HttpUtils_BlockingGet($param); - Twilight_WeatherCallback($param, $err, $result); - } else { - HttpUtils_NonblockingGet($param); - } - return; + Twilight_getWeatherHorizon( $hash, $cloudCover ); + Twilight_TwilightTimes( $hash, $mode, $cloudCover ); + return Twilight_StandardTimerSet( $hash ); } -################################################################################ -sub Twilight_WeatherCallback { - my ($param, $err, $result) = @_; - my $hash = $param->{hash}; - return if (!defined($hash)); - - if ($err) { - #Log3 $hash, 3, "[$hash->{NAME}] got no weather info from yahoo. Error code: $err"; - #disabled due to yahoo - $result = undef; - } else { - Log3 ($hash, 4, "[$hash->{NAME}] got weather info from yahoo for $hash->{WEATHER}"); - Log3 ($hash, 5, "[$hash->{NAME}] answer=$result") if defined $result; - } - - Twilight_getWeatherHorizon($hash, $result); - #$hash->{CONDITION} = 50; - - ##repetition makes no longer sense...! - #if ($hash->{CONDITION} == 50 && $hash->{VERSUCHE} <= 10) { - # $hash->{VERSUCHE} += 1; - # Twilight_RepeatTimerSet($hash, $param->{mode}); - # return; - #} - - Twilight_TwilightTimes($hash, $param->{mode}, $result); - - #Log3 $hash, 3, "[$hash->{NAME}] " . ($hash->{VERSUCHE}+1) . " attempt(s) needed to get valid weather data from yahoo" if ($hash->{CONDITION} != 50 && $hash->{VERSUCHE} > 0); - #Log3 $hash, 3, "[$hash->{NAME}] " . ($hash->{VERSUCHE}+1) . " attempt(s) needed got NO valid weather data from yahoo" if ($hash->{CONDITION} == 50 && $hash->{VERSUCHE} > 0); - #$hash->{VERSUCHE} = 0; - - return Twilight_StandardTimerSet($hash); - -} ################################################################################ sub Twilight_RepeatTimerSet { - my $hash = shift; - my $mode = shift // return; - - my $midnight = time() + 60; + my $hash = shift; + my $mode = shift // return; - myRemoveInternalTimer("Midnight", $hash); - return myInternalTimer ("Midnight", $midnight, "Twilight_Midnight", $hash, 0) if $mode eq "Mid"; - - return myInternalTimer ("Midnight", $midnight, "Twilight_WeatherTimerUpdate", $hash, 0); + my $midnight = time() + 60; + + Twilight_RemoveInternalTimer( "Midnight", $hash ); + return Twilight_InternalTimer( "Midnight", $midnight, \&Twilight_Midnight, $hash, + 0 ) + if $mode eq "Mid"; + + return Twilight_InternalTimer( "Midnight", $midnight, + \&Twilight_WeatherTimerUpdate, $hash, 0 ); } ################################################################################ sub Twilight_StandardTimerSet { - my $hash = shift // return; - my $midnight = time() - Twilight_midnight_seconds(time()) + 24*3600 + 1; + my $hash = shift // return; + my $midnight = time() - Twilight_midnight_seconds( time() ) + 24 * 3600 + 1; - myRemoveInternalTimer ("Midnight", $hash); - myInternalTimer ("Midnight", $midnight, "Twilight_Midnight", $hash, 0); - return Twilight_WeatherTimerSet($hash); + Twilight_RemoveInternalTimer( "Midnight", $hash ); + Twilight_InternalTimer( "Midnight", $midnight, \&Twilight_Midnight, $hash, 0 ); + return Twilight_WeatherTimerSet($hash); } ################################################################################ sub Twilight_WeatherTimerSet { - my $hash = shift // return; - my $now = time(); + my $hash = shift // return; + my $now = time(); - myRemoveInternalTimer ("weather", $hash); - for my $key ("sr_weather", "ss_weather") { - my $tim = $hash->{TW}{$key}{TIME}; - if ($tim-60*60>$now+60) { - myInternalTimer ("weather", $tim-60*60, "Twilight_WeatherTimerUpdate", $hash, 0); - last; - } - } + Twilight_RemoveInternalTimer( "weather", $hash ); + for my $key ( "sr_weather", "ss_weather" ) { + my $tim = $hash->{TW}{$key}{TIME}; + if ( $tim - 60 * 60 > $now + 60 ) { + Twilight_InternalTimer( "weather", $tim - 60 * 60, + \&Twilight_WeatherTimerUpdate, $hash, 0 ); + last; + } + } + + return; } + ################################################################################ sub Twilight_sunposTimerSet { - my $hash = shift // return; - - myRemoveInternalTimer("sunpos", $hash); - return myInternalTimer("sunpos", time()+$hash->{SUNPOS_OFFSET}, "Twilight_sunpos", $hash, 0); + my $hash = shift // return; + + Twilight_RemoveInternalTimer( "sunpos", $hash ); + return Twilight_InternalTimer( "sunpos", time() + $hash->{SUNPOS_OFFSET}, + \&Twilight_sunpos, $hash, 0 ); } ################################################################################ sub Twilight_getWeatherHorizon { - my $hash = shift; - my $result = shift // return; - - my $location=$hash->{WEATHER}; - if ($location == 0) { - $hash->{WEATHER_HORIZON}="0"; - $hash->{CONDITION}="0"; - return 1; - } + my $hash = shift; + my $result = shift // return; + + return if !looks_like_number($result) || $result < 0 || $result > 100; + $hash->{WEATHER_CORRECTION} = $result / 12.5; + $hash->{WEATHER_HORIZON} = $hash->{WEATHER_CORRECTION} + $hash->{INDOOR_HORIZON}; + my $doy = strftime("%j",localtime); + my $declination = 0.4095*sin(0.016906*($doy-80.086)); + if($hash->{WEATHER_HORIZON} > (89-$hash->{helper}{'.LATITUDE'}+$declination) ){ + $hash->{WEATHER_HORIZON} = 89-$hash->{helper}{'.LATITUDE'}+$declination; + }; - my $mod = "[".$hash->{NAME} ."] "; - my @faktor_cond_code = (10,10,10,10, 9, 7, 7, 7, 7, 7, - 7, 5, 5, 5, 5, 7, 7, 5, 5, 5, - 7, 5, 5, 5, 5, 5, 2, 4, 4, 2, - 2, 0, 0, 0, 0, 5, 0, 8, 8, 8, - 6, 8, 6, 5, 2, 5, 6, 6, 0, 0, - 0); - - # condition codes are described in FHEM wiki and in the documentation of the yahoo weather API - - my ($cond_code, $cond_txt, $temperatur, $aktTemp); - if (defined($result)) { - - # ersetze in result(json) ": durch "=> - # dadurch entsteht ein Perlausdruck, der direkt geparst werden kann - - my $perlAusdruck = $result; - #$perlAusdruck = "

could"; - $perlAusdruck =~ s/("[\w ]+")(\s*)(:)/$1=>/g; - $perlAusdruck =~ s/null/undef/g; - $perlAusdruck =~ s/true/1/g; - $perlAusdruck =~ s/false/0/g; - $perlAusdruck = 'return ' .$perlAusdruck; - - my $anonymSub = eval "sub {$perlAusdruck}"; - Log3 ($hash, 3, "[$hash->{NAME}] error $@ parsing $result") if $@; - if (!$@) { - my $resHash = $anonymSub->() if ($anonymSub gt ""); - Log3 ($hash, 3, "[$hash->{NAME}] error $@ parsing $result") if($@); - #Log3 $hash, 3, "jsonAsPerl". Dumper $resHash->{query}{results}{channel}{item}{condition}; - if (!$@) { - - $cond_code = $resHash->{query}{results}{channel}{item}{condition}{code}; - $cond_txt = $resHash->{query}{results}{channel}{item}{condition}{text}; - $temperatur = $resHash->{query}{results}{channel}{item}{condition}{temp}; - } - } - } - - # wenn kein Code ermittelt werden kann, wird ein Pseudocode gesetzt - if (!defined($cond_code) ) { - $cond_code = "50"; # eigener neutraler Code - $cond_txt = "undefined"; - $temperatur = "undefined"; - } else { - $hash->{WEATHER_CORRECTION} = $faktor_cond_code[$cond_code] / 25 * 20; - $hash->{WEATHER_HORIZON} = $hash->{WEATHER_CORRECTION} + $hash->{INDOOR_HORIZON}; - $hash->{CONDITION} = $cond_code; - $hash->{CONDITION_TXT} = $cond_txt; - $hash->{TEMPERATUR} = $temperatur; - Log3 ($hash, 4, "[$hash->{NAME}] $cond_code=$cond_txt $temperatur, correction: $hash->{WEATHER_CORRECTION}°"); - } - - my $doy = strftime("%j",localtime); - my $declination = 0.4095*sin(0.016906*($doy-80.086)); - if($hash->{WEATHER_HORIZON} > (89-$hash->{LATITUDE}+$declination) ){ - $hash->{WEATHER_HORIZON} = 89-$hash->{LATITUDE}+$declination; - } - - return 1; + return; } ################################################################################ sub Twilight_sunpos { - my $myHash = shift // return; - - my $hash = myGetHashIndirekt($myHash, (caller(0))[3]); - return if (!defined($hash)); + my $myHash = shift // return; + + my $hash = Twilight_GetHashIndirekt( $myHash, ( caller(0) )[3] ); + return if ( !defined($hash) ); - my $hashName = $hash->{NAME}; + my $hashName = $hash->{NAME}; + return if(IsDisabled($hashName)); - return "" if(AttrVal($hashName, "disable", undef)); + my ( + $dSeconds, $dMinutes, $dHours, $iDay, $iMonth, + $iYear, $wday, $yday, $isdst + ) = gmtime(time); + $iMonth++; + $iYear += 100; - my $tn = TimeNow(); - my ($dSeconds,$dMinutes,$dHours,$iDay,$iMonth,$iYear,$wday,$yday,$isdst) = gmtime(time); - $iMonth++; - $iYear += 100; + my $dLongitude = $hash->{helper}{'.LONGITUDE'}; + my $dLatitude = $hash->{helper}{'.LATITUDE'}; + Log3( $hash, 5, + "Compute sunpos for latitude $dLatitude , longitude $dLongitude" ) + if $dHours == 0 && $dMinutes <= 6; - my $dLongitude = $hash->{LONGITUDE}; - my $dLatitude = $hash->{LATITUDE}; - Log3 ($hash, 5, "Compute sunpos for latitude $dLatitude , longitude $dLongitude") if $dHours == 0 && $dMinutes <= 6; + my $pi = 3.14159265358979323846; + my $twopi = ( 2 * $pi ); + my $rad = ( $pi / 180 ); + my $dEarthMeanRadius = 6371.01; # In km + my $dAstronomicalUnit = 149597890; # In km - my $pi=3.14159265358979323846; - my $twopi=(2*$pi); - my $rad=($pi/180); - my $dEarthMeanRadius=6371.01; # In km - my $dAstronomicalUnit=149597890; # In km + # Calculate difference in days between the current Julian Day + # and JD 2451545.0, which is noon 1 January 2000 Universal Time - # Calculate difference in days between the current Julian Day - # and JD 2451545.0, which is noon 1 January 2000 Universal Time + # Calculate time of the day in UT decimal hours + my $dDecimalHours = $dHours + $dMinutes / 60.0 + $dSeconds / 3600.0; - # Calculate time of the day in UT decimal hours - my $dDecimalHours=$dHours + $dMinutes/60.0 + $dSeconds/3600.0; + # Calculate current Julian Day + my $iYfrom2000 = $iYear; #expects now as YY ; + my $iA = ( 14 - ($iMonth) ) / 12; + my $iM = ($iMonth) + 12 * $iA - 3; + my $liAux3 = ( 153 * $iM + 2 ) / 5; + my $liAux4 = 365 * ( $iYfrom2000 - $iA ); + my $liAux5 = ( $iYfrom2000 - $iA ) / 4; + my $dElapsedJulianDays = + ( $iDay + $liAux3 + $liAux4 + $liAux5 + 59 ) + -0.5 + + $dDecimalHours / 24.0; - # Calculate current Julian Day - my $iYfrom2000=$iYear;#expects now as YY ; - my $iA=(14 - ($iMonth)) / 12; - my $iM=($iMonth) + 12 * $iA -3; - my $liAux3=(153 * $iM + 2)/5; - my $liAux4=365 * ($iYfrom2000 - $iA); - my $liAux5=( $iYfrom2000 - $iA)/4; - my $dElapsedJulianDays=($iDay + $liAux3 + $liAux4 + $liAux5 + 59)+ -0.5 + $dDecimalHours/24.0; + # Calculate ecliptic coordinates (ecliptic longitude and obliquity of the + # ecliptic in radians but without limiting the angle to be less than 2*Pi + # (i.e., the result may be greater than 2*Pi) - # Calculate ecliptic coordinates (ecliptic longitude and obliquity of the - # ecliptic in radians but without limiting the angle to be less than 2*Pi - # (i.e., the result may be greater than 2*Pi) + my $dOmega = 2.1429 - 0.0010394594 * $dElapsedJulianDays; + my $dMeanLongitude = + 4.8950630 + 0.017202791698 * $dElapsedJulianDays; # Radians + my $dMeanAnomaly = 6.2400600 + 0.0172019699 * $dElapsedJulianDays; + my $dEclipticLongitude = + $dMeanLongitude + + 0.03341607 * sin($dMeanAnomaly) + + 0.00034894 * sin( 2 * $dMeanAnomaly ) - 0.0001134 - + 0.0000203 * sin($dOmega); + my $dEclipticObliquity = + 0.4090928 - 6.2140e-9 * $dElapsedJulianDays + 0.0000396 * cos($dOmega); - my $dOmega = 2.1429 - 0.0010394594 * $dElapsedJulianDays; - my $dMeanLongitude = 4.8950630 + 0.017202791698 * $dElapsedJulianDays; # Radians - my $dMeanAnomaly = 6.2400600 + 0.0172019699 * $dElapsedJulianDays; - my $dEclipticLongitude = $dMeanLongitude + 0.03341607 * sin( $dMeanAnomaly ) + 0.00034894 * sin( 2 * $dMeanAnomaly ) -0.0001134 -0.0000203 * sin($dOmega); - my $dEclipticObliquity = 0.4090928 - 6.2140e-9 * $dElapsedJulianDays +0.0000396 * cos($dOmega); + # Calculate celestial coordinates ( right ascension and declination ) in radians + # but without limiting the angle to be less than 2*Pi (i.e., the result may be + # greater than 2*Pi) - # Calculate celestial coordinates ( right ascension and declination ) in radians - # but without limiting the angle to be less than 2*Pi (i.e., the result may be - # greater than 2*Pi) + my $dSin_EclipticLongitude = sin($dEclipticLongitude); + my $dY1 = cos($dEclipticObliquity) * $dSin_EclipticLongitude; + my $dX1 = cos($dEclipticLongitude); + my $dRightAscension = atan2( $dY1, $dX1 ); + if ( $dRightAscension < 0.0 ) { + $dRightAscension = $dRightAscension + $twopi; + } + my $dDeclination = + asin( sin($dEclipticObliquity) * $dSin_EclipticLongitude ); - my $dSin_EclipticLongitude=sin( $dEclipticLongitude ); - my $dY1=cos( $dEclipticObliquity ) * $dSin_EclipticLongitude; - my $dX1=cos( $dEclipticLongitude ); - my $dRightAscension=atan2( $dY1,$dX1 ); - if ( $dRightAscension < 0.0 ) { $dRightAscension=$dRightAscension + $twopi }; - my $dDeclination=asin( sin( $dEclipticObliquity )* $dSin_EclipticLongitude ); + # Calculate local coordinates ( azimuth and zenith angle ) in degrees + my $dGreenwichMeanSiderealTime = + 6.6974243242 + 0.0657098283 * $dElapsedJulianDays + $dDecimalHours; - # Calculate local coordinates ( azimuth and zenith angle ) in degrees - my $dGreenwichMeanSiderealTime=6.6974243242 + 0.0657098283 * $dElapsedJulianDays + $dDecimalHours; + my $dLocalMeanSiderealTime = + ( $dGreenwichMeanSiderealTime * 15 + $dLongitude ) * $rad; + my $dHourAngle = $dLocalMeanSiderealTime - $dRightAscension; + my $dLatitudeInRadians = $dLatitude * $rad; + my $dCos_Latitude = cos($dLatitudeInRadians); + my $dSin_Latitude = sin($dLatitudeInRadians); + my $dCos_HourAngle = cos($dHourAngle); + my $dZenithAngle = ( + acos( + $dCos_Latitude * $dCos_HourAngle * cos($dDeclination) + + sin($dDeclination) * $dSin_Latitude + ) + ); + my $dY = -sin($dHourAngle); + my $dX = + tan($dDeclination) * $dCos_Latitude - $dSin_Latitude * $dCos_HourAngle; + my $dAzimuth = atan2( $dY, $dX ); + if ( $dAzimuth < 0.0 ) { $dAzimuth = $dAzimuth + $twopi } + $dAzimuth = $dAzimuth / $rad; - my $dLocalMeanSiderealTime=($dGreenwichMeanSiderealTime*15 + $dLongitude)* $rad; - my $dHourAngle=$dLocalMeanSiderealTime - $dRightAscension; - my $dLatitudeInRadians=$dLatitude * $rad; - my $dCos_Latitude=cos( $dLatitudeInRadians ); - my $dSin_Latitude=sin( $dLatitudeInRadians ); - my $dCos_HourAngle=cos( $dHourAngle ); - my $dZenithAngle=(acos( $dCos_Latitude * $dCos_HourAngle * cos($dDeclination) + sin( $dDeclination )* $dSin_Latitude)); - my $dY=-sin( $dHourAngle ); - my $dX=tan( $dDeclination )* $dCos_Latitude - $dSin_Latitude * $dCos_HourAngle; - my $dAzimuth=atan2( $dY, $dX ); - if ( $dAzimuth < 0.0 ) {$dAzimuth=$dAzimuth + $twopi}; - $dAzimuth=$dAzimuth / $rad; + # Parallax Correction + my $dParallax = + ( $dEarthMeanRadius / $dAstronomicalUnit ) * sin($dZenithAngle); + $dZenithAngle = ( $dZenithAngle + $dParallax ) / $rad; + my $dElevation = 90 - $dZenithAngle; - # Parallax Correction - my $dParallax=($dEarthMeanRadius / $dAstronomicalUnit) * sin( $dZenithAngle); - $dZenithAngle=($dZenithAngle + $dParallax) / $rad; - my $dElevation=90 - $dZenithAngle; + my $twilight = int( ( $dElevation + 12.0 ) / 18.0 * 1000 ) / 10; + $twilight = 100 if ( $twilight > 100 ); + $twilight = 0 if ( $twilight < 0 ); - my $twilight = int(($dElevation+12.0)/18.0 * 1000)/10; - $twilight = 100 if ($twilight>100); - $twilight = 0 if ($twilight< 0); + my $twilight_weather; - my $twilight_weather ; + if (!defined $hash->{helper}{extWeather}{Device}) { + $twilight_weather = + int( ( $dElevation - $hash->{WEATHER_HORIZON} + 12.0 ) / 18.0 * 1000 ) + / 10; + Log3( $hash, 5, "[$hash->{NAME}] Original weather readings" ); + } else { + my $extDev = $hash->{helper}{extWeather}{Device}; + my $extReading = $hash->{helper}{extWeather}{Reading}; + #my ( $extDev, $extReading ) = split( ":", $ExtWeather ); + my $extWeatherHorizont = ReadingsVal($extDev ,$extReading , -1 ); + if ( $extWeatherHorizont >= 0 ) { + $extWeatherHorizont = min (100, $extWeatherHorizont); + Log3( $hash, 5, + "[$hash->{NAME}] " + . "New weather readings from: " + . $extDev . ":" + . $extReading . ":" + . $extWeatherHorizont ); + $twilight_weather = $twilight - + int( 0.007 * ( $extWeatherHorizont**2 ) ) + ; ## SCM: 100% clouds => 30% light (rough estimation) + } + else { + $twilight_weather = + int( ( $dElevation - $hash->{WEATHER_HORIZON} + 12.0 ) / 18.0 * + 1000 ) / 10; + Log3( $hash, 3, + "[$hash->{NAME}] " + . "Error with external readings from: " + . $extDev . ":" + . $extReading + . " , taking original weather readings" ); + } + } - if( (my $ExtWeather = AttrVal($hashName, "useExtWeather", "")) eq "") { - $twilight_weather = int(($dElevation-$hash->{WEATHER_HORIZON}+12.0)/18.0 * 1000)/10; - Log3 ($hash, 5, "[$hash->{NAME}] " . "Original weather readings"); - } else { - my($extDev,$extReading) = split(":",$ExtWeather); - my $extWeatherHorizont = ReadingsVal($extDev,$extReading,-1); - if ($extWeatherHorizont >= 0){ - $extWeatherHorizont = 100 if ($extWeatherHorizont > 100); - Log3 ($hash, 5, "[$hash->{NAME}] " . "New weather readings from: ".$extDev.":".$extReading.":".$extWeatherHorizont); - $twilight_weather = $twilight - int(0.007 * ($extWeatherHorizont ** 2)); ## SCM: 100% clouds => 30% light (rough estimation) - } else { - $twilight_weather = int(($dElevation-$hash->{WEATHER_HORIZON}+12.0)/18.0 * 1000)/10; - Log3 ($hash, 3, "[$hash->{NAME}] " . "Error with external readings from: ".$extDev.":".$extReading." , taking original weather readings"); - } - } + $twilight_weather = + min( 100, max( $twilight_weather, 0 ) ); - $twilight_weather = List::Util::min(100, List::Util::max($twilight_weather,0)); - - # set readings - $dAzimuth = int(100*$dAzimuth )/100; - $dElevation = int(100*$dElevation)/100; + # set readings + $dAzimuth = int( 100 * $dAzimuth ) / 100; + $dElevation = int( 100 * $dElevation ) / 100; - my $compassPoint = Twilight_CompassPoint($dAzimuth); + my $compassPoint = Twilight_CompassPoint($dAzimuth); - readingsBeginUpdate($hash); - readingsBulkUpdate ($hash, "azimuth", $dAzimuth ); - readingsBulkUpdate ($hash, "elevation", $dElevation ); - readingsBulkUpdate ($hash, "twilight", $twilight ); - readingsBulkUpdate ($hash, "twilight_weather", $twilight_weather ); - readingsBulkUpdate ($hash, "compasspoint", $compassPoint); - readingsEndUpdate ($hash, defined($hash->{LOCAL} ? 0 : 1)); + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "azimuth", $dAzimuth ); + readingsBulkUpdate( $hash, "elevation", $dElevation ); + readingsBulkUpdate( $hash, "twilight", $twilight ); + readingsBulkUpdate( $hash, "twilight_weather", $twilight_weather ); + readingsBulkUpdate( $hash, "compasspoint", $compassPoint ); + readingsEndUpdate( $hash, defined( $hash->{LOCAL} ? 0 : 1 ) ); - Twilight_sunposTimerSet($hash); + Twilight_sunposTimerSet($hash); - return; + return; } ################################################################################ sub Twilight_CompassPoint { - my $azimuth = shift // return; + my $azimuth = shift // return; - return "unknown" if !looks_like_number($azimuth) || $azimuth < 0; - return "north" if $azimuth < 22.5; - return "north-northeast" if $azimuth < 45; - return "northeast" if $azimuth < 67.5; - return "east-northeast" if $azimuth < 90; - return "east" if $azimuth < 112.5; - return "east-southeast" if $azimuth < 135; - return "southeast" if $azimuth < 157.5; - return "south-southeast" if $azimuth < 180; - return "south" if $azimuth < 202.5; - return "south-southwest" if $azimuth < 225; - return "southwest" if $azimuth < 247.5; - return "west-southwest" if $azimuth < 270; - return "west" if $azimuth < 292.5; - return "west-northwest" if $azimuth < 315; - return "northwest" if $azimuth < 337.5; - return "north-northwest" if $azimuth <= 361; - return "unknown"; + return "unknown" if !looks_like_number($azimuth) || $azimuth < 0; + return "north" if $azimuth < 22.5; + return "north-northeast" if $azimuth < 45; + return "northeast" if $azimuth < 67.5; + return "east-northeast" if $azimuth < 90; + return "east" if $azimuth < 112.5; + return "east-southeast" if $azimuth < 135; + return "southeast" if $azimuth < 157.5; + return "south-southeast" if $azimuth < 180; + return "south" if $azimuth < 202.5; + return "south-southwest" if $azimuth < 225; + return "southwest" if $azimuth < 247.5; + return "west-southwest" if $azimuth < 270; + return "west" if $azimuth < 292.5; + return "west-northwest" if $azimuth < 315; + return "northwest" if $azimuth < 337.5; + return "north-northwest" if $azimuth <= 361; + return "unknown"; } sub twilight { - my ($twilight, $reading, $min, $max) = @_; + my ( $twilight, $reading, $min, $max ) = @_; - my $t = hms2h(ReadingsVal($twilight,$reading,0)); + my $t = hms2h( ReadingsVal( $twilight, $reading, 0 ) ); - $t = hms2h($min) if(defined($min) && (hms2h($min) > $t)); - $t = hms2h($max) if(defined($max) && (hms2h($max) < $t)); + $t = hms2h($min) if ( defined($min) && ( hms2h($min) > $t ) ); + $t = hms2h($max) if ( defined($max) && ( hms2h($max) < $t ) ); - return h2hms_fmt($t); + return h2hms_fmt($t); } 1; +__END__ + + =pod -=item device +=encoding utf8 +=item helper =item summary generate twilight & sun related events; check alternative Astro. =item summary_DE liefert Dämmerungs Sonnen basierte Events. Alternative: Astro =begin html @@ -710,25 +975,31 @@ sub twilight { Define
    - define <name> Twilight [<latitude> <longitude> [<indoor_horizon> [<Weather_Position>]]]
    + define <name> Twilight [<latitude> <longitude>] [<indoor_horizon> [<weatherDevice[:Reading]>]]

    Defines a virtual device for Twilight calculations

    latitude, longitude
    - The parameters latitude and longitude are decimal numbers which give the position on earth for which the twilight states shall be calculated. They are optional, but necessary in case you also want to set an indoor horizon. If not set, global values will be used instead (global itself defaults to Frankfurt/Main). + The parameters latitude and longitude are decimal numbers which give the position on earth for which the twilight states shall be calculated. They are optional, but in case if set, you have to set both of them. If not set, global values will be used instead (global itself defaults to Frankfurt/Main).

    indoor_horizon
    - The parameter indoor_horizon gives a virtual horizon, that shall be used for calculation of indoor twilight. Minimal value -6 means indoor values are the same like civil values. - indoor_horizon 0 means indoor values are the same as real values. indoor_horizon > 0 means earlier indoor sunset resp. later indoor sunrise. + The parameter indoor_horizon gives a virtual horizon, that shall be used for calculation of indoor twilight. Minimal value -6 means indoor values are the same like civil values. + indoor_horizon 0 means indoor values are the same as real values. indoor_horizon > 0 means earlier indoor sunset resp. later indoor sunrise. +
    + Defaults to 3 if not set.

    - Weather_Position + weatherDevice:Reading
    - The parameter Weather_Position is the yahoo weather id used for getting the weather condition. Go to http://weather.yahoo.com/ and enter a city or zip code. In the upcoming webpage, the id is a the end of the URL. Example: Munich, Germany -> 676757 + The parameter weatherDevice:Reading can be used to point to a device providing cloud coverage information to calculate twilight_weather.
    + The reading used shoud be in the range of 0 to 100 like the reading c_clouds in an openweathermap device, where 0 is clear sky and 100 are overcast clouds.
    Example: MyWeather:cloudCover

    - NOTE: As yahoo weather service is no longer available, this setting will not be used any longer; consider using useExtWeather attribute to partly compensate. + NOTE 1: using useExtWeather attribute may override settings in DEF. +
    +
    + NOTE 2: If weatherDevice-type is known, is optional (atm only "Weather"-type devices are supported).
    A Twilight device periodically calculates the times of different twilight phases throughout the day. It calculates a virtual "light" element, that gives an indicator about the amount of the current daylight. @@ -759,9 +1030,12 @@ sub twilight { consider these aspects.

    - Example: + Examples:
    -      define myTwilight Twilight 49.962529  10.324845 3 676757
    +      define myTwilight Twilight 49.962529  10.324845 3 localWeather:clouds
    +    
    +
    +      define myTwilight2 Twilight 4 localWeather
         

@@ -813,10 +1087,10 @@ sub twilight { Attributes
  • readingFnAttributes
  • -
  • useExtWeather <device>:<reading>
  • +
  • useExtWeather <device>[:<reading>]
  • use data from other devices to calculate twilight_weather.
    The reading used shoud be in the range of 0 to 100 like the reading c_clouds in an openweathermap device, where 0 is clear sky and 100 are overcast clouds.
    - With the use of this attribute weather effects like heavy rain or thunderstorms are neglegted for the calculation of the twilight_weather reading.
    + Note: Atm. additional weather effects like heavy rain or thunderstorms are neglegted for the calculation of the twilight_weather reading.

@@ -847,32 +1121,38 @@ Example:

Twilight

    - Akkgemeine Hinweise
    - Dieses Modul nutzte früher Daten von der Yahoo Wetter API. Diese ist leider nicht mehr verfügbar, daher ist die heutige Funktionalität deutlich eingeschränkt. Dies kann zu einem gewissen Grad kompensiert werden, indem man useExtWeather setzt, um Bedeckungsgrade mit Wolken zu berücksichtigen. Falls Sie nur Astronomische Daten benötigen, wäre Astro hierfür eine genauere Alternative.

    + Allgemeine Hinweise
    + Dieses Modul nutzte früher Daten von der Yahoo Wetter API. Diese ist leider nicht mehr verfügbar, daher ist die heutige Funktionalität deutlich eingeschränkt. Dies kann zu einem gewissen Grad kompensiert werden, indem man im define oder das Attribut useExtWeather ein externes Wetter-Device setzt, um Bedeckungsgrade mit Wolken zu berücksichtigen. Falls Sie nur Astronomische Daten benötigen, wäre Astro hierfür eine genauere Alternative.


    Define
      - define <name> Twilight [<latitude> <longitude> [<indoor_horizon> [<Weather_Position>]]]
      + define <name> Twilight [<latitude> <longitude>] [<indoor_horizon> [<weatherDevice[:Reading]>]]

      Erstellt ein virtuelles Device für die Dämmerungsberechnung (Zwielicht)

      latitude, longitude (geografische Länge & Breite)
      - Die Parameter latitude und longitude sind Dezimalzahlen welche die Position auf der Erde bestimmen, für welche der Dämmerungs-Status berechnet werden soll. Sie sind optional, wenn nicht vorhanden, werden die Angaben in global berücksichtigt, bzw. ohne weitere Angaben die Daten von Frankfurt/Main. + Die Parameter latitude und longitude sind Dezimalzahlen welche die Position auf der Erde bestimmen, für welche der Dämmerungs-Status berechnet werden soll. Sie sind optional, wenn nicht vorhanden, werden die Angaben in global berücksichtigt, bzw. ohne weitere Angaben die Daten von Frankfurt/Main. Möchte man andere als die in global gesetzten Werte setzen, müssen zwingend beide Werte angegeben werden.

      indoor_horizon
      - Der Parameter indoor_horizon bestimmt einen virtuellen Horizont, der für die Berechnung der Dämmerung innerhalb von Rämen genutzt werden kann. Minimalwert ist -6 (ergibt gleichen Wert wie Zivile Dämmerung). Bei 0 fallen + Der Parameter indoor_horizon bestimmt einen virtuellen Horizont, der für die Berechnung der Dämmerung innerhalb von Rämen genutzt werden kann. Minimalwert ist -6 (ergibt gleichen Wert wie Zivile Dämmerung). Bei 0 fallen indoor- und realer Dämmerungswert zusammen. Werte grösser 0 ergeben frühere Werte für den Abend bzw. spätere für den Morgen.

      - Weather_Position + weatherDevice:Reading
      - Der Parameter Weather_Position ist die Yahoo! Wetter-ID welche für den Bezug der Wetterinformationen gebraucht wird. Gehe auf http://weather.yahoo.com/ und gebe einen Ort (ggf. PLZ) ein. In der URL der daraufhin geladenen Seite ist an letzter Stelle die ID. Beispiel: München, Deutschland -> 676757 + Der Parameter weatherDevice:Reading kann genutzt werden, um über ein anderes Device an den Bedeckungsgrad für die Berechnung von twilight_weather bereitzustellen.
      + Das Reading sollte sich im Intervall zwischen 0 und 100 bewegen, z.B. das Reading c_clouds in einem openweathermap device, bei dem 0 heiteren und 100 bedeckten Himmel bedeuten. +
      Beispiel: MyWeather:cloudCover

      - Hinweis: Da der Yahoo-Wetterdienst nicht mehr zur Verfügung steht, ist dieses Attribut nutzlos. Siehe useExtWeather als Alternative.
      + Hinweis 1: Eventuelle Angaben im useExtWeather-Attribut überschreiben die Angaben im define. +
      + Hinweis 2: Bei bekannten Wetter-Device-Typen (im Moment ausschließlich: Weather) ist die Angabe des Readings optional. +
      +
      Ein Twilight-Device berechnet periodisch die Dämmerungszeiten und -phasen während des Tages. Es berechnet ein virtuelles "Licht"-Element das einen Indikator für die momentane Tageslichtmenge ist. Neben der Position auf der Erde wird es vom sog. "indoor horizon" (Beispielsweise hohe Gebäde oder Berge) @@ -902,7 +1182,7 @@ Wissenswert dazu ist, dass die Sonne, abhägnig vom Breitengrad, bestimmte E Beispiel:
      -      define myTwilight Twilight 49.962529  10.324845 3 676757
      +      define myTwilight Twilight 49.962529 10.324845 4.5 MeinWetter_cloudCover
           

    @@ -923,14 +1203,14 @@ Wissenswert dazu ist, dass die Sonne, abhägnig vom Breitengrad, bestimmte E - + - - + + @@ -956,7 +1236,7 @@ Wissenswert dazu ist, dass die Sonne, abhägnig vom Breitengrad, bestimmte E
  • useExtWeather <device>:<reading>
  • Nutzt Daten von einem anderen Device um twilight_weather zu berechnen.
    Das Reading sollte sich im Intervall zwischen 0 und 100 bewegen, z.B. das Reading c_clouds in einemopenweathermap device, bei dem 0 heiteren und 100 bedeckten Himmel bedeuten. - Wird diese Attribut genutzt , werden Wettereffekte wie Starkregen oder Gewitter fuer die Berechnung von twilight_weather nicht mehr herangezogen. + Wettereffekte wie Starkregen oder Gewitter k¨nnen derzeit für die Berechnung von twilight_weather nicht mehr herangezogen werden.
    @@ -981,4 +1261,48 @@ Anwendungsbeispiel: =end html_DE + +=for :application/json;q=META.json 59_Twilight.pm +{ + "abstract" : "generate twilight & sun related events; check alternative Astro.", + "author" : [ + "Beta-User <>", + "orphan <>" + ], + "keywords" : [ + "Timer", + "light", + "twlight", + "Dämmerung", + "Helligkeit", + "Wetter", + "weather" + ], + "name" : "FHEM::Twilight", + "prereqs" : { + "runtime" : { + "requires" : { + "FHEM::Meta" : "0", + "GPUtils" : "0", + "List::Util" : "0", + "Math::Trig" : "0", + "Time::Local" : "0", + "strict" : "0", + "warnings" : "0", + "FHEM::Core::Timer::Helper" : "0" + } + } + }, + "x_fhem_maintainer" : [ + "Beta-User", + "orphan" + ], + "x_lang" : { + "de" : { + "abstract" : "liefert Dämmerungs- und Sonnenstands-basierte Events. Alternative: Astro" + } + } +} +=end :application/json;q=META.json + =cut
    lightder aktuelle virtuelle Tageslicht-Wert
    nextEventName des nächsten Events
    nextEventTimedie Zeit wann das nächste Event wahrscheinlich passieren wird (während Lichtphase 5 und 6 wird dieser Wert aktualisiert wenn sich das Wetter ändert)
    nextEventTimedie Zeit wann das nächste Event wahrscheinlich passieren wird; (während Lichtphase 5 und 6 wird dieser Wert aktualisiert wenn sich das Wetter ändert)
    sr_astroZeit des astronomitschen Sonnenaufgangs
    sr_nautZeit des nautischen Sonnenaufgangs
    sr_civilZeit des zivilen/bürgerlichen Sonnenaufgangs
    srZeit des Sonnenaufgangs
    sr_indoorZeit des "indoor" Sonnenaufgangs
    sr_weather"Wert" des Wetters beim Sonnenaufgang
    ss_weather"Wert" des Wetters beim Sonnenuntergang
    sr_weather"Zeit" des wetterabhängigen Sonnenaufgangs
    ss_weather"Zeit" des wetterabhängigen Sonnenuntergangs
    ss_indoorZeit des "indoor" Sonnenuntergangs
    ssZeit des Sonnenuntergangs
    ss_civilZeit des zivilen/bürgerlichen Sonnenuntergangs