From 14f5f73e4d0cbdb56f3a2652ef6ac9b9597ccda0 Mon Sep 17 00:00:00 2001 From: unimatrix27 <> Date: Fri, 23 Mar 2012 12:33:55 +0000 Subject: [PATCH] New module "Twilight" for calculation of daylight-phases based on the script available for the Homematic CCU from http://www.homematic-wiki.info/mw/index.php/TCLScript:twilight Update commandref.html to reflect interface of new module git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@1376 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- FHEM/59_Twilight.pm | 316 +++++++++++++++++++++++++++++++++++++++++++ docs/commandref.html | 78 +++++++++++ 2 files changed, 394 insertions(+) create mode 100644 FHEM/59_Twilight.pm diff --git a/FHEM/59_Twilight.pm b/FHEM/59_Twilight.pm new file mode 100644 index 000000000..2cd580da4 --- /dev/null +++ b/FHEM/59_Twilight.pm @@ -0,0 +1,316 @@ + +# +# 59_Twilight.pm +# written by Sebastian Stuecker based on Twilight.tcl http://www.homematic-wiki.info/mw/index.php/TCLScript:twilight +# +############################################## + +package main; +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use Switch; +use POSIX; + +sub dayofyear { + my ($day1,$month,$year)=@_; + my @cumul_d_in_m = +(0,31,59,90,120,151,181,212,243,273,304,334,365); + my $doy=$cumul_d_in_m[--$month]+$day1; + return $doy if $month < 2; + return $doy unless $year % 4 == 0; + return ++$doy unless $year % 100 == 0; + return $doy unless $year % 400 == 0; + return ++$doy; +} + +##################################### +sub Twilight_Initialize($) { + + my ($hash) = @_; + +# Provider# $hash->{Clients} = undef; + +# Consumer + $hash->{DefFn} = "Twilight_Define"; + $hash->{UndefFn} = "Twilight_Undef"; + $hash->{GetFn} = "Twilight_Get"; + $hash->{AttrList}= "loglevel:0,1,2,3,4,5 event-on-update-reading event-on-change-reading"; + +} + +sub Twilight_Get($@) { + + my ($hash, @a) = @_; + + return "argument is missing" if(int(@a) != 2); + + $hash->{LOCAL} = 1; + Twilight_GetUpdate($hash); + delete $hash->{LOCAL}; + + my $reading= $a[1]; + my $value; + + if(defined($hash->{READINGS}{$reading})) { + $value= $hash->{READINGS}{$reading}{VAL}; + } else { + return "no such reading: $reading"; + } + + return "$a[0] $reading => $value"; +} + + +sub Twilight_Define($$) { + + my ($hash, $def) = @_; + + # define Twilight [indoor_horizon [Weather_Position]] + # define MyTwilight Twilight 48.47 11.92 Weather_Position + + my @a = split("[ \t][ \t]*", $def); + + return "syntax: define Twilight [indoor_horizon [Weather]]" + if(int(@a) < 4 && int(@a) > 6); + + $hash->{STATE} = "0"; + my $latitude; + my $longitude; + + my $name = $a[0]; + if ($a[2] =~ /^[\+-]*[0-9]*\.*[0-9]*$/ && $a[2] !~ /^[\. ]*$/ ) { + $latitude = $a[2]; + if($latitude>90){$latitude=90;} + if($latitude<-90){$latitude=-90;} + }else{return "Argument Latitude is not a valid number";} + if ($a[3] =~ /^[\+-]*[0-9]*\.*[0-9]*$/ && $a[3] !~ /^[\. ]*$/ ) { + $longitude = $a[3]; + if($longitude>180){$longitude=180;} + if($longitude<-180){$longitude=-180;} + }else{return "Argument Longitude is not a valid number";} + my $weather = ""; + my $indoor_horizon="4"; + if(int(@a)>5) { $weather=$a[5] } + if(int(@a)>4) { if ($a[4] =~ /^[\+-]*[0-9]*\.*[0-9]*$/ && $a[4] !~ /^[\. ]*$/ ) { + $indoor_horizon = $a[4]; + if($indoor_horizon>20){ $indoor_horizon=20;} + if($indoor_horizon<0){$indoor_horizon=0;} + }else{return "Argument Indoor_Horizon is not a valid number";} } + + $hash->{LATITUDE} = $latitude; + $hash->{LONGITUDE} = $longitude; + $hash->{WEATHER} = $weather; + $hash->{INDOOR_HORIZON} = $indoor_horizon; + + Twilight_GetUpdate($hash); + + return undef; +} + +sub Twilight_Undef($$) { + + my ($hash, $arg) = @_; + + RemoveInternalTimer($hash); + return undef; +} + + +sub twilight_midnight_seconds() { my @time = localtime(); my $secs = ($time[2] * 3600) + ($time[1] * 60) + $time[0]; return $secs; } + + +sub Twilight_GetUpdate($){ + my ($hash) = @_; + my @sunrise_set; + + readingsBeginUpdate($hash); + my $sunrise; + my $sunset; + my $latitude=$hash->{LATITUDE}; + my $longitude=$hash->{LONGITUDE}; + my $horizon=$hash->{HORIZON}; + my $now=time; + my $midnight=twilight_midnight_seconds(); + my $midseconds=$now-$midnight; + my $year=strftime("%Y",localtime); + my $month=strftime("%m",localtime); + my $day=strftime("%d",localtime); + my $doy = dayofyear($day,$month,$year)+(($year%4)/4); + $doy+=($doy/365.0)/4.0; + my $timezone=(mktime(localtime($now))-mktime(gmtime($now)))/3600; + my $timediff=-0.171*sin(0.0337*$doy+0.465) - 0.1299*sin(0.01787 * $doy - 0.168); + my $declination=0.4095*sin(0.016906*($doy-80.086)); + my $twilight_midnight=$now+(0-$timediff-$longitude/15+$timezone)*3600; + my $yesterday_offset; + if($now<$twilight_midnight){ + $yesterday_offset=86400; + }else{ + $yesterday_offset=0; + } + $year=strftime("%Y",localtime($now-$yesterday_offset)); + $month=strftime("%m",localtime($now-$yesterday_offset)); + $day=strftime("%d",localtime($now-$yesterday_offset)); + $doy = dayofyear($day,$month,$year)+(($year%4)/4); + + $sunrise_set[0]{SR_NAME}="sr_astro"; + $sunrise_set[0]{SS_NAME}="ss_astro"; + $sunrise_set[0]{DEGREE}=-18; + $sunrise_set[1]{SR_NAME}="sr_naut"; + $sunrise_set[1]{SS_NAME}="ss_naut"; + $sunrise_set[1]{DEGREE}=-12; + $sunrise_set[2]{SR_NAME}="sr_civil"; + $sunrise_set[2]{SS_NAME}="ss_civil"; + $sunrise_set[2]{DEGREE}=-6; + $sunrise_set[3]{SR_NAME}="sr"; + $sunrise_set[3]{SS_NAME}="ss"; + $sunrise_set[3]{DEGREE}=0; + $sunrise_set[4]{SR_NAME}="sr_indoor"; + $sunrise_set[4]{SS_NAME}="ss_indoor"; + $sunrise_set[4]{DEGREE}=$hash->{INDOOR_HORIZON}; + $sunrise_set[5]{SR_NAME}="sr_weather"; + $sunrise_set[5]{SS_NAME}="ss_weather"; + $hash->{WEATHER_HORIZON}=Twilight_getWeatherHorizon($hash->{WEATHER})+$hash->{INDOOR_HORIZON}; + if($hash->{WEATHER_HORIZON}>(89-$hash->{LATITUDE}+$declination)){$hash->{WEATHER_HORIZON}=89-$hash->{LATITUDE}+$declination;} + $sunrise_set[5]{DEGREE}=$hash->{WEATHER_HORIZON}; + + + for(my $i=0;$i<6;$i++){ + ($sunrise_set[$i]{RISE},$sunrise_set[$i]{SET})= + twilight_calc($latitude,$longitude,$sunrise_set[$i]{DEGREE},$declination,$timezone,$midseconds,$timediff); + readingsUpdate($hash,$sunrise_set[$i]{SR_NAME},strftime("%H:%M:%S",localtime($sunrise_set[$i]{RISE}))); + readingsUpdate($hash,$sunrise_set[$i]{SS_NAME},strftime("%H:%M:%S",localtime($sunrise_set[$i]{SET}))); + } + my $k=0; + my $half="RISE"; + my $nexttime; + my $licht; + for(my $i=0;$i < 12;$i++){ + $nexttime=$sunrise_set[6-abs($i-6)-$k]{$half}; + if($nexttime > $now && $nexttime!=2000000000){ + readingsUpdate($hash,"light", 6-abs($i-6)); + if($i<6){ + readingsUpdate($hash,"nextEvent",$sunrise_set[6-abs($i-6)-$k]{SR_NAME}); + }else{ + readingsUpdate($hash,"nextEvent",$sunrise_set[6-abs($i-6)-$k]{SS_NAME}); + } + readingsUpdate($hash,"nextEventTime",strftime("%H:%M:%S",localtime($nexttime))); + if($i==5 || $i==6){ + $nexttime = ($nexttime-$now)/5; + $nexttime=120 if($nexttime<120); + $nexttime=900 if($nexttime>900); + }else{ + $nexttime = $nexttime+10; + } + if(!$hash->{LOCAL}) { + InternalTimer(sprintf("%.0f",$nexttime), "Twilight_GetUpdate", $hash, 0); + } + $hash->{STATE}=$i; + last; + } + if ($i == 5){ + $k=1; + $half="SET"; + } + if($nexttime<$now && $i==11){ + if(!$hash->{LOCAL}) { + InternalTimer($now+900, "Twilight_GetUpdate", $hash, 0); + } + readingsUpdate($hash,"light", 0); + $hash->{STATE}=0; + } + } + + readingsEndUpdate($hash, defined($hash->{LOCAL} ? 0 : 1)); + return 1; +} + +sub twilight_calc() { + my $latitude=shift; + my $longitude=shift; + my $horizon=shift; + my $declination=shift; + my $timezone=shift; + my $midseconds=shift; + my $timediff=shift; + my $suntime=0; + my $sunrise=0; + my $sunset=0; + eval { + $suntime=12*acos((sin($horizon/57.29578)-sin($latitude/57.29578)*sin($declination))/(cos($latitude/57.29578)*cos($declination)))/3.141592 ; + $sunrise=$midseconds+(12-$timediff-$suntime-$longitude/15+$timezone)*3600; + $sunset=$midseconds+(12-$timediff+$suntime-$longitude/15+$timezone)*3600; + }; + if($@){ + $sunrise=0; + $sunset=2000000000; + } + return $sunrise, $sunset; +} + +sub Twilight_getWeatherHorizon(){ + my $location=shift; + #my $xml = GetHttpFile("www.google.com:80", "/ig/api?weather=" . $location . "&hl=en"); + #$xml =~/\(.*)\<\/current_conditions\>/; + #my $current=$1; + #$current=~/\TRX_SECURITY   TRX_WEATHER   TUL   + Twilight   USF1000   USBWX   VantagePro2   @@ -4392,6 +4393,83 @@ A line ending with \ will be concatenated with the next one, so long lines
+ +

Twilight

+
    +
    + + + Define +
      + define <name> Twilight <latitude> <longitude> [<indoor_horizon> [<Weather_Position>]]
      +
      + Defines a virtual device for Twilight calculations

      + + 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. + Besides the location on earth it is influenced by a so called "indoor horizon" (e.g. if there are high buildings, mountains) as well as by weather conditions. Very bad weather conditions lead to a reduced daylight for nearly the whole day. + The light calculated spans between 0 and 6, where the values mean the following:

      + 0 - total night, sun is at least -18 degree below horizon
      + 1 - astronomical twilight, sun is between -12 and -18 degree below horizon
      + 2 - nautical twilight, sun is between -6 and -12 degree below horizon
      + 3 - civil twilight, sun is between 0 and -6 degree below horizon
      + 4 - indoor twilight, sun is between the indoor_horizon and 0 degree below horizon (not used if indoor_horizon=0)
      + 5 - weather twilight, sun is between indoor_horizon and a virtual weather horizon (the weather horizon depends on weather conditions (optional)
      + 6 - maximum daylight
      +
      + + The parameters latitude and longitude are decimal numbers which give the position on earth for which the twilight states shall be calculated.
      + The parameter indoor_horizon gives a virtual horizon higher than 0, that shall be used for calculation of indoor twilight (typical values are between 0 and 6)
      + 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
      + + Example: +
      +      define myTwilight Twilight 49.962529  10.324845 3 676757
      +    
      +
    +
    + + + Set +
      + N/A +
    +
    + + + + Get +
      + + get <name> <reading>

      + + + + + + + + + + + + + + + + +
      lightthe current virtual daylight value
      nextEventthe name of the next event
      nextEventTimethe time when the next event will probably happen (durint light phase 5 and 6 this is updated when weather conditions change
      sr_astrotime of astronomical sunrise
      sr_nauttime of nautical sunrise
      sr_civiltime of civil sunrise
      srtime of sunrise
      sr_indoortime of indoor sunrise
      sr_weathertime of weather sunrise
      ss_weathertime of weather sunset
      ss_indoortime of indoor sunset
      sstime of sunset
      ss_civiltime of civil sunset
      ss_nautictime of nautic sunset
      ss_astrotime of astro sunset
      + +
    +
    + + + Attributes +
      + N/A +
    +
    +

USF1000