]'
if int(@$aref) < 2 || int(@$aref) > 6;
my $DEFmayChange = int(@$aref) == 6 ? 1 : 0;
my $weather = q{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...
my $indoor_horizon = q{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;
$hash->{helper}{'.LATITUDE'} = $latitude;
$hash->{helper}{'.LONGITUDE'} = $longitude;
$hash->{helper}{'.startuptime'} = time;
$hash->{SUNPOS_OFFSET} = 5 * 60;
$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 : 'none';
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_Undef {
my $hash = shift;
my $arg = shift // return;
deleteAllRegIntTimer($hash);
notifyRegexpChanged( $hash, "" );
for my $key ( keys %{ $hash->{helper}{extWeather} } ) {
delete $hash->{helper}{extWeather}{$key};
}
delete $hash->{helper}{extWeather};
return;
}
################################################################################
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|PROPLANTA");
my ($err, $wreading);
($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";
$hash->{DEF} = $newdef;
return;
}
################################################################################
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} // q{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);
return Twilight_HandleWeatherData( $hash, 1) if $found;
}
return;
}
sub Twilight_HandleWeatherData {
my $hash = shift // return;
my $inNotify = shift // 0;
my $wname = $hash->{helper}{extWeather}{Device} // q{none};
my $name = $hash->{NAME};
my ($extWeather, $sr_extWeather, $ss_extWeather);
my $dispatch = defined $hash->{helper}{extWeather} && defined $hash->{helper}{extWeather}{dispatch} ? 1 : 0;
if (!$dispatch
|| $dispatch && !defined $hash->{helper}{extWeather}{dispatch}{function} && !defined $hash->{helper}{extWeather}{dispatch}{userfunction} )
{
$extWeather = ReadingsNum($wname, $hash->{helper}{extWeather}{Reading},-1);
} elsif (defined $hash->{helper}{extWeather}{dispatch}{function} ) {
#Log3( $hash, 5, "[$hash->{NAME}] before dispatch" );
return if ref $hash->{helper}{extWeather}{dispatch}->{function} ne 'CODE';
( $extWeather, $sr_extWeather, $ss_extWeather ) = $hash->{helper}{extWeather}{dispatch}->{function}->($hash, $wname);
Log3( $hash, 5, "[$hash->{NAME}] after dispatch. results: $extWeather $sr_extWeather $ss_extWeather" );
} elsif (defined $hash->{helper}{extWeather}{dispatch}{userfunction} ) {
#Log3( $hash, 5, "[$hash->{NAME}] before dispatch" );
my %specials = (
'$W_DEVICE' => $extWeather,
'$W_READING' => $hash->{helper}{extWeather}{trigger}
);
my $evalcode = $hash->{helper}{extWeather}{dispatch}{userfunction};
#map { my $key = $_; $key =~ s{\$}{\\\$}gxms;
# my $val = $specials{$_};
# $evalcode =~ s{$key}{$val}gxms
#} keys %specials;
for my $key (keys %specials) {
my $val = $specials{$key};
$evalcode =~ s{\Q$key\E}{$val}gxms;
}
my $ret = AnalyzePerlCommand( $hash, $evalcode );
Log3( $hash, 4, "[$hash->{NAME}] external code result: $ret" );
( $extWeather, $sr_extWeather, $ss_extWeather ) =
split m{:}x, $ret;
return if !looks_like_number($extWeather);
$sr_extWeather = 50 if !looks_like_number($sr_extWeather);
$ss_extWeather = 50 if !looks_like_number($ss_extWeather);
}
my $lastcc = ReadingsNum($name, 'cloudCover', -1);
#here we have to split up for extended forecast handling...
$inNotify ? Log3( $hash, 5, "[$name] NotifyFn called, reading is $extWeather, last is $lastcc" )
: Log3( $hash, 5, "[$name] timer based weather update called, reading is $extWeather, last is $lastcc" );
return if $inNotify && (abs($lastcc - $extWeather) <6 && !defined $hash->{helper}{extWeather}{dispatch} || ReadingsAge($name, 'cloudCover', 4000) < 3575 && defined $hash->{helper}{extWeather}{dispatch});
my $weather_horizon = Twilight_getWeatherHorizon( $hash, $extWeather, 1);
my ($sr, $ss) = Twilight_calc( $hash, $weather_horizon, '7' ); #these are numbers
my ($sr_wh,$ss_wh);
if (defined $ss_extWeather) {
Log3( $hash, 5, "[$name] ss_extWeather exists" );
$sr_wh = Twilight_getWeatherHorizon( $hash, $sr_extWeather, 0);
$ss_wh = Twilight_getWeatherHorizon( $hash, $ss_extWeather, 0);
my ($srt_wh, $sst_wh) = Twilight_calc( $hash, $sr_wh, "7" );
$sr = $srt_wh;
($srt_wh, $sst_wh) = Twilight_calc( $hash, $ss_wh, "7" );
$ss = $sst_wh;
}
my $now = time;
Log3( $hash, 5, "[$name] extW-update fn: calc sr_w is ".FmtTime( $sr) .", ss_w is ".FmtTime( $ss) );
#done for today?
return if $inNotify && $now > min($ss , $hash->{TW}{ss_weather}{TIME});
Log3( $hash, 5, "[$name] not yet done for today" );
#set potential dates in the past to now
my $sr_passed = 0;
my $ss_passed = 0;
if ($inNotify) {
$sr = max( $sr, $now - 0.01 );
$ss = max( $ss, $now - 0.01 );
$sr_passed = $hash->{TW}{sr_weather}{TIME} > $sr ? 1 : 0;
$ss_passed = $hash->{TW}{ss_weather}{TIME} > $ss ? 1 : 0;
}
#renew dates and timers?, fire events?
my $nextevent = ReadingsVal($name,'nextEvent','none');
my $nextEventTime = FmtTime( $sr );
readingsBeginUpdate( $hash );
readingsBulkUpdate( $hash, 'cloudCover', $extWeather );
readingsBulkUpdate( $hash, 'cloudCover_sr', $sr_extWeather ) if $sr_wh && $now < $sr;
readingsBulkUpdate( $hash, 'cloudCover_ss', $ss_extWeather ) if $ss_wh && $now < $ss;
if ( $now < $sr ) {
$hash->{TW}{sr_weather}{TIME} = $sr;
resetRegIntTimer( 'sr_weather', $sr, \&Twilight_fireEvent, $hash, 0 );
readingsBulkUpdate( $hash, 'sr_weather', $nextEventTime ) if $inNotify;
readingsBulkUpdate( $hash, 'nextEventTime', $nextEventTime ) if $nextevent eq 'sr_weather';
} elsif ( $sr_passed ) {
deleteSingleRegIntTimer( 'sr_weather', $hash, \&Twilight_fireEvent);
readingsBulkUpdate( $hash, 'sr_weather', $nextEventTime );
if ( $nextevent eq 'sr_weather' ) {
readingsBulkUpdate( $hash, 'nextEvent', 'ss_weather' ) ;
readingsBulkUpdate( $hash, 'nextEventTime', FmtTime( $ss ) ) ;
readingsBulkUpdate( $hash, 'state', '6' );
readingsBulkUpdate( $hash, 'light', '6' );
readingsBulkUpdate( $hash, 'aktEvent', 'sr_weather' );
resetRegIntTimer( 'ss_weather', $ss, \&Twilight_fireEvent, $hash, 0 );
}
}
if ( $now < $ss ) {
$nextEventTime = FmtTime( $ss );
$hash->{TW}{ss_weather}{TIME} = $ss;
resetRegIntTimer( 'ss_weather', $ss, \&Twilight_fireEvent, $hash, 0 );
readingsBulkUpdate( $hash, 'ss_weather', $nextEventTime ) if $inNotify;
readingsBulkUpdate( $hash, 'nextEventTime', $nextEventTime ) if $nextevent eq 'ss_weather' && !$ss_passed;
} elsif ( $ss_passed ) {
deleteSingleRegIntTimer( 'ss_weather', $hash, \&Twilight_fireEvent );
readingsBulkUpdate( $hash, 'ss_weather', $nextEventTime );
if ( $nextevent eq 'ss_weather' ) {
readingsBulkUpdate( $hash, 'nextEvent', 'ss_indoor' ) ;
readingsBulkUpdate( $hash, 'nextEventTime', FmtTime( $hash->{TW}{ss_indoor}{TIME} ) ) ;
readingsBulkUpdate( $hash, 'aktEvent', 'ss_weather' );
readingsBulkUpdate( $hash, 'state', '8' );
readingsBulkUpdate( $hash, 'light', '4' );
}
}
readingsEndUpdate( $hash, defined( $hash->{LOCAL} ? 0 : 1 ) );
resetRegIntTimer('sunpos', $now+1, \&Twilight_sunpos, $hash, 0);
return;
}
sub Twilight_Firstrun {
my $hash = shift // return;
my $name = $hash->{NAME};
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};
$attrVal = "$hash->{helper}{extWeather}{Device}:$hash->{helper}{extWeather}{trigger}" if !$attrVal && defined $hash->{helper} && defined $hash->{helper}{extWeather}{Device} && defined $hash->{helper}{extWeather}{trigger};
my $extWeatherVal = 0;
if ($attrVal && $attrVal ne 'none') {
Twilight_init_ExtWeather_usage( $hash, $attrVal );
my $ewr = $hash->{helper}{extWeather}{Reading} // $hash->{helper}{extWeather}{dispatch}{trigger} // $hash->{helper}{extWeather}{trigger};
$extWeatherVal = ReadingsNum( $name, 'cloudCover', ReadingsNum( $hash->{helper}{extWeather}{Device}, $ewr, 0 ) );
readingsSingleUpdate ( $hash, 'cloudCover', $extWeatherVal, 0 ) if $extWeatherVal;
}
Twilight_getWeatherHorizon( $hash, $extWeatherVal );
my $fnHash = { HASH => $hash };
Twilight_sunpos($fnHash) if !$attrVal || $attrVal eq 'none';
Twilight_Midnight($fnHash, 1);
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 defined $hash->{helper} && defined $hash->{helper}{extWeather} && defined $hash->{helper}{extWeather}{regexp} && $hash->{helper}{extWeather}{regexp} =~ m{$attrVal}xms;
return Twilight_init_ExtWeather_usage($hash, $attrVal);
} elsif ($cmd eq 'del') {
notifyRegexpChanged( $hash, q{} );
for my $key ( keys %{ $hash->{helper}{extWeather} } ) {
delete $hash->{helper}{extWeather}{$key};
}
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);
my @parts;
if (!looks_like_number($devreading)) {
@parts = split m{\s}x, $devreading, 2;
($extWeather, $extWReading) = split m{:}x, $parts[0];
return 'External weather device seems not to exist' if !defined $defs{$extWeather} && $init_done;
### This is the place to allow external dispatch functions...
if ($parts[1] ) { #&& 0 to disable this part atm...
my %specials = (
'$W_DEV' => $extWeather,
'$W_READING' => $extWReading
);
$err = perlSyntaxCheck( $parts[1] );
return $err if ( $err );
}
} else {
#conversion code, try to guess the ext. weather device to replace yahoo
my @devices=devspec2array(q{TYPE=Weather});
return 'No Weather-Type device found' if !$devices[0];
$extWeather = $devices[0];
}
($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 if !$parts[1] && !exists $hash->{helper}{extWeather}{dispatch};
if ($parts[1]) {
$hash->{helper}{extWeather}{trigger} = $extWReading ;
$parts[1] = qq ({ $parts[1] }) if $parts[1] !~ m/^[{].*}$/xms;
$hash->{helper}{extWeather}{dispatch}{userfunction} = $parts[1];
}
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;
for my $key ( keys %{ $hash->{helper}{extWeather} } ) {
delete $hash->{helper}{extWeather}{$key};
}
delete $hash->{helper}{extWeather}{dispatch};
my $dispatch = {
Weather => {
trigger => 'cloudCover',
function => \&getwTYPE_Weather
},
PROPLANTA => {
trigger => 'fc0_cloud06',
function => \&getwTYPE_PROPLANTA
}
};
if (ref $dispatch->{$wtype} eq 'HASH') {
$extWReading = $dispatch->{$wtype}{trigger};
$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 secondsSinceMidnight {
my $now = shift // time;
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 $now = shift // time;
my $midnight = $now - secondsSinceMidnight( $now );
my $lat = $hash->{helper}{'.LATITUDE'};
my $long = $hash->{helper}{'.LONGITUDE'};
my $sr =
sr_alt( $now, 1, 0, 0, 0, "Horizon=$deg", undef, undef, undef, $lat,
$long );
my $ss =
sr_alt( $now, 0, 0, 0, 0, "Horizon=$deg", undef, undef, undef, $lat,
$long );
my ( $srhour, $srmin, $srsec ) = split m{:}x, $sr;
$srhour -= 24 if ( $srhour >= 24 );
my ( $sshour, $ssmin, $sssec ) = split m{:}x, $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 = shift;
my $whitchTimes = shift // return;
my $firstrun = shift // 0;
my $name = $hash->{NAME};
my $swip = $firstrun;
my $lat = $hash->{helper}{'.LATITUDE'};
my $long = $hash->{helper}{'.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}x;
my ( $sxname, $deg ) = split m{:}x, $horizon;
my $sr = "sr$sxname";
my $ss = "ss$sxname";
$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;
( $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)" );
}
}
# ------------------------------------------------------------------------------
readingsBeginUpdate($hash);
for my $ereignis ( keys %{ $hash->{TW} } ) {
next if $whitchTimes eq 'weather' && $ereignis !~ m{weather}x;
readingsBulkUpdate( $hash, $ereignis,
!defined $hash->{TW}{$ereignis}{TIME} || $hash->{TW}{$ereignis}{TIME} == 0
? 'undefined'
: FmtTime( $hash->{TW}{$ereignis}{TIME} ) );
}
readingsEndUpdate( $hash, defined $hash->{LOCAL} ? 0 : 1 );
# ------------------------------------------------------------------------------
my @horizonsOhneDeg =
map { my ( $e, $deg ) = split m{:}x, $_; "$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 $now = time;
for my $ereignis ( sort keys %{ $hash->{TW} } ) {
next if ( $whitchTimes eq 'weather' && $ereignis !~ m{weather}x );
deleteSingleRegIntTimer( $ereignis, $hash, \&Twilight_fireEvent );
if ( defined $hash->{TW}{$ereignis}{TIME} && ($hash->{TW}{$ereignis}{TIME} > $now || $firstrun) ) { # had been > 0
setRegIntTimer( $ereignis, $hash->{TW}{$ereignis}{TIME},
\&Twilight_fireEvent, $hash, 0 );
}
}
# ------------------------------------------------------------------------------
return 1;
}
################################################################################
sub Twilight_fireEvent {
my $fnHash = shift // return;
my ($hash, $modifier) = ($fnHash->{HASH}, $fnHash->{MODIFIER});
return if ( !defined $hash );
my $name = $hash->{NAME};
my $event = $modifier;
my $myHash =$hash->{TW}{$modifier};
my $deg = $myHash->{DEG};
my $light = $myHash->{LIGHT};
my $state = $myHash->{STATE};
my $swip = $myHash->{SWIP};
$swip = 0 if time - $hash->{helper}{'.startuptime'} < 60;
my $eventTime = $myHash->{TIME};
my $nextEvent = $myHash->{NAMENEXT};
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 $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 )
);
deleteSingleRegIntTimer($modifier, $hash, 1);
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 );
}
################################################################################
sub Twilight_Midnight {
my $fnHash = shift // return;
my $firstrun = shift // 0;
my ($hash, $modifier) = ($fnHash->{HASH}, $fnHash->{MODIFIER});
return if !defined $hash;
if (!defined $hash->{helper}{extWeather}{Device}) {
Twilight_TwilightTimes( $hash, 'mid', $firstrun);
Twilight_sunpos({HASH => $hash});
} else {
Twilight_HandleWeatherData( $hash, 0);
Twilight_TwilightTimes( $hash, 'mid', $firstrun);
}
my $now = time;
my $midnight = $now - secondsSinceMidnight( $now ) + DAYSECONDS + 1;
my $daylightsavingdelta = (localtime ( $now - 2 * HOURSECONDS ) )[2] - ( localtime( $now + 22 * HOURSECONDS ) )[2];
$midnight -= 19 * HOURSECONDS if $daylightsavingdelta == 1 && (localtime)[2] < 2;
$midnight -= 20 * HOURSECONDS if $daylightsavingdelta == -1 && (localtime)[2] < 3;
return resetRegIntTimer( 'Midnight', $midnight, \&Twilight_Midnight, $hash, 0 );
}
################################################################################
sub Twilight_sunposTimerSet {
my $hash = shift // return;
return resetRegIntTimer( 'sunpos', time + $hash->{SUNPOS_OFFSET}, \&Twilight_sunpos, $hash, 0 );
}
################################################################################
sub Twilight_getWeatherHorizon {
my $hash = shift;
my $result = shift // return;
my $setInternal = shift // 1;
return $hash->{INDOOR_HORIZON} if !looks_like_number($result) || $result < 0 || $result > 100;
my $weather_horizon = $result / 12.5;
$hash->{WEATHER_CORRECTION} = $weather_horizon if $setInternal;
$weather_horizon += $hash->{INDOOR_HORIZON};
my $doy = strftime("%j",localtime);
my $declination = 0.4095*sin(0.016906*($doy-80.086));
$weather_horizon = min( 89-$hash->{helper}{'.LATITUDE'}+$declination, $weather_horizon );
$hash->{WEATHER_HORIZON} = $weather_horizon if $setInternal;
return $weather_horizon;
}
################################################################################
sub Twilight_sunpos {
my $fnHash = shift // return;
my ($hash, $modifier) = ($fnHash->{HASH}, $fnHash->{MODIFIER});
#my $hash = Twilight_GetHashIndirekt( $fnHash, ( caller(0) )[3] );
return if ( !defined($hash) );
my $hashName = $hash->{NAME};
return if(IsDisabled($hashName));
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 $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 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 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);
# 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 );
# 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;
# 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_weather;
if (!defined $hash->{helper}{extWeather}{Device}) {
$twilight_weather =
int( ( $dElevation - $hash->{WEATHER_HORIZON} + 12.0 ) / 18.0 * 1000 )
/ 10;
Log3( $hash, 5, "[$hashName] Original weather readings" );
} else {
my $extWeatherHorizont = ReadingsNum($hashName,'cloudCover' , -1 );
if ( $extWeatherHorizont >= 0 ) {
$extWeatherHorizont = min (100, $extWeatherHorizont);
Log3( $hash, 5,
"[$hashName] Using cloudCover value $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,
"[$hashName] No useable cloudCover value available: ${extWeatherHorizont}, taking existant weather horizon." );
}
}
$twilight_weather =
min( 100, max( $twilight_weather, 0 ) );
# set readings
$dAzimuth = int( 100 * $dAzimuth ) / 100;
$dElevation = int( 100 * $dElevation ) / 100;
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 ) );
Twilight_sunposTimerSet($hash);
return;
}
################################################################################
sub Twilight_CompassPoint {
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';
}
sub twilight {
my ( $twilight, $reading, $min, $max, $cloudCover ) = @_;
my $hash = $defs{$twilight};
return "unknown device $twilight" if !defined $hash;
my $t;
$t = hms2h( ReadingsVal( $twilight, $reading, 0 ) ) if $reading ne 'sr_tomorrow' and $reading ne 'ss_tomorrow';
if ($reading eq 'sr_tomorrow' or $reading eq 'ss_tomorrow') {
my $wh = Twilight_getWeatherHorizon( $hash, $cloudCover // 50, 0);
my ($sr, $ss) = Twilight_calc( $hash, $wh, '7', time + DAYSECONDS );
$t = hms2h( FmtTime( $reading eq 'sr_tomorrow' ? $sr : $ss ) );
}
$t = hms2h($min) if ( defined($min) && ( hms2h($min) > $t ) );
$t = hms2h($max) if ( defined($max) && ( hms2h($max) < $t ) );
return h2hms_fmt($t);
}
###########
# Dispatch functions
sub getTwilightHours {
my $hash = shift // return;
my $hour = ( localtime )[2];
my $sr_hour = defined $hash->{TW}{sr_weather}{TIME} ? ( localtime( $hash->{TW}{sr_weather}{TIME} ))[2] : 7;
my $ss_hour = defined $hash->{TW}{ss_weather}{TIME} ? ( localtime( $hash->{TW}{ss_weather}{TIME} ))[2] : 18;
return $hour,$sr_hour,$ss_hour;
}
sub getwTYPE_Weather {
my $hash = shift // return;
my @ret;
my $extDev = $hash->{helper}{extWeather}{Device};
my ($hour, $sr_hour, $ss_hour) = getTwilightHours($hash);
my $rAge = int(ReadingsAge($extDev,'cloudCover',0)/3600);
$ret[0] = $rAge < 24 ? ReadingsNum($extDev,'cloudCover',0) : 50;
Log3( $hash, 5, "[$hash->{NAME}] function is called, cc is $ret[0], hours sr: $sr_hour, ss: $ss_hour" );
my $lastestFcHourVal = -1;
for (my $i=28; $i>-1; $i--) {
$lastestFcHourVal = ReadingsNum($extDev,"hfc${i}_cloudCover",-1);
last if $lastestFcHourVal > -1;
}
$lastestFcHourVal = 0 if $lastestFcHourVal == -1;
my $hfc_sr = max( 0 , $sr_hour - $hour ) + $rAge; #remark: needs some additionals logic for midnight updates! (ReadingsAge()?)
my $hfc_ss = max( 0 , $ss_hour - $hour ) + $rAge;
$ret[1] = $hfc_sr && $rAge < 24 ? ReadingsNum($extDev,"hfc${hfc_sr}_cloudCover",$lastestFcHourVal) : $ret[0];
$ret[2] = $hfc_ss && $rAge < 24 ? ReadingsNum($extDev,"hfc${hfc_ss}_cloudCover",$lastestFcHourVal) : $ret[0];
return @ret;
}
sub getwTYPE_PROPLANTA {
my $hash = shift // return;
my $extDev = $hash->{helper}{extWeather}{Device};
my @hour = getTwilightHours($hash);
my $fc_day0 = secondsSinceMidnight( time ) > 60 ? 0 : 1;
my $fc_day1 = $fc_day0 + 1;
my @ret;
for (my $i = 0; $i < 3 ; $i++) {
$hour[$i] < 4 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud03",0) : (
$hour[$i] < 7 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud06",0) : (
$hour[$i] < 10 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud09",0) : (
$hour[$i] < 13 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud12",0) : (
$hour[$i] < 16 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud15",0) : (
$hour[$i] < 19 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud18",0) : (
$hour[$i] < 22 ? $ret[$i] = ReadingsNum($extDev,"fc${fc_day0}_cloud21",0) :
$ret[$i] = ReadingsNum($extDev,"fc${fc_day1}_cloud00",0)))))));
}
#Log3( $hash, 4, "[$hash->{NAME}] Proplanta data: hours $hour[0]-$hour[1]-$hour[2], fc_day0 $fc_day0, data $ret[0]-$ret[1]-$ret[2]");
return @ret;
}
1;
__END__
=pod
=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
Twilight
General Remarks
This module profited much from the use of the yahoo weather API. Unfortunately, this service is no longer available, so Twilight functionality is very limited nowerdays. To some extend, the use of useExtWeather may compensate to dect cloudy skys. If you just want to have astronomical data available, consider using Astro instead.
Define
Set
Get
get <name> <reading>
light | the current virtual daylight value |
nextEvent | the name of the next event |
nextEventTime | the time when the next event will probably happen (during light phase 5 and 6 this is updated when weather conditions change |
sr_astro | time of astronomical sunrise |
sr_naut | time of nautical sunrise |
sr_civil | time of civil sunrise |
sr | time of sunrise |
sr_indoor | time of indoor sunrise |
sr_weather | time of weather sunrise |
ss_weather | time of weather sunset |
ss_indoor | time of indoor sunset |
ss | time of sunset |
ss_civil | time of civil sunset |
ss_nautic | time of nautic sunset |
ss_astro | time of astro sunset |
azimuth | the current azimuth of the sun 0° ist north 180° is south |
compasspoint | a textual representation of the compass point |
elevation | the elevaltion of the sun |
twilight | a percetal value of a new (twi)light value: (elevation+12)/18 * 100) |
twilight_weather | a percetal value of a new (twi)light value: (elevation-WEATHER_HORIZON+12)/18 * 100). So if there is weather, it
is always a little bit darker than by fair weather |
condition | the yahoo condition weather code |
condition_txt | the yahoo condition weather code as textual representation |
horizon | value auf the actual horizon 0°, -6°, -12°, -18° |
Attributes
- readingFnAttributes
- useExtWeather <device>[:<reading>] [<usercode>]
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.
Note: Atm. additional weather effects like heavy rain or thunderstorms are neglegted for the calculation of the twilight_weather reading.
By adding usercode, (Note: experimental feature! May work or not or lead to crashes etc....) you may get more realistic results for sr_weather and ss_weather calculation. Just return - besides the actual cloudCover reading value additional predicted values for corresponding indoor times, seperated by ":"
Value_A:Value_B:Value_C
Value_A representing the actual cloudCover value, Value_B at sr_indoor and Value_C at ss_indoor (all values in 0-100 format).
Example:
attr myTwilight useExtWeather MyWeather:cloudCover { myCloudCoverAnalysis("MyWeather") }
with corresponding code for myUtils:
sub myCloudCoverAnalysis {
my $weatherName = shift;
my $ret = ReadingsVal($weatherName,"cloudCover",50);
$ret .= ":".ReadingsVal($weatherName,"cloudCover_morning",55);
$ret .= ":".ReadingsVal($weatherName,"cloudCover_evening",65);
return $ret;
}
Functions
- twilight($twilight, $reading, $min, $max)
- implements a routine to compute the twilighttimes like sunrise with min max values.
$twilight | name of the twilight instance |
$reading | name of the reading to use example: ss_astro, ss_weather ... |
$min | parameter min time - optional |
$max | parameter max time - optional |
Example:
define BlindDown at *{twilight("myTwilight","sr_indoor","7:30","9:00")} set xxxx position 100
# xxxx is a defined blind
=end html
=begin html_DE
Twilight
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> [<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. 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äumen genutzt werden kann. Minimalwert ist -6 (ergibt gleichen Wert wie Zivile Dämmerung). Bei 0 fallen indoor- und realer Dämmerungswert zusammen. Werte größer 0 ergeben frühere Werte für den Abend bzw. spätere für den Morgen.
weatherDevice:Reading
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 1: Eventuelle Angaben im useExtWeather-Attribut überschreiben die Angaben im define.
Hinweis 2: Bei bekannten Wetter-Device-Typen (im Moment ausschließlich: Weather oder PROPLANTA) 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äude oder Berge)
und dem Wetter beeinflusst. Schlechtes Wetter führt zu einer Reduzierung des Tageslichts für den ganzen Tag.
Das berechnete Licht liegt zwischen 0 und 6 wobei die Werte folgendes bedeuten:
light
0 - Totale Nacht, die Sonne ist mind. -18 Grad hinter dem Horizont
1 - Astronomische Dämmerung, die Sonne ist zw. -12 und -18 Grad hinter dem Horizont
2 - Nautische Dämmerung, die Sonne ist zw. -6 and -12 Grad hinter dem Horizont
3 - Zivile/Bürgerliche Dämmerung, die Sonne ist zw. 0 and -6 hinter dem Horizont
4 - "indoor twilight", die Sonne ist zwischen dem Wert indoor_horizon und 0 Grad hinter dem Horizont (wird nicht verwendet wenn indoor_horizon=0)
5 - Wetterbedingte Dämmerung, die Sonne ist zwischen indoor_horizon und einem virtuellen Wetter-Horizonz (der Wetter-Horizont ist Wetterabhängig (optional)
6 - Maximales Tageslicht
state entspricht der aktuellen virtuellen "Tages-Phase" (0 = nach Mitternacht, 1 = nach sr_astro, ...12 = nach ss_astro)
Azimut, Elevation, Twilight (Seitenwinkel, Höhenwinkel, Dämmerung)
Das Modul berechnet zusätzlich Azimuth und Elevation der Sonne. Diese Werte können zur Rolladensteuerung verwendet werden.
Das Reading Twilight wird als neuer "(twi)light" Wert hinzugefügt. Er wird aus der Elevation der Sonne mit folgender Formel abgeleitet: (Elevation+12)/18 * 100). Das erlaubt eine detailliertere Kontrolle der Lampen während Sonnenauf - und untergang. Dieser Wert ist zwischen 0% und 100% wenn die Elevation zwischen -12° und 6°
Wissenswert dazu ist, dass die Sonne, abhägnig vom Breitengrad, bestimmte Elevationen nicht erreicht. Im Juni und Juli liegt die Sonne in Mitteleuropa nie unter -18°. In nördlicheren Gebieten (Norwegen, ...) kommt die Sonne beispielsweise nicht über 0°.
All diese Aspekte müssen berücksichtigt werden bei Schaltungen die auf Twilight basieren.
Beispiel:
define myTwilight Twilight 49.962529 10.324845 4.5 MeinWetter:cloudCover
Set
Get
get <name> <reading>
light | der aktuelle virtuelle Tageslicht-Wert |
nextEvent | Name des nächsten Events |
nextEventTime | die 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_astro | Zeit des astronomitschen Sonnenaufgangs |
sr_naut | Zeit des nautischen Sonnenaufgangs |
sr_civil | Zeit des zivilen/bürgerlichen Sonnenaufgangs |
sr | Zeit des Sonnenaufgangs |
sr_indoor | Zeit des "indoor" Sonnenaufgangs |
sr_weather | "Zeit" des wetterabhängigen Sonnenaufgangs |
ss_weather | "Zeit" des wetterabhängigen Sonnenuntergangs |
ss_indoor | Zeit des "indoor" Sonnenuntergangs |
ss | Zeit des Sonnenuntergangs |
ss_civil | Zeit des zivilen/bürgerlichen Sonnenuntergangs |
ss_nautic | Zeit des nautischen Sonnenuntergangs |
ss_astro | Zeit des astro. Sonnenuntergangs |
azimuth | aktueller Azimuth der Sonne. 0° ist Norden 180° ist Süden |
compasspoint | Ein Wortwert des Kompass-Werts |
elevation | the elevaltion of the sun |
twilight | Prozentualer Wert eines neuen "(twi)light" Wertes: (elevation+12)/18 * 100) |
twilight_weather | Prozentualer Wert eines neuen "(twi)light" Wertes: (elevation-WEATHER_HORIZON+12)/18 * 100). Wenn ein Wetterwert vorhanden ist, ist es immer etwas dunkler als bei klarem Wetter. |
condition | Yahoo! Wetter code |
condition_txt | Yahoo! Wetter code als Text |
horizon | Wert des aktuellen Horizont 0°, -6°, -12°, -18° |
Attributes
- readingFnAttributes
- useExtWeather <device>:<reading> [<usercode>]
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 einem openweathermap device, bei dem 0 heiteren und 100 bedeckten Himmel bedeuten.
Wettereffekte wie Starkregen oder Gewitter k¨nnen derzeit für die Berechnung von twilight_weather nicht mehr herangezogen werden.
Durch Angabe von usercode (Achtung: experimentelles feature! Kann auch schiefgehen...) kann die Berechnung der sr_weather und ss_weather-Zeiten verbessert werden, indem die zum jeweils zugehörigen indoor-Zeitpunkt gehörenden Vorhersage-Werte zurückgegeben werden. Das Rückgabe-Format der Funktion muss sein:
Wert_A:Wert_B:Wert_C
wobei Wert_A der aktuelle cloudCover-Wert ist, Wert_B der zum Zeitpunt für sr_indoor und Wert_C für ss_indoor (alle Werte nummerisch im Bereich 0-100).
Beispiel:
attr myTwilight useExtWeather MeinWetter:cloudCover { myCloudCoverAnalysis("MeinWetter") }
mit folgendem (wenig sinnvollen) Code für myUtils:
sub myCloudCoverAnalysis {
my $weatherName = shift;
my $ret = ReadingsVal($weatherName,"cloudCover",50);
$ret .= ":".ReadingsVal($weatherName,"cloudCover_morning",55);
$ret .= ":".ReadingsVal($weatherName,"cloudCover_evening",65);
return $ret;
}
Functions
- twilight($twilight, $reading, $min, $max)
- implementiert eine Routine um die Dämmerungszeiten wie Sonnenaufgang mit min und max Werten zu berechnen.
$twilight | Name der twiligh Instanz |
$reading | Name des zu verwendenden Readings. Beispiel: ss_astro, ss_weather ... |
$min | Parameter min time - optional |
$max | Parameter max time - optional |
Optional ist es möglich, auch die morgigen sr_weather bzw. ss_weather abzufragen, dafür werden die "fiktiven" Reading-Namen "sr_tomorrow" bzw. "ss_tomorrow" verwendet. Als Bedeckungsgrad wird dabei ein fiktiver Wert von "50" angenommen, dieser kann mit (optionalem) 5. Parameter auch abweichend (Bereich: 0-100) angegeben werden. Beispiel:
{ twilight('tw_test1','sr_tomorrow','08:00','09:10',100) }
Anwendungsbeispiel:
define BlindDown at *{twilight("myTwilight","sr_indoor","7:30","9:00")} set xxxx position 100
# xxxx ist ein definiertes Rollo
=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"
}
}
},
"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