######################################################################################## # # 95_Astro.pm # # Collection of various routines for astronomical data # Prof. Dr. Peter A. Henning # # Equations from "Practical Astronomy with your Calculator" by Peter Duffett-Smith # Program skeleton (with some errors) by Arnold Barmettler # http://lexikon.astronomie.info/java/sunmoon/ # # $Id$ # ######################################################################################## # # This programm is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the textfile GPL.txt and important notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ######################################################################################## package FHEM::Astro; use strict; use warnings; use POSIX; use utf8; use Encode; use FHEM::Meta; use GPUtils qw(GP_Import GP_Export); use Math::Trig; use Time::HiRes qw(gettimeofday); use Time::Local; use UConv; #use Data::Dumper; my $DEG = pi/180.0; my $RAD = 180./pi; my $deltaT = 65; # Correction time in s my %Astro; my %Date; #-- These we may set on request my %sets = ( "update" => "noArg", ); #-- These we may get on request my %gets = ( "json" => undef, "text" => undef, "version" => "noArg", ); #-- These we may configure my %attrs = ( "altitude" => undef, "disable" => "1,0", "horizon" => undef, "interval" => undef, "language" => "EN,DE,ES,FR,IT,NL,PL", "latitude" => undef, "lc_numeric" => "en_EN.UTF-8,de_DE.UTF-8,es_ES.UTF-8,fr_FR.UTF-8,it_IT.UTF-8,nl_NL.UTF-8,pl_PL.UTF-8", "lc_time" => "en_EN.UTF-8,de_DE.UTF-8,es_ES.UTF-8,fr_FR.UTF-8,it_IT.UTF-8,nl_NL.UTF-8,pl_PL.UTF-8", "longitude" => undef, "recomputeAt" => "multiple-strict,MoonRise,MoonSet,MoonTransit,NewDay,SunRise,SunSet,SunTransit,AstroTwilightEvening,AstroTwilightMorning,CivilTwilightEvening,CivilTwilightMorning,CustomTwilightEvening,CustomTwilightMorning", "timezone" => undef, ); my $json; my $tt; #-- Export variables to other programs our %transtable = ( EN => { "coord" => "Coordinates", "position" => "Position", "longitude" => "Longitude", "latitude" => "Latitude", "altitude" => "Height a.s.l.", "lonecl" => "Ecliptical longitude", "latecl" => "Ecliptical latitude", "ra" => "Right ascension", "dec" => "Declination", "az" => "Azimuth", "alt" => "Horizontal altitude", "age" => "Age", "rise" => "Rise", "set" => "Set", "transit" => "Transit", "distance" => "Distance", "diameter" => "Diameter", "toobs" => "to observer", "toce" => "to Earth center", "progress" => "Progress", "twilightcivil" => "Civil twilight", "twilightnautic" => "Nautical twilight", "twilightastro" => "Astronomical twilight", "twilightcustom" => "Custom twilight", "sign" => "Zodiac sign", "dst" => "daylight saving time", "nt" => "standard time", "switchtodst" => "Time change ahead to daylight saving time", "switchtont" => "Time change back to standard time", "hoursofsunlight" => "Hours of sunlight", "hoursofnight" => "Hours of night", "hoursofvisibility" => "Visibility", #-- "time" => "Time", "date" => "Date", "jdate" => "Julian date", "dayofyear" => "day of year", "days" => "days", "timezone" => "Time Zone", "lmst" => "Local Sidereal Time", #-- "season" => "Astronomical Season", "spring" => "Spring", "summer" => "Summer", "fall" => "Fall", "winter" => "Winter", #-- "aries" => "Ram", "taurus" => "Bull", "gemini" => "Twins", "cancer" => "Crab", "leo" => "Lion", "virgo" => "Maiden", "libra" => "Scales", "scorpio" => "Scorpion", "sagittarius" => "Archer", "capricorn" => "Goat", "aquarius" => "Water Bearer", "pisces" => "Fish", #-- "sun" => "Sun", #-- "moon" => "Moon", "phase" => "Phase", "newmoon" => "New Moon", "waxingcrescent" => "Waxing Crescent", "firstquarter" => "First Quarter", "waxingmoon" => "Waxing Moon", "fullmoon" => "Full Moon", "waningmoon" => "Waning Moon", "lastquarter" => "Last Quarter", "waningcrescent" => "Waning Crescent", }, DE => { "coord" => "Koordinaten", "position" => "Position", "longitude" => "Länge", "latitude" => "Breite", "altitude" => "Höhe ü. NHN", "lonecl" => "Eklipt. Länge", "latecl" => "Eklipt. Breite", "ra" => "Rektaszension", "dec" => "Deklination", "az" => "Azimut", "alt" => "Horizontwinkel", "age" => "Alter", "phase" => "Phase", "rise" => "Aufgang", "set" => "Untergang", "transit" => "Kulmination", "distance" => "Entfernung", "diameter" => "Durchmesser", "toobs" => "z. Beobachter", "toce" => "z. Erdmittelpunkt", "progress" => "Fortschritt", "twilightcivil" => "Bürgerliche Dämmerung", "twilightnautic" => "Nautische Dämmerung", "twilightastro" => "Astronomische Dämmerung", "twilightcustom" => "Konfigurierte Dämmerung", "sign" => "Tierkreiszeichen", "dst" => "Sommerzeit", "nt" => "Normalzeit", "switchtodst" => "Umstellung vorwärts auf Sommerzeit", "switchtont" => "Umstellung zurück auf Normalzeit", "hoursofsunlight" => "Tagesstunden", "hoursofnight" => "Nachtstunden", "hoursofvisibility" => "Sichtbarkeit", #-- "time" => "Zeit", "date" => "Datum", "jdate" => "Julianisches Datum", "dayofyear" => "Tag d. Jahres", "days" => "Tage", "timezone" => "Zeitzone", "lmst" => "Lokale Sternzeit", #-- "season" => "Astronomische Jahreszeit", "spring" => "Frühling", "summer" => "Sommer", "fall" => "Herbst", "winter" => "Winter", #-- "aries" => "Widder", "taurus" => "Stier", "gemini" => "Zwillinge", "cancer" => "Krebs", "leo" => "Löwe", "virgo" => "Jungfrau", "libra" => "Waage", "scorpio" => "Skorpion", "sagittarius" => "Schütze", "capricorn" => "Steinbock", "aquarius" => "Wassermann", "pisces" => "Fische", #-- "sun" => "Sonne", #-- "moon" => "Mond", "phase" => "Phase", "newmoon" => "Neumond", "waxingcrescent" => "Zunehmende Sichel", "firstquarter" => "Erstes Viertel", "waxingmoon" => "Zunehmender Mond", "fullmoon" => "Vollmond", "waningmoon" => "Abnehmender Mond", "lastquarter" => "Letztes Viertel", "waningcrescent" => "Abnehmende Sichel", }, ES => { "coord" => "Coordenadas", "position" => "Posición", "longitude" => "Longitud", "latitude" => "Latitud", "altitude" => "Altura sobre el mar", "lonecl" => "Longitud eclíptica", "latecl" => "Latitud eclíptica", "ra" => "Ascensión recta", "dec" => "Declinación", "az" => "Azimut", "alt" => "Ángulo horizonte", "age" => "Años", "rise" => "Salida", "set" => "Puesta", "transit" => "Culminación", "distance" => "Distancia", "diameter" => "Diámetro", "toobs" => "al observar", "toce" => "al centro de la tierra", "progress" => "Progreso", "twilightcivil" => "Crepúsculo civil", "twilightnautic" => "Crepúsculo náutico", "twilightastro" => "Crepúsculo astronómico", "twilightcustom" => "Crepúsculo personalizado", "sign" => "Signo del zodiaco", "dst" => "horario de verano", "nt" => "hora estándar", "switchtodst" => "Conversión adelante a la hora de verano", "switchtont" => "Conversión hacia atrás a la hora estándar", "hoursofsunlight" => "Horas de luz solar", "hoursofnight" => "Horas de la noche", "hoursofvisibility" => "Visibilidad", #-- "time" => "Tiempo", "date" => "Fecha", "jdate" => "Fecha de Julian", "dayofyear" => "Día del año", "days" => "Días", "timezone" => "Zona horaria", "lmst" => "Hora sideral local", #-- "season" => "Temporada Astronomica", "spring" => "Primavera", "summer" => "Verano", "fall" => "Otoño", "winter" => "Invierno", #-- "aries" => "Aries", "taurus" => "Tauro", "gemini" => "Geminis", "cancer" => "Cáncer", "leo" => "León", "virgo" => "Virgo", "libra" => "Libra", "scorpio" => "Escorpión", "sagittarius" => "Sagitario", "capricorn" => "Capricornio", "aquarius" => "Acuario", "pisces" => "Piscis", #-- "sun" => "Sol", #-- "moon" => "Luna", "phase" => "Fase", "newmoon" => "Luna nueva", "waxingcrescent" => "Luna creciente", "firstquarter" => "Primer cuarto", "waxingmoon" => "Luna creciente", "fullmoon" => "Luna llena", "waningmoon" => "Luna menguante", "lastquarter" => "Último cuarto", "waningcrescent" => "Creciente menguante", }, FR => { "coord" => "Coordonnées", "position" => "Position", "longitude" => "Longitude", "latitude" => "Latitude", "altitude" => "Hauteur au dessus de la mer", "lonecl" => "Longitude écliptique", "latecl" => "Latitude écliptique", "ra" => "Ascension droite", "dec" => "Déclinaison", "az" => "Azimut", "alt" => "Angle horizon", "age" => "Âge", "rise" => "Lever", "set" => "Coucher", "transit" => "Culmination", "distance" => "Distance", "diameter" => "Diamètre", "toobs" => "à l'observateur", "toce" => "au centre de la terre", "progress" => "Progrès", "twilightcivil" => "Crépuscule civil", "twilightnautic" => "Crépuscule nautique", "twilightastro" => "Crépuscule astronomique", "twilightcustom" => "Crépuscule personnalisé", "sign" => "Signe du zodiaque", "dst" => "heure d'été", "nt" => "heure normale", "switchtodst" => "Conversion en avant à l'heure d'été", "switchtont" => "Conversion de retour à l'heure normale", "hoursofsunlight" => "Heures de soleil", "hoursofnight" => "Heures de la nuit", "hoursofvisibility" => "Visibilité", #-- "time" => "Temps", "date" => "Date", "jdate" => "Date de Julien", "dayofyear" => "jour de l'année", "days" => "jours", "timezone" => "Fuseau horaire", "lmst" => "Heure sidérale locale", #-- "season" => "Saison Astronomique", "spring" => "Printemps", "summer" => "Été", "fall" => "Automne", "winter" => "Hiver", #-- "aries" => "bélier", "taurus" => "Taureau", "gemini" => "Gémeaux", "cancer" => "Cancer", "leo" => "Lion", "virgo" => "Jeune fille", "libra" => "Balance", "scorpio" => "Scorpion", "sagittarius" => "Sagittaire", "capricorn" => "Capricorne", "aquarius" => "Verseau", "pisces" => "Poissons", #-- "sun" => "Soleil", #-- "moon" => "Lune", "phase" => "Phase", "newmoon" => "Nouvelle lune", "waxingcrescent" => "Croissant croissant", "firstquarter" => "Premier quart", "waxingmoon" => "Lune croissante", "fullmoon" => "Pleine lune", "waningmoon" => "Lune décroissante", "lastquarter" => "Le dernier quart", "waningcrescent" => "Croissant décroissant", }, IT => { "coord" => "Coordinate", "position" => "Posizione", "longitude" => "Longitudine", "latitude" => "Latitudine", "altitude" => "Altezza sopra il mare", "lonecl" => "Longitudine ellittica", "latecl" => "Latitudine eclittica", "ra" => "Giusta ascensione", "dec" => "Declinazione", "az" => "Azimut", "alt" => "Angolo di orizzonte", "age" => "Età", "rise" => "Crescente", "set" => "Affondamento", "transit" => "Culmine", "distance" => "Distanza", "diameter" => "Diametro", "toobs" => "verso l'osservatore", "toce" => "verso centro della terra", "progress" => "Progresso", "twilightcivil" => "Crepuscolo civile", "twilightnautic" => "Crepuscolo nautico", "twilightastro" => "Crepuscolo astronomico", "twilightcustom" => "Crepuscolo personalizzato", "sign" => "Segno zodiacale", "dst" => "ora legale", "nt" => "ora standard", "switchtodst" => "Conversione in avanti a ora legale", "switchtont" => "Conversione a ritroso in ora standard", "hoursofsunlight" => "Ore di luce solare", "hoursofnight" => "Ore della notte", "hoursofvisibility" => "Visibilità", #-- "time" => "Tempo", "date" => "Data", "jdate" => "Data giuliana", "dayofyear" => "giorno dell'anno", "days" => "giorni", "timezone" => "Fuso orario", "lmst" => "Tempo siderale locale", #-- "season" => "Stagione Astronomica", "spring" => "Stagione primaverile", "summer" => "Estate", "fall" => "Autunno", "winter" => "Inverno", #-- "aries" => "Ariete", "taurus" => "Toro", "gemini" => "Gemelli", "cancer" => "Cancro", "leo" => "Leone", "virgo" => "Vergine", "libra" => "Libra", "scorpio" => "Scorpione", "sagittarius" => "Arciere", "capricorn" => "Capricorno", "aquarius" => "Acquario", "pisces" => "Pesci", #-- "sun" => "Sole", #-- "moon" => "Luna", "phase" => "Fase", "newmoon" => "Nuova luna", "waxingcrescent" => "Luna crescente", "firstquarter" => "Primo quarto", "waxingmoon" => "Luna crescente", "fullmoon" => "Luna piena", "waningmoon" => "Luna calante", "lastquarter" => "Ultimo quarto", "waningcrescent" => "Pericolo crescente", }, NL => { "coord" => "Coördinaten", "position" => "Positie", "longitude" => "Lengtegraad", "latitude" => "Breedtegraad", "altitude" => "Hoogte b. Zee", "lonecl" => "Eclipticale Lengtegraad", "latecl" => "Eclipticale Breedtegraad", "ra" => "Juiste klimming", "dec" => "Declinatie", "az" => "Azimuth", "alt" => "Horizon Angle", "age" => "Leeftijd", "rise" => "Opkomst", "set" => "Ondergang", "transit" => "Culminatie", "distance" => "Afstand", "diameter" => "Diameter", "toobs" => "voor de Waarnemer", "toce" => "naar het Middelpunt van de Aarde", "progress" => "Vooruitgang", "twilightcivil" => "Burgerlijke Schemering", "twilightnautic" => "Nautische Schemering", "twilightastro" => "Astronomische Schemering", "twilightcustom" => "Aangepaste Schemering", "sign" => "Sterrenbeeld", "dst" => "Zomertijd", "nt" => "Standaardtijd", "switchtodst" => "Voorwaartse omschakeling naar de zomertijd", "switchtont" => "Omzetting terug naar de normale tijd", "hoursofsunlight" => "Dagen Uur", "hoursofnight" => "Uren van de Nacht", "hoursofvisibility" => "Zichtbaarheid", #-- "time" => "Tijd", "date" => "Datum", "jdate" => "Juliaanse Datum", "dayofyear" => "Dag van het Jaar", "days" => "Dagen", "timezone" => "Tijdzone", "lmst" => "Lokale Sterrentijd", #-- "season" => "Astronomisch Seizoen", "spring" => "De lente", "summer" => "Zomer", "fall" => "Herfst", "winter" => "Winter", #-- "aries" => "Ram", "taurus" => "Stier", "gemini" => "Tweelingen", "cancer" => "Kanker", "leo" => "Leeuw", "virgo" => "Maagd", "libra" => "Weegschaal", "scorpio" => "Schorpioen", "sagittarius" => "Boogschutter", "capricorn" => "Steenbok", "aquarius" => "Waterman", "pisces" => "Vis", #-- "sun" => "Zon", #-- "moon" => "Maan", "phase" => "Fase", "newmoon" => "Nieuwe Maan", "waxingcrescent" => "Wassende halve Maan", "firstquarter" => "Eerste Kwartier", "waxingmoon" => "Wassende Maan", "fullmoon" => "Volle Maan", "waningmoon" => "Afnemende Maan", "lastquarter" => "Het laatste Kwartier", "waningcrescent" => "Afnemende halve Maan", }, PL => { "coord" => "Współrzędne", "position" => "Pozycja", "longitude" => "Długość", "latitude" => "Szerokość", "altitude" => "Wysokość nad morzem", "lonecl" => "Długość ekliptyczna", "latecl" => "Szerokość ekliptyczna", "ra" => "Rektascencja", "dec" => "Deklinacja", "az" => "Azymut", "alt" => "Kąt horyzont", "age" => "Wiek", "rise" => "Wschód", "set" => "Zachód", "transit" => "Kulminacja", "distance" => "Dystans", "diameter" => "Średnica", "toobs" => "w kierunku obserwatora", "toce" => "w kierunku środka ziemi", "progress" => "Postęp", "twilightcivil" => "Zmierzch cywilny", "twilightnautic" => "Zmierzch morski", "twilightastro" => "Zmierzch astronomiczny", "twilightcustom" => "Zmierzch niestandardowy", "sign" => "Znak zodiaku", "dst" => "czas letni", "nt" => "standardowy czas", "switchtodst" => "Przeliczenie napastnicy na czas letni", "switchtont" => "Przeliczanie wstecz do czasu normalnego", "hoursofsunlight" => "Godziny światła słonecznego", "hoursofnight" => "Godziny nocy", "hoursofvisibility" => "Widoczność", #-- "time" => "Czas", "date" => "Data", "jdate" => "Juliańska data", "dayofyear" => "dzień roku", "days" => "dni", "timezone" => "Strefa czasowa", "lmst" => "Lokalny czas gwiazdowy", #-- "season" => "Sezon Astronomiczny", "spring" => "Wiosna", "summer" => "Lato", "fall" => "Jesień", "winter" => "Zima", #-- "aries" => "Baran", "taurus" => "Byk", "gemini" => "Bliźnięta", "cancer" => "Rak", "leo" => "Lew", "virgo" => "Panna", "libra" => "Libra", "scorpio" => "Skorpion", "sagittarius" => "Strzelec", "capricorn" => "Koziorożec", "aquarius" => "Wodnik", "pisces" => "Ryby", #-- "sun" => "Słońce", #-- "moon" => "Księżyc", "phase" => "Faza", "newmoon" => "Nów", "waxingcrescent" => "Półksiężyc woskowy", "firstquarter" => "Pierwszym kwartale", "waxingmoon" => "Księżyc przybywający", "fullmoon" => "Pełnia księżyca", "waningmoon" => "Zmniejszający się księżyc", "lastquarter" => "Ostatni kwartał", "waningcrescent" => "Zwiększający się księżyc", } ); our @zodiac = ("aries","taurus","gemini","cancer","leo","virgo", "libra","scorpio","sagittarius","capricorn","aquarius","pisces"); our @phases = ("newmoon","waxingcrescent", "firstquarter", "waxingmoon", "fullmoon", "waningmoon", "lastquarter", "waningcrescent"); our %seasons = ( N => [ "winter", "spring", "summer", "fall" ], S => [ "summer", "fall", "winter", "spring" ] ); #-- Run before package compilation BEGIN { # Import from main context GP_Import( qw( attr AttrVal data Debug defs deviceEvents FmtDateTime FW_pO FW_RET FW_RETTYPE FW_webArgs GetType init_done InternalTimer IsDisabled Log Log3 maxNum minNum modules readingFnAttributes readingsBeginUpdate readingsBulkUpdateIfChanged readingsEndUpdate readingsSingleUpdate RemoveInternalTimer time_str2num toJSON ) ); # Export to main context GP_Export( qw( Get Initialize ) ); # Export to main context with different name no strict qw/refs/; *{'main::asunrise_rel'} = *{ 'FHEM::Astro::SUNRISE_EL_sunrise_rel' }; *{'main::asunset_rel'} = *{ 'FHEM::Astro::SUNRISE_EL_sunset_rel' }; *{'main::asunrise_abs'} = *{ 'FHEM::Astro::SUNRISE_EL_sunrise_abs' }; *{'main::asunset_abs'} = *{ 'FHEM::Astro::SUNRISE_EL_sunset_abs' }; *{'main::asunrise'} = *{ 'FHEM::Astro::SUNRISE_EL_sunrise' }; *{'main::asunset'} = *{ 'FHEM::Astro::SUNRISE_EL_sunset' }; *{'main::aisday'} = *{ 'FHEM::Astro::SUNRISE_EL_isday' }; *{'main::asunrise_abs_dat'} = *{ 'FHEM::Astro::SUNRISE_EL_sunrise_abs_dat' }; *{'main::asunset_abs_dat'} = *{ 'FHEM::Astro::SUNRISE_EL_sunset_abs_dat' }; use strict qw/refs/; } _LoadOptionalPackages(); sub SunRise($$$$$$$$); sub MoonRise($$$$$$$); sub SetTime(;$$$); sub Compute($;$); ######################################################################################################## # # Initialize # # Parameter hash = hash of device addressed # ######################################################################################################## sub Initialize ($) { my ($hash) = @_; $hash->{DefFn} = "FHEM::Astro::Define"; $hash->{SetFn} = "FHEM::Astro::Set"; $hash->{GetFn} = "FHEM::Astro::Get"; $hash->{UndefFn} = "FHEM::Astro::Undef"; $hash->{AttrFn} = "FHEM::Astro::Attr"; $hash->{NotifyFn} = "FHEM::Astro::Notify"; $hash->{AttrList} = join (" ", map { defined($attrs{$_}) ? "$_:$attrs{$_}" : $_ } sort keys %attrs ) ." " .$readingFnAttributes; $hash->{parseParams} = 1; $hash->{NotifyOrderPrefix} = '45-'; # we are a data provider $data{FWEXT}{"/Astro_moonwidget"}{FUNC} = "FHEM::Astro::Moonwidget"; $data{FWEXT}{"/Astro_moonwidget"}{FORKABLE} = 0; return FHEM::Meta::InitMod( __FILE__, $hash ); } ######################################################################################################## # # Define - Implements DefFn function # # Parameter hash = hash of device addressed, a = array of arguments, h = hash of parameters # ######################################################################################################## sub Define ($@) { my ($hash,$a,$h) = @_; my $name = shift @$a; my $type = shift @$a; my $global = shift @$a; return $@ unless ( FHEM::Meta::SetInternals($hash) ); use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); if ($global) { return "$type global device $modules{$type}{global}{NAME} is already defined" if ( defined( $modules{$type}{global} ) && $modules{$type}{global}{NAME} ne $name ); $modules{$type}{global} = $hash; $hash->{SCOPE} = 'global'; } $hash->{NOTIFYDEV} = "global"; $hash->{INTERVAL} = 3600; readingsSingleUpdate( $hash, "state", "Initialized", $init_done ); $modules{Astro}{defptr}{$name} = $hash; # for the very first definition, set some default attributes if ( $init_done && !defined( $hash->{OLDDEF} ) ) { $attr{$name}{icon} = 'telescope'; $attr{$name}{recomputeAt} = 'NewDay,SunRise,SunSet,AstroTwilightEvening,AstroTwilightMorning,CivilTwilightEvening,CivilTwilightMorning,CustomTwilightEvening,CustomTwilightMorning'; $hash->{RECOMPUTEAT} = $attr{$name}{recomputeAt}; } return undef; } ######################################################################################################## # # Undef - Implements Undef function # # Parameter hash = hash of device addressed, arg = array of arguments # ######################################################################################################## sub Undef ($$) { my ($hash,$arg) = @_; my $name = $hash->{NAME}; my $type = $hash->{TYPE}; RemoveInternalTimer($hash); delete $modules{$type}{defptr}{$name}; if ( defined( $modules{$type}{global} ) && $modules{$type}{global}{NAME} eq $name ) { delete $modules{$type}{global}; Debug "Yes"; } return undef; } ######################################################################################################## # # Notify - Implements Notify function # # Parameter hash = hash of device addressed, dev = hash of device that triggered notification # ######################################################################################################## sub Notify ($$) { my ($hash,$dev) = @_; my $name = $hash->{NAME}; my $TYPE = $hash->{TYPE}; my $devName = $dev->{NAME}; my $devType = GetType($devName); return "" if ( IsDisabled($name) ); if ( $devName eq "global" ) { my $events = deviceEvents( $dev, 1 ); return "" unless ($events); foreach my $event ( @{$events} ) { next unless ( defined($event) ); next if ( $event =~ m/^[A-Za-z\d_-]+:/ ); if ( $event =~ m/^INITIALIZED|REREADCFG$/ ) { if ( ( defined( $hash->{INTERVAL} ) && $hash->{INTERVAL} > 0 ) || defined( $hash->{RECOMPUTEAT} ) ) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+5,"FHEM::Astro::Update",$hash,0); } } elsif ( $event =~ m/^(DEFINED|MODIFIED)\s+([A-Za-z\d_-]+)$/ && $2 eq $name ) { if ( ( defined( $hash->{INTERVAL} ) && $hash->{INTERVAL} > 0 ) || defined( $hash->{RECOMPUTEAT} ) ) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+1,"FHEM::Astro::Update",$hash,0); } } # only process attribute events next unless ( $event =~ m/^((?:DELETE)?ATTR)\s+([A-Za-z\d._]+)\s+([A-Za-z\d_\.\-\/]+)(?:\s+(.*)\s*)?$/ ); my $cmd = $1; my $d = $2; my $attr = $3; my $val = $4; my $type = GetType($d); # filter attributes to be processed next unless ( $attr eq "altitude" || $attr eq "language" || $attr eq "latitude" || $attr eq "lc_numeric" || $attr eq "lc_time" || $attr eq "longitude" || $attr eq "timezone" ); # when global attributes were changed if ( $d eq "global" ) { RemoveInternalTimer($hash); InternalTimer( gettimeofday() + 1, "FHEM::Astro::Update", $hash, 0 ); } } } return undef; } ######################################################################################################## # # Attr - Implements Attr function # # Parameter do = action, name = name of device, key = attribute name, value = attribute value # ######################################################################################################## sub Attr(@) { my ($do,$name,$key,$value) = @_; my $hash = $defs{$name}; my $ret; if ( $do eq "set") { ARGUMENT_HANDLER: { #-- altitude modified at runtime $key eq "altitude" and do { #-- check value return "[Astro] $do $name attribute $key must be a float number >= 0 meters" unless($value =~ m/^(\d+(?:\.\d+)?)$/ && $1 >= 0.); }; #-- disable modified at runtime $key eq "disable" and do { #-- check value return "[Astro] $do $name attribute $key can only be 1 or 0" unless($value =~ m/^(1|0)$/); readingsSingleUpdate($hash,"state",$value?"inactive":"Initialized",$init_done); }; #-- horizon modified at runtime $key eq "horizon" and do { #-- check value return "[Astro] $do $name attribute $key must be a float number >= -45 and <= 45 degrees" unless($value =~ m/^(-?\d+(?:\.\d+)?)(?::(-?\d+(?:\.\d+)?))?$/ && $1 >= -45. && $1 <= 45. && (!$2 || $2 >= -45. && $2 <= 45.)); }; #-- interval modified at runtime $key eq "interval" and do { #-- check value return "[Astro] $do $name attribute $key must be >= 0 seconds" unless($value =~ m/^\d+$/); #-- update timer $hash->{INTERVAL} = $value; }; #-- latitude modified at runtime $key eq "latitude" and do { #-- check value return "[Astro] $do $name attribute $key must be float number >= -90 and <= 90 degrees" unless($value =~ m/^(-?\d+(?:\.\d+)?)$/ && $1 >= -90. && $1 <= 90.); }; #-- longitude modified at runtime $key eq "longitude" and do { #-- check value return "[Astro] $do $name attribute $key must be float number >= -180 and <= 180 degrees" unless($value =~ m/^(-?\d+(?:\.\d+)?)$/ && $1 >= -180. && $1 <= 180.); }; #-- recomputeAt modified at runtime $key eq "recomputeAt" and do { my @skel = split(',', $attrs{recomputeAt}); shift @skel; #-- check value 1/2 return "[Astro] $do $name attribute $key must be one or many of ".join(',', @skel) if(!$value || $value eq ""); #-- check value 2/2 my @vals = split(',', $value); foreach my $val (@vals) { return "[Astro] $do $name attribute value $val is invalid, must be one or many of ".join(',', @skel) unless(grep( m/^$val$/, @skel )); } $hash->{RECOMPUTEAT} = join(',', @vals); }; } } elsif ( $do eq "del") { readingsSingleUpdate($hash,"state","Initialized",$init_done) if ($key eq "disable"); $hash->{INTERVAL} = 3600 if ($key eq "interval"); delete $hash->{RECOMPUTEAT} if ($key eq "recomputeAt"); } if ( $init_done && exists( $attrs{$key} ) && ( $hash->{INTERVAL} > 0 || $hash->{RECOMPUTEAT} || $hash->{NEXTUPDATE} ) ) { RemoveInternalTimer($hash); InternalTimer( gettimeofday() + 2, "FHEM::Astro::Update", $hash, 0 ); } return $ret; } sub _mod($$) { my ($a,$b)=@_;if( $a =~ /\d*\.\d*/){return($a-floor($a/$b)*$b)}else{return undef}; } sub _mod2Pi($) { my ($x)=@_;$x = _mod($x, 2.*pi);return($x); } sub _round($$) { my ($x,$n)=@_; return int(10**$n*$x+0.5)/10**$n}; sub _tzoffset($) { my ($t) = @_; my $utc = mktime(gmtime($t)); #-- the following does not properly calculate dst my $local = mktime(localtime($t)); #-- this is the correction my $isdst = (localtime($t))[8]; #-- correction if($isdst == 1){ $local+=3600; } return (($local - $utc)/36); } ######################################################################################################## # # _LoadOptionalPackages - Load Perl packages that may not be installed # ######################################################################################################## sub _LoadOptionalPackages { # JSON preference order local $ENV{PERL_JSON_BACKEND} = 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); # try to use JSON::MaybeXS wrapper # for chance of better performance + open code eval { require JSON::MaybeXS; $json = JSON::MaybeXS->new; 1; }; if ($@) { $@ = undef; # try to use JSON wrapper # for chance of better performance eval { require JSON; $json = JSON->new; 1; }; if ($@) { $@ = undef; # In rare cases, Cpanel::JSON::XS may # be installed but JSON|JSON::MaybeXS not ... eval { require Cpanel::JSON::XS; $json = Cpanel::JSON::XS->new; 1; }; if ($@) { $@ = undef; # In rare cases, JSON::XS may # be installed but JSON not ... eval { require JSON::XS; $json = JSON::XS->new; 1; }; if ($@) { $@ = undef; # Fallback to built-in JSON which SHOULD # be available since 5.014 ... eval { require JSON::PP; $json = JSON::PP->new; 1; }; if ($@) { $@ = undef; # Last chance may be a backport eval { require JSON::backportPP; $json = JSON::backportPP->new; 1; }; $@ = undef if ($@); } } } } } if ($@) { $@ = undef; } else { $json->allow_nonref; $json->shrink; $json->utf8; } # only available for Perl versions after 2018-06-09 eval { import Time::Local 'timelocal_modern'; 1; }; eval { import Time::Local 'timegm_modern'; 1; }; } ######################################################################################################## # # subroutines for 99_SUNRISE_EL.pm compatibility layer # ######################################################################################################## sub SUNRISE_EL_sr($$$$$$) { my ( $rise, $seconds, $isrel, $daycheck, $min, $max ) = @_; SUNRISE_EL_sr_alt( time(), $rise, $isrel, $daycheck, 1, $main::defaultaltit, $seconds, $min, $max ); } sub SUNRISE_EL_sr_alt($$$$$$$$$) { my $nt=shift; my $rise=shift; my $isrel=shift; my $daycheck=shift; my $nextDay=shift; my $altit = defined($_[0]) ? $_[0] : ""; my $hasalt = 0; if(exists $main::alti{uc($altit)}) { $hasalt = 1; $altit=$main::alti{uc($altit)}; shift; } elsif($altit =~ /HORIZON=(([\-\+]*[0-9\.]+)(?::([\-\+]*[0-9\.]+))?)/i) { $hasalt = 1; $altit=$1; shift; } else { $altit=-6; #default } my($seconds, $min, $max)=@_; my $needrise = ($rise || $daycheck) ? 1 : 0; my $needset = (!$rise || $daycheck) ? 1 : 0; $seconds = 0 if(!$seconds); my $hash = defined( $modules{Astro}{global} ) ? $modules{Astro}{global} : (); my $name = defined( $hash->{NAME} ) ? $hash->{NAME} : ''; ############################ # If set in global, use longitude/latitude # from global, otherwise set Frankfurt/Germany as # default my $long = AttrVal( $name, "longitude", AttrVal( "global", "longitude", 10.0 ) ); my $lat = AttrVal( $name, "latitude", AttrVal( "global", "latitude", 50.0 ) ); $altit = AttrVal( $name, "horizon", AttrVal( "global", "horizon", -6. ) ) unless($hasalt); Log3 $name ne '' ? $name : undef, $name ne '' ? 4 : 5, "[FHEM::Astro::SUNRISE_EL] " . ( $name ne '' ? "$name: " : '' ) . "Compute sunrise/sunset for latitude $lat , longitude $long , horizon $altit at " . FmtDateTime($nt); #-- readjust timezone my $tz = AttrVal( $name, "timezone", AttrVal( "global", "timezone", undef ) ); local $ENV{TZ} = $tz if ($tz); eval { tzset() if ( exists &{'tzset'} ) }; #my $nt = time; my @lt = localtime($nt); my $gmtoff = main::_calctz($nt,@lt); # in hour my ($rt,$st) = _SUNRISE_EL_sr_alt($lat,$long,$altit,$needrise,$needset,$nt,$gmtoff); my $sst = ($rise ? $rt : $st) + ($seconds/3600); my $nh = $lt[2] + $lt[1]/60 + $lt[0]/3600; # Current hour since midnight if($daycheck) { if(defined($min) && defined($max)) { #Forum #43742 $min = main::hms2h($min); $max = main::hms2h($max); if($min < $max) { $rt = $min if($rt < $min); $st = $max if($st > $max); } else { $rt = $max if($rt > $max); $st = $min if($st < $min); } } return 1 if($rt <= $nh && $nh <= $st); return 0; } $sst = main::hms2h($min) if(defined($min) && (main::hms2h($min) > $sst)); $sst = main::hms2h($max) if(defined($max) && (main::hms2h($max) < $sst)); my $diff = 0; if (($data{AT_RECOMPUTE} || # compute it for tommorow int(($nh-$sst)*3600) >= 0) && $nextDay) { # if called a subsec earlier $nt += 86400; @lt = localtime($nt); my $ngmtoff = main::_calctz($nt,@lt); # in hour $diff = 24; ($rt,$st) = _SUNRISE_EL_sr_alt($lat,$long,$altit,$needrise,$needset,$nt,$ngmtoff); $sst = ($rise ? $rt : $st) + ($seconds/3600); $sst = main::hms2h($min) if(defined($min) && (main::hms2h($min) > $sst)); $sst = main::hms2h($max) if(defined($max) && (main::hms2h($max) < $sst)); } $sst += $diff if($isrel); $sst -= $nh if($isrel == 1); delete local $ENV{TZ}; tzset() if ( exists &{'tzset'} ); return main::h2hms_fmt($sst); } sub _SUNRISE_EL_sr_alt($$$$$$$) { my ( $lat, $long, $altit, $needrise, $needset, $nt, $offset ) = @_; my $hash = defined( $modules{Astro}{global} ) ? $modules{Astro}{global} : (); my $name = defined( $hash->{NAME} ) ? $hash->{NAME} : ''; my $horM = 0.0; my $horE = 0.0; if ( $altit =~ m/^([^:]+)(?::(.+))?$/ ) { $horM = $1; $horE = defined($2) ? $2 : $1; } my $tz = AttrVal( $name, "timezone", AttrVal( "global", "timezone", undef ) ); SetTime( $nt, $tz ); my $JD0 = Date2JD( $Date{day}, $Date{month}, $Date{year} ); my ( $suntransit, $sunrise, $sunset, $CivilTwilightMorning, $CivilTwilightEvening, $NauticTwilightMorning, $NauticTwilightEvening, $AstroTwilightMorning, $AstroTwilightEvening, $CustomTwilightMorning, $CustomTwilightEvening ) = SunRise( $JD0, $deltaT, $long * $DEG, $lat * $DEG, $Date{zonedelta}, $horM, $horE, 0 ); return ( $needrise ? $CustomTwilightMorning : undef ), ( $needset ? $CustomTwilightEvening : undef ); } sub SUNRISE_EL_sunrise_rel(@) { return SUNRISE_EL_sr_alt(time(),1,1,0,1,shift,shift,shift,shift); } sub SUNRISE_EL_sunset_rel (@) { return SUNRISE_EL_sr_alt(time(),0,1,0,1,shift,shift,shift,shift); } sub SUNRISE_EL_sunrise_abs(@) { return SUNRISE_EL_sr_alt(time(),1,0,0,0,shift,shift,shift,shift); } sub SUNRISE_EL_sunset_abs (@) { return SUNRISE_EL_sr_alt(time(),0,0,0,0,shift,shift,shift,shift); } sub SUNRISE_EL_sunrise (@) { return SUNRISE_EL_sr_alt(time(),1,2,0,1,shift,shift,shift,shift); } sub SUNRISE_EL_sunset (@) { return SUNRISE_EL_sr_alt(time(),0,2,0,1,shift,shift,shift,shift); } sub SUNRISE_EL_isday (@) { return SUNRISE_EL_sr_alt(time(),1,0,1,1,shift,shift,shift,shift); } sub SUNRISE_EL_sunrise_abs_dat(@) { return SUNRISE_EL_sr_alt(main::sr_noon(shift),1,0,0,0,shift,shift,shift,shift); } sub SUNRISE_EL_sunset_abs_dat (@) { return SUNRISE_EL_sr_alt(main::sr_noon(shift),0,0,0,0,shift,shift,shift,shift); } ######################################################################################################## # # wrapper for Perl versions before 2018-06-09 # ######################################################################################################## sub _timelocal_modern { return timelocal_modern(@_) if ( exists &{'timelocal_modern'} ); my @r = @_; $r[5] -= 1900; return timelocal(@r); } ######################################################################################################## # # time fragments into minutes, seconds # ######################################################################################################## sub HHMM($){ my ($hh) = @_; return("---") if (!defined($hh) || $hh !~ /^-?\d+/ || $hh==0) ; my $h = floor($hh); my $m = ($hh-$h)*60.; return sprintf("%02d:%02d",$h,$m); } sub HHMMSS($){ my ($hh) = @_; return("---") if (!defined($hh) || $hh !~ /^-?\d+/ || $hh==0) ; my $m = ($hh-floor($hh))*60.; my $s = ($m-floor($m))*60; my $h = floor($hh); return sprintf("%02d:%02d:%02d",$h,$m,$s); } ######################################################################################################## # # Date2JD - Calculate Julian date, adopted from Perl CPAN module Astro::Coord::ECI::Utils # ######################################################################################################## sub Date2JD { my @args = @_; unshift @args, 0 while @args < 6.; my ( $sec, $min, $hr, $day, $mon, $yr ) = @args; return undef if ( ( $yr < -4712. ) or ( $mon < 1. || $mon > 12. ) ); if ( $mon < 3. ) { --$yr; $mon += 12.; } my $JD_GREGORIAN = 2299160.5; my $A = floor( $yr / 100 ); my $B = 2 - $A + floor( $A / 4 ); my $jd = floor( 365.25 * ( $yr + 4716 ) ) + floor( 30.6001 * ( $mon + 1 ) ) + $day + $B - 1524.5 + ( ( ( $sec || 0 ) / 60 + ( $min || 0 ) ) / 60 + ( $hr || 0 ) ) / 24; $jd < $JD_GREGORIAN and $jd = floor( 365.25 * ( $yr + 4716 ) ) + floor( 30.6001 * ( $mon + 1 ) ) + $day - 1524.5 + ( ( ( $sec || 0 ) / 60 + ( $min || 0 ) ) / 60 + ( $hr || 0 ) ) / 24; return $jd; } ######################################################################################################## # # GMST - Julian Date to Greenwich Mean Sidereal Time # ######################################################################################################## sub GMST($){ my ($JD) = @_; my $UT = ($JD-0.5) - int($JD-0.5); $UT = $UT*24.; # UT in hours $JD = floor($JD-0.5)+0.5; # JD at 0 hours UT my $T = ($JD-2451545.0)/36525.0; my $T0 = 6.697374558 + $T*(2400.051336 + $T*0.000025862); return( _mod($T0+$UT*1.002737909,24.)); } ######################################################################################################## # # GMST2UT - Convert Greenweek mean sidereal time to UT # ######################################################################################################## sub GMST2UT($$){ my ($JD, $gmst) = @_; $JD = floor($JD-0.5)+0.5; # JD at 0 hours UT my $T = ($JD-2451545.0)/36525.0; my $T0 = _mod(6.697374558 + $T*(2400.051336 + $T*0.000025862), 24.); my $UT = 0.9972695663*(($gmst-$T0)); return($UT); } ######################################################################################################## # # GMST2LMST - Local Mean Sidereal Time, geographical longitude in radians, # East is positive # ######################################################################################################## sub GMST2LMST($$){ my ($gmst, $lon) = @_; my $lmst = _mod($gmst+$RAD*$lon/15, 24.); return( $lmst ); } ######################################################################################################## # # Ecl2Equ - Transform ecliptical coordinates (lon/lat) to equatorial coordinates (RA/dec) # ######################################################################################################## sub Ecl2Equ($$$){ my ($lon, $lat, $TDT) = @_; my $T = ($TDT-2451545.0)/36525.; # Epoch 2000 January 1.5 my $eps = (23.+(26+21.45/60.)/60. + $T*(-46.815 +$T*(-0.0006 + $T*0.00181) )/3600. )*$DEG; my $coseps = cos($eps); my $sineps = sin($eps); my $sinlon = sin($lon); my $ra = _mod2Pi(atan2( ($sinlon*$coseps-tan($lat)*$sineps), cos($lon) )); my $dec = asin( sin($lat)*$coseps + cos($lat)*$sineps*$sinlon ); return ($ra,$dec); } ######################################################################################################## # # Equ2Altaz - Transform equatorial coordinates (RA/Dec) to horizontal coordinates # (azimuth/altitude). Refraction is ignored # ######################################################################################################## sub Equ2Altaz($$$$$){ my ($ra, $dec, $TDT, $lat, $lmst)=@_; my $cosdec = cos($dec); my $sindec = sin($dec); my $lha = $lmst - $ra; my $coslha = cos($lha); my $sinlha = sin($lha); my $coslat = cos($lat); my $sinlat = sin($lat); my $N = -$cosdec * $sinlha; my $D = $sindec * $coslat - $cosdec * $coslha * $sinlat; my $az = _mod2Pi( atan2($N, $D) ); my $alt = asin( $sindec * $sinlat + $cosdec * $coslha * $coslat ); return ($az,$alt); } ######################################################################################################## # # GeoEqu2TopoEqu - Transform geocentric equatorial coordinates (RA/Dec) to # topocentric equatorial coordinates # ######################################################################################################## sub GeoEqu2TopoEqu($$$$$$$){ my ($ra, $dec, $distance, $lon, $lat, $radius, $lmst) = @_; my $cosdec = cos($dec); my $sindec = sin($dec); my $coslst = cos($lmst); my $sinlst = sin($lmst); my $coslat = cos($lat); # we should use geocentric latitude, not geodetic latitude my $sinlat = sin($lat); my $rho = $radius; # observer-geocenter in km my $x = $distance*$cosdec*cos($ra) - $rho*$coslat*$coslst; my $y = $distance*$cosdec*sin($ra) - $rho*$coslat*$sinlst; my $z = $distance*$sindec - $rho*$sinlat; my $distanceTopocentric = sqrt($x*$x + $y*$y + $z*$z); my $decTopocentric = asin($z/$distanceTopocentric); my $raTopocentric = _mod2Pi( atan2($y, $x) ); return ( ($distanceTopocentric,$decTopocentric,$raTopocentric) ); } ######################################################################################################## # # EquPolar2Cart - Calculate cartesian from polar coordinates # ######################################################################################################## sub EquPolar2Cart($$$){ my ($lon,$lat,$distance) = @_; my $rcd = cos($lat)*$distance; my $x = $rcd*cos($lon); my $y = $rcd*sin($lon); my $z = sin($lat)*$distance; return( ($x,$y,$z) ); } ######################################################################################################## # # Observer2EquCart - Calculate observers cartesian equatorial coordinates (x,y,z in celestial frame) # from geodetic coordinates (longitude, latitude, height above WGS84 ellipsoid) # Currently only used to calculate distance of a body from the observer # ######################################################################################################## sub Observer2EquCart($$$$){ my ($lon, $lat, $height, $gmst ) = @_; my $flat = 298.257223563; # WGS84 flatening of earth my $aearth = 6378.137; # GRS80/WGS84 semi major axis of earth ellipsoid #-- Calculate geocentric latitude from geodetic latitude my $co = cos ($lat); my $si = sin ($lat); $si = $si * $si; my $fl = 1.0 - 1.0 / $flat; $fl = $fl * $fl; my $u = 1.0 / sqrt ($co * $co + $fl * $si); my $a = $aearth * $u + $height; my $b = $aearth * $fl * $u + $height; my $radius = sqrt ($a * $a * $co *$co + $b *$b * $si); # geocentric distance from earth center my $y = acos ($a * $co / $radius); # geocentric latitude, rad my $x = $lon; # longitude stays the same my $z; if ($lat < 0.0) { $y = -$y; } # adjust sign #-- convert from geocentric polar to geocentric cartesian, with regard to Greenwich ($x,$y,$z) = EquPolar2Cart( $x, $y, $radius ); #-- rotate around earth's polar axis to align coordinate system from Greenwich to vernal equinox my $rotangle = $gmst/24*2*pi; # sideral time gmst given in hours. Convert to radians my $x2 = $x*cos($rotangle) - $y*sin($rotangle); my $y2 = $x*sin($rotangle) + $y*cos($rotangle); return( ($x2,$y2,$z,$radius) ); } ######################################################################################################## # # SunPosition - Calculate coordinates for Sun # Coordinates are accurate to about 10s (right ascension) # and a few minutes of arc (declination) # ######################################################################################################## sub SunPosition($$$){ my ($TDT, $observerlat, $lmst)=@_; my $D = $TDT-2447891.5; my $eg = 279.403303*$DEG; my $wg = 282.768422*$DEG; my $e = 0.016713; my $a = 149598500; # km #-- mean angular diameter of sun my $diameter0 = 0.533128*$DEG; my $MSun = 360*$DEG/365.242191*$D+$eg-$wg; my $nu = $MSun + 360.*$DEG/pi*$e*sin($MSun); my %sunCoor; $sunCoor{lon} = _mod2Pi($nu+$wg); $sunCoor{lat} = 0; $sunCoor{anomalyMean} = $MSun; my $distance = (1-$e*$e)/(1+$e*cos($nu)); # distance in astronomical units $sunCoor{diameter} = $diameter0/$distance; # angular diameter $sunCoor{distance} = $distance*$a; # distance in km $sunCoor{parallax} = 6378.137/$sunCoor{distance}; # horizontal parallax ($sunCoor{ra},$sunCoor{dec}) = Ecl2Equ($sunCoor{lon}, $sunCoor{lat}, $TDT); #-- calculate horizontal coordinates of sun, if geographic positions is given if (defined($observerlat) && defined($lmst) ) { ($sunCoor{az},$sunCoor{alt}) = Equ2Altaz($sunCoor{ra}, $sunCoor{dec}, $TDT, $observerlat, $lmst); } $sunCoor{sign} = floor($sunCoor{lon}*$RAD/30.); $sunCoor{sig} = $zodiac[$sunCoor{sign}]; return ( \%sunCoor ); } ######################################################################################################## # # MoonPosition - Calculate data and coordinates for the Moon # Coordinates are accurate to about 1/5 degree (in ecliptic coordinates) # ######################################################################################################## sub MoonPosition($$$$$$$){ my ($sunlon, $sunanomalyMean, $TDT, $observerlon, $observerlat, $observerradius, $lmst) = @_; my $D = $TDT-2447891.5; #-- Mean Moon orbit elements as of 1990.0 my $l0 = 318.351648*$DEG; my $P0 = 36.340410*$DEG; my $N0 = 318.510107*$DEG; my $i = 5.145396*$DEG; my $e = 0.054900; my $a = 384401; # km my $diameter0 = 0.5181*$DEG; # angular diameter of Moon at a distance my $parallax0 = 0.9507*$DEG; # parallax at distance a my $l = 13.1763966*$DEG*$D+$l0; my $MMoon = $l-0.1114041*$DEG*$D-$P0; # Moon's mean anomaly M my $N = $N0-0.0529539*$DEG*$D; # Moon's mean ascending node longitude my $C = $l-$sunlon; my $Ev = 1.2739*$DEG*sin(2*$C-$MMoon); my $Ae = 0.1858*$DEG*sin($sunanomalyMean); my $A3 = 0.37*$DEG*sin($sunanomalyMean); my $MMoon2 = $MMoon+$Ev-$Ae-$A3; # corrected Moon anomaly my $Ec = 6.2886*$DEG*sin($MMoon2); # equation of centre my $A4 = 0.214*$DEG*sin(2*$MMoon2); my $l2 = $l+$Ev+$Ec-$Ae+$A4; # corrected Moon's longitude my $V = 0.6583*$DEG*sin(2*($l2-$sunlon)); my $l3 = $l2+$V; # true orbital longitude; my $N2 = $N-0.16*$DEG*sin($sunanomalyMean); my %moonCoor; $moonCoor{lon} = _mod2Pi( $N2 + atan2( sin($l3-$N2)*cos($i), cos($l3-$N2) ) ); $moonCoor{lat} = asin( sin($l3-$N2)*sin($i) ); $moonCoor{orbitLon} = $l3; ($moonCoor{ra},$moonCoor{dec}) = Ecl2Equ($moonCoor{lon},$moonCoor{lat},$TDT); #-- relative distance to semi mayor axis of lunar oribt my $distance = (1-$e*$e) / (1+$e*cos($MMoon2+$Ec) ); $moonCoor{diameter} = $diameter0/$distance; # angular diameter in radians $moonCoor{parallax} = $parallax0/$distance; # horizontal parallax in radians $moonCoor{distance} = $distance*$a; # distance in km #-- Calculate horizontal coordinates of moon, if geographic positions is given #-- backup geocentric coordinates $moonCoor{raGeocentric} = $moonCoor{ra}; $moonCoor{decGeocentric} = $moonCoor{dec}; $moonCoor{distanceGeocentric} = $moonCoor{distance}; if (defined($observerlat) && defined($observerlon) && defined($lmst) ) { #-- transform geocentric coordinates into topocentric (==observer based) coordinates my ($distanceTopocentric,$decTopocentric,$raTopocentric) = GeoEqu2TopoEqu($moonCoor{ra}, $moonCoor{dec}, $moonCoor{distance}, $observerlon, $observerlat, $observerradius, $lmst); #-- now ra and dec are topocentric $moonCoor{ra} = $raTopocentric; $moonCoor{dec} = $decTopocentric; ($moonCoor{az},$moonCoor{alt})= Equ2Altaz($moonCoor{ra}, $moonCoor{dec}, $TDT, $observerlat, $lmst); } #-- Age of Moon in radians since New Moon (0) - Full Moon (pi) $moonCoor{age} = _mod2Pi($l3-$sunlon); $moonCoor{phasen} = 0.5*(1-cos($moonCoor{age})); # Moon phase numerical, 0-1 my $mainPhase = 1./29.53*360*$DEG; # show 'Newmoon, 'Quarter' for +/-1 day around the actual event my $p = _mod($moonCoor{age}, 90.*$DEG); if ($p < $mainPhase || $p > 90*$DEG-$mainPhase){ $p = 2*floor($moonCoor{age} / (90.*$DEG)+0.5); }else{ $p = 2*floor($moonCoor{age} / (90.*$DEG))+1; } $p = $p % 8; $moonCoor{phases} = $phases[$p]; $moonCoor{phasei} = $p; $moonCoor{sign} = floor($moonCoor{lon}*$RAD/30); $moonCoor{sig} = $zodiac[$moonCoor{sign}]; return ( \%moonCoor ); } ######################################################################################################## # # Refraction - Input true altitude in radians, Output: increase in altitude in degrees # ######################################################################################################## sub Refraction($){ my ($alt) = @_; my $altdeg = $alt*$RAD; if ($altdeg<-2 || $altdeg>=90){ return(0); } my $pressure = 1015; my $temperature = 10; if ($altdeg>15){ return( 0.00452*$pressure/( (273+$temperature)*tan($alt)) ); } my $y = $alt; my $D = 0.0; my $P = ($pressure-80.)/930.; my $Q = 0.0048*($temperature-10.); my $y0 = $y; my $D0 = $D; my $N; for (my $i=0; $i<3; $i++) { $N = $y+(7.31/($y+4.4)); $N = 1./tan($N*$DEG); $D = $N*$P/(60.+$Q*($N+39.)); $N = $y-$y0; $y0 = $D-$D0-$N; if (($N != 0.) && ($y0 != 0.)) { $N = $y-$N*($alt+$D-$y)/$y0; } else { $N = $alt+$D; } $y0 = $y; $D0 = $D; $y = $N; } return( $D ); } ######################################################################################################## # # GMSTRiseSet - returns Greenwich sidereal time (hours) of time of rise # and set of object with coordinates ra/dec # at geographic position lon/lat (all values in radians) # Correction for refraction and semi-diameter/parallax of body is taken care of in function RiseSet # h is used to calculate the twilights. It gives the required elevation of the disk center of the sun # ######################################################################################################## sub GMSTRiseSet($$$$$){ my ($ra, $dec, $lon, $lat, $h) = @_; $h = (defined($h)) ? $h : 0.0; # set default value #Log 1,"-------------------> Called GMSTRiseSet with $ra $dec $lon $lat $h"; # my $tagbogen = acos(-tan(lat)*tan(coor.dec)); // simple formula if twilight is not required my $tagbarg = (sin($h) - sin($lat)*sin($dec)) / (cos($lat)*cos($dec)); if( ($tagbarg > 1.000000) || ($tagbarg < -1.000000) ){ Log 5,"[FHEM::Astro::GMSTRiseSet] Parameters $ra $dec $lon $lat $h give complex angle"; return( ("---","---","---") ); }; my $tagbogen = acos($tagbarg); my $transit = $RAD/15*( +$ra-$lon); my $rise = 24.+$RAD/15*(-$tagbogen+$ra-$lon); # calculate GMST of rise of object my $set = $RAD/15*(+$tagbogen+$ra-$lon); # calculate GMST of set of object #--Using the modulo function mod, the day number goes missing. This may get a problem for the moon $transit = _mod($transit, 24); $rise = _mod($rise, 24); $set = _mod($set, 24); return( ($transit, $rise, $set) ); } ######################################################################################################## # # InterpolateGMST - Find GMST of rise/set of object from the two calculated # (start)points (day 1 and 2) and at midnight UT(0) # ######################################################################################################## sub InterpolateGMST($$$$){ my ($gmst0, $gmst1, $gmst2, $timefactor) = @_; return( ($timefactor*24.07*$gmst1- $gmst0*($gmst2-$gmst1)) / ($timefactor*24.07+$gmst1-$gmst2) ); } ######################################################################################################## # # RiseSet # // JD is the Julian Date of 0h UTC time (midnight) # ######################################################################################################## sub RiseSet($$$$$$$$$$$){ my ($jd0UT, $diameter, $parallax, $ra1, $dec1, $ra2, $dec2, $lon, $lat, $timeinterval, $altip) = @_; #--altitude of sun center: semi-diameter, horizontal parallax and (standard) refraction of 34' # true height of sun center for sunrise and set calculation. Is kept 0 for twilight (ie. altitude given): my $alt = (!defined($altip)) ? 0.5*$diameter-$parallax+34./60*$DEG : 0.; my $altitude = (!defined($altip)) ? 0. : $altip; my ($transit1, $rise1, $set1) = GMSTRiseSet($ra1, $dec1, $lon, $lat, $altitude); my ($transit2, $rise2, $set2) = GMSTRiseSet($ra2, $dec2, $lon, $lat, $altitude); #-- complex angle if( ($transit1 eq "---") || ($transit2 eq "---") ){ return( ("---","---","---") ); } #-- unwrap GMST in case we move across 24h -> 0h $transit2 += 24 if ($transit1 > $transit2 && abs($transit1-$transit2)>18); $rise2 += 24 if ($rise1 > $rise2 && abs($rise1-$rise2)>18); $set2 += 24 if ($set1 > $set2 && abs($set1-$set2)>18); my $T0 = GMST($jd0UT); # my $T02 = T0-zone*1.002738; // Greenwich sidereal time at 0h time zone (zone: hours) #-- Greenwich sidereal time for 0h at selected longitude my $T02 = $T0-$lon*$RAD/15*1.002738; $T02 +=24 if ($T02 < 0); if ($transit1 < $T02) { $transit1 += 24; $transit2 += 24; } if ($rise1 < $T02) { $rise1 += 24; $rise2 += 24; } if ($set1 < $T02) { $set1 += 24; $set2 += 24; } #-- Refraction and Parallax correction my $decMean = 0.5*($dec1+$dec2); my $psi = acos(sin($lat)/cos($decMean)); my $y = asin(sin($alt)/sin($psi)); my $dt = 240*$RAD*$y/cos($decMean)/3600; # time correction due to refraction, parallax my $transit = GMST2UT( $jd0UT, InterpolateGMST( $T0, $transit1, $transit2, $timeinterval) ); my $rise = GMST2UT( $jd0UT, InterpolateGMST( $T0, $rise1, $rise2, $timeinterval) - $dt ); my $set = GMST2UT( $jd0UT, InterpolateGMST( $T0, $set1, $set2, $timeinterval) + $dt ); return( ($transit,$rise,$set) ); } ######################################################################################################## # # SunRise - Find (local) time of sunrise and sunset, and twilights # JD is the Julian Date of 0h local time (midnight) # Accurate to about 1-2 minutes # recursive: 1 - calculate rise/set in UTC in a second run # recursive: 0 - find rise/set on the current local day. # This is set when doing the first call to this function # ######################################################################################################## sub SunRise($$$$$$$$){ my ($JD, $deltaT, $lon, $lat, $zone, $horM, $horE, $recursive) = @_; my $jd0UT = floor($JD-0.5)+0.5; # JD at 0 hours UT #-- calculations for noon my $sunCoor1 = SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef); #-- calculations for next day's UTC midnight my $sunCoor2 = SunPosition($jd0UT+1.+$deltaT/24./3600.,undef,undef); #-- rise/set time in UTC my ($transit,$rise,$set) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1,undef); if( $transit eq "---" ){ Log 3,"[FHEM::Astro::SunRise] no solution possible - maybe the sun never sets ?"; return( ($transit,$rise,$set) ); } my ($transittemp,$risetemp,$settemp); #-- check and adjust to have rise/set time on local calendar day if ( $recursive==0 ) { if ($zone>0) { #rise time was yesterday local time -> calculate rise time for next UTC day if ($rise >=24-$zone || $transit>=24-$zone || $set>=24-$zone) { ($transittemp,$risetemp,$settemp) = SunRise($JD+1, $deltaT, $lon, $lat, $zone, $horM, $horE, 1); $transit = $transittemp if ($transit>=24-$zone); $rise = $risetemp if ($rise>=24-$zone); $set = $settemp if ($set>=24-$zone); } }elsif ($zone<0) { #rise time was yesterday local time -> calculate rise time for previous UTC day if ($rise<-$zone || $transit<-zone || $set<-zone) { ($transittemp,$risetemp,$settemp) = SunRise($JD-1, $deltaT, $lon, $lat, $zone, $horM, $horE, 1); $transit = $transittemp if ($transit<-$zone); $rise = $risetemp if ($rise<-$zone); $set = $settemp if ($set <-$zone); } } $transit = _mod($transit+$zone, 24.); $rise = _mod($rise +$zone, 24.); $set = _mod($set +$zone, 24.); #-- Twilight calculation #-- civil twilight time in UTC. my $CivilTwilightMorning; my $CivilTwilightEvening; ($transittemp,$risetemp,$settemp) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -6.*$DEG); if( $transittemp eq "---" ){ Log 4,"[FHEM::Astro::SunRise] no solution possible for civil twilight - maybe the sun never sets below -6 degrees?"; $CivilTwilightMorning = "---"; $CivilTwilightEvening = "---"; }else{ $CivilTwilightMorning = _mod($risetemp +$zone, 24.); $CivilTwilightEvening = _mod($settemp +$zone, 24.); } #-- nautical twilight time in UTC. my $NauticTwilightMorning; my $NauticTwilightEvening; ($transittemp,$risetemp,$settemp) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -12.*$DEG); if( $transittemp eq "---" ){ Log 4,"[FHEM::Astro::SunRise] no solution possible for nautical twilight - maybe the sun never sets below -12 degrees?"; $NauticTwilightMorning = "---"; $NauticTwilightEvening = "---"; }else{ $NauticTwilightMorning = _mod($risetemp +$zone, 24.); $NauticTwilightEvening = _mod($settemp +$zone, 24.); } #-- astronomical twilight time in UTC. my $AstroTwilightMorning; my $AstroTwilightEvening; ($transittemp,$risetemp,$settemp) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, -18.*$DEG); if( $transittemp eq "---" ){ Log 4,"[FHEM::Astro::SunRise] no solution possible for astronomical twilight - maybe the sun never sets below -18 degrees?"; $AstroTwilightMorning = "---"; $AstroTwilightEvening = "---"; }else{ $AstroTwilightMorning = _mod($risetemp +$zone, 24.); $AstroTwilightEvening = _mod($settemp +$zone, 24.); } #-- custom twilight time in UTC my $CustomTwilightMorning; my $CustomTwilightEvening; ($transittemp,$risetemp,$settemp) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, $horM*$DEG); if( $transittemp eq "---" ){ Log 4,"[FHEM::Astro::SunRise] no solution possible for custom morning twilight - maybe the sun never sets below ".$horM." degrees?"; $CustomTwilightMorning = "---"; }else{ $CustomTwilightMorning = _mod($risetemp +$zone, 24.); } ($transittemp,$risetemp,$settemp) = RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, $horE*$DEG); if( $transittemp eq "---" ){ Log 4,"[FHEM::Astro::SunRise] no solution possible for custom evening twilight - maybe the sun never sets below ".$horE." degrees?"; $CustomTwilightEvening = "---"; }else{ $CustomTwilightEvening = _mod($settemp +$zone, 24.); } return( ($transit,$rise,$set,$CivilTwilightMorning,$CivilTwilightEvening, $NauticTwilightMorning,$NauticTwilightEvening,$AstroTwilightMorning,$AstroTwilightEvening,$CustomTwilightMorning,$CustomTwilightEvening) ); }else{ return( ($transit,$rise,$set) ); } } ######################################################################################################## # # MoonRise - Find local time of moonrise and moonset # JD is the Julian Date of 0h local time (midnight) # Accurate to about 5 minutes or better # recursive: 1 - calculate rise/set in UTC # recursive: 0 - find rise/set on the current local day (set could also be first) # returns '' for moonrise/set does not occur on selected day # ######################################################################################################## sub MoonRise($$$$$$$){ my ($JD, $deltaT, $lon, $lat, $radius, $zone, $recursive) = @_; my $timeinterval = 0.5; my $jd0UT = floor($JD-0.5)+0.5; # JD at 0 hours UT #-- calculations for noon my $sunCoor1 = SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef); my $moonCoor1 = MoonPosition($sunCoor1->{lon}, $sunCoor1->{anomalyMean}, $jd0UT+ $deltaT/24./3600.,undef,undef,undef,undef); #-- calculations for next day's midnight my $sunCoor2 = SunPosition($jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef); my $moonCoor2 = MoonPosition($sunCoor2->{lon}, $sunCoor2->{anomalyMean}, $jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef,undef,undef); # rise/set time in UTC, time zone corrected later. # Taking into account refraction, semi-diameter and parallax my ($transit,$rise,$set) = RiseSet($jd0UT, $moonCoor1->{diameter}, $moonCoor1->{parallax}, $moonCoor1->{ra}, $moonCoor1->{dec}, $moonCoor2->{ra}, $moonCoor2->{dec}, $lon, $lat, $timeinterval,undef); my ($transittemp,$risetemp,$settemp); my ($transitprev,$riseprev,$setprev); # check and adjust to have rise/set time on local calendar day if ( $recursive==0 ) { if ($zone>0) { # recursive call to MoonRise returns events in UTC ($transitprev,$riseprev,$setprev) = MoonRise($JD-1., $deltaT, $lon, $lat, $radius, $zone, 1); if ($transit >= 24.-$zone || $transit < -$zone) { # transit time is tomorrow local time if ($transitprev < 24.-$zone){ $transit = ""; # there is no moontransit today }else{ $transit = $transitprev; } } if ($rise >= 24.-$zone || $rise < -$zone) { # rise time is tomorrow local time if ($riseprev < 24.-$zone){ $rise = ""; # there is no moonrise today }else{ $rise = $riseprev; } } if ($set >= 24.-$zone || $set < -$zone) { # set time is tomorrow local time if ($setprev < 24.-$zone){ $set = ""; # there is no moonset today }else{ $set = $setprev; } } }elsif ($zone<0) { # rise/set time was tomorrow local time -> calculate rise time for previous UTC day if ($rise<-$zone || $set<-$zone || $transit<-$zone) { ($transittemp,$risetemp,$settemp) = MoonRise($JD+1., $deltaT, $lon, $lat, $radius, $zone, 1); if ($transit < -$zone){ if ($transittemp > -$zone){ $transit = ''; # there is no moontransit today }else{ $transit = $transittemp; } } if ($rise < -$zone) { if ($risetemp > -$zone){ $rise = ''; # there is no moonrise today }else{ $rise = $risetemp; } } if ($set < -$zone){ if ($settemp > -$zone){ $set = ''; # there is no moonset today }else{ $set = $settemp; } } } } #-- correct for time zone, if time is valid $transit = _mod($transit +$zone, 24.) if( $transit ne ""); $rise = _mod($rise +$zone, 24.) if ($rise ne ""); $set = _mod($set +$zone, 24.) if ($set ne ""); } return( ($transit,$rise,$set) ); } ######################################################################################################## # # Season - Find astronomical season # ######################################################################################################## sub Season($$;$) { my ( $JD0, $deltaT, $lat ) = @_; # season starts during the day so we need to # look for tomorrows quarter at noon my $sunCoor = SunPosition( $JD0 + 1. + $deltaT / 86400.0, undef, undef ); my $quarter = ceil( rad2deg( $sunCoor->{lon} ) / 90. ) % 4; return wantarray ? ( $quarter, $seasons{ $lat && $lat < 0 ? 'S' : 'N' }[$quarter] ) : $quarter; } ######################################################################################################## # # SetTime - update of the %Date hash for today # ######################################################################################################## sub SetTime (;$$$) { my ( $time, $tz, $lc_time ) = @_; #-- readjust locale my $old_lctime = setlocale(LC_TIME); setlocale(LC_TIME, $lc_time) if ($lc_time); use locale ':not_characters'; #-- readjust timezone local $ENV{TZ} = $tz if ($tz); tzset() if ( exists &{'tzset'} ); $time = gettimeofday() unless ( defined($time) ); my ( $sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst ) = localtime($time); $year += 1900; $month += 1; $Date{timestamp} = $time; $Date{timeday} = $hour + $min / 60. + $sec / 3600.; $Date{year} = $year; $Date{month} = $month; $Date{day} = $day; $Date{hour} = $hour; $Date{min} = $min; $Date{sec} = $sec; $Date{isdst} = $isdst; #-- broken on windows #$Date{zonedelta} = (strftime "%z", localtime($time))/100; $Date{zonedelta} = _tzoffset($time) / 100; #-- half broken in windows $Date{dayofyear} = 1 * strftime( "%j", localtime($time) ); $Date{wdayl} = strftime( "%A", localtime($time) ); $Date{wdays} = strftime( "%a", localtime($time) ); $Date{monthl} = strftime( "%B", localtime($time) ); $Date{months} = strftime( "%b", localtime($time) ); $Date{datetime} = strftime( "%c", localtime($time) ); $Date{week} = 1 * strftime( "%V", localtime($time) ); $Date{wday} = 1 * strftime( "%w", localtime($time) ); $Date{time} = strftime( "%X", localtime($time) ); $Date{date} = strftime( "%x", localtime($time) ); $Date{tz} = strftime( "%Z", localtime($time) ); delete $Date{tz} if (!$Date{tz} || $Date{tz} eq "" || $Date{tz} eq " "); delete local $ENV{TZ}; tzset() if ( exists &{'tzset'} ); setlocale(LC_TIME, ""); setlocale(LC_TIME, $old_lctime); no locale; return (undef); } ######################################################################################################## # # Compute - sequential calculation of properties # ######################################################################################################## sub Compute($;$){ my ($hash,$params) = @_; undef %Astro; my $name = $hash->{NAME}; SetTime() if (scalar keys %Date == 0); # fill %Date if it is still empty after restart to avoid warnings return undef if( !$init_done ); #-- readjust language my $lang = uc(AttrVal($name,"language",AttrVal("global","language","EN"))); if( defined($params->{"language"}) && exists($transtable{uc($params->{"language"})}) ){ $lang = uc($params->{"language"}); $tt = $transtable{$lang}; }elsif( exists($transtable{uc($lang)}) ){ $tt = $transtable{uc($lang)}; }else{ $tt = $transtable{EN}; } #-- readjust timezone my $tz = AttrVal($name,"timezone",AttrVal("global","timezone",undef)); $tz = $params->{"timezone"} if ( defined( $params->{"timezone"} ) ); local $ENV{TZ} = $tz if ($tz); tzset() if ( exists &{'tzset'} ); #-- geodetic latitude and longitude of observer on WGS84 if( defined($params->{"latitude"}) ){ $Astro{ObsLat} = $params->{"latitude"}; }elsif( defined($attr{$name}) && defined($attr{$name}{"latitude"}) ){ $Astro{ObsLat} = $attr{$name}{"latitude"}; }elsif( defined($attr{"global"}{"latitude"}) ){ $Astro{ObsLat} = $attr{"global"}{"latitude"}; }else{ $Astro{ObsLat} = 50.0; Log3 $name,3,"[Astro] No latitude attribute set in global device, using 50.0°"; } if( defined($params->{"longitude"}) ){ $Astro{ObsLon} = $params->{"longitude"}; }elsif( defined($attr{$name}) && defined($attr{$name}{"longitude"}) ){ $Astro{ObsLon} = $attr{$name}{"longitude"}; }elsif( defined($attr{"global"}{"longitude"}) ){ $Astro{ObsLon} = $attr{"global"}{"longitude"}; }else{ $Astro{ObsLon} = 10.0; Log3 $name,3,"[Astro] No longitude attribute set in global device, using 10.0°"; } #-- altitude of observer in meters above WGS84 ellipsoid if( defined($params->{"altitude"}) ){ $Astro{ObsAlt} = $params->{"altitude"}; }elsif( defined($attr{$name}) && defined($attr{$name}{"altitude"}) ){ $Astro{ObsAlt} = $attr{$name}{"altitude"}; }elsif( defined($attr{"global"}{"altitude"}) ){ $Astro{ObsAlt} = $attr{"global"}{"altitude"}; }else{ $Astro{ObsAlt} = 0.0; Log3 $name,3,"[Astro] No altitude attribute set in global device, using 0.0 m above sea level"; } #-- custom horizon of observer in degrees if( defined($params->{"horizon"}) && $params->{"horizon"} =~ m/^([^:]+)(?::(.+))?$/ ){ $Astro{ObsHorMorning} = $1; $Astro{ObsHorEvening} = defined($2) ? $2 : $1; }elsif( defined($attr{$name}) && defined($attr{$name}{"horizon"}) && $attr{$name}{"horizon"} =~ m/^([^:]+)(?::(.+))?$/ ){ $Astro{ObsHorMorning} = $1; $Astro{ObsHorEvening} = defined($2) ? $2 : $1; } else { $Astro{ObsHorMorning} = 0.0; $Astro{ObsHorEvening} = 0.0; Log3 $name,5,"[Astro] No horizon attribute defined, using 0.0° for morning and evening"; } #-- internal variables converted to Radians and km my $lat = $Astro{ObsLat}*$DEG; my $lon = $Astro{ObsLon}*$DEG; my $height = $Astro{ObsAlt} * 0.001; #if (eval(form.Year.value)<=1900 || eval(form.Year.value)>=2100 ) { # alert("Dies Script erlaubt nur Berechnungen"+ # return; #} my $JD0 = Date2JD( $Date{day}, $Date{month}, $Date{year} ); my $JD = $JD0 + ( $Date{hour} - $Date{zonedelta} + $Date{min}/60. + $Date{sec}/3600.)/24; my $TDT = $JD + $deltaT/86400.0; $Astro{".ObsJD"} = $JD; $Astro{ObsJD} = _round($JD,2); my $gmst = GMST($JD); my $lmst = GMST2LMST($gmst, $lon); $Astro{".ObsGMST"} = $gmst; $Astro{".ObsLMST"} = $lmst; $Astro{ObsGMST} = HHMMSS($gmst); $Astro{ObsLMST} = HHMMSS($lmst); #-- geocentric cartesian coordinates of observer my ($x,$y,$z,$radius) = Observer2EquCart($lon, $lat, $height, $gmst); #-- calculate data for the sun at given time my $sunCoor = SunPosition($TDT, $lat, $lmst*15.*$DEG); $Astro{SunLon} = _round($sunCoor->{lon}*$RAD,1); #$Astro{SunLat} = $sunCoor->{lat}*$RAD; $Astro{".SunRa"}= _round($sunCoor->{ra} *$RAD/15,1); $Astro{SunRa} = HHMM($Astro{".SunRa"}); $Astro{SunDec} = _round($sunCoor->{dec}*$RAD,1); $Astro{SunAz} = _round($sunCoor->{az} *$RAD,1); $Astro{SunAlt} = _round($sunCoor->{alt}*$RAD + Refraction($sunCoor->{alt}),1); # including refraction WARNUNG => *RAD ??? $Astro{SunSign} = $tt->{$sunCoor->{sig}}; $Astro{SunSignN}= $sunCoor->{sign}; $Astro{SunDiameter}=_round($sunCoor->{diameter}*$RAD*60,1); #angular diameter in arc seconds $Astro{SunDistance}=_round($sunCoor->{distance},0); #-- calculate distance from the observer (on the surface of earth) to the center of the sun my ($xs,$ys,$zs) = EquPolar2Cart($sunCoor->{ra}, $sunCoor->{dec}, $sunCoor->{distance}); $Astro{SunDistanceObserver} = _round(sqrt( ($xs-$x)**2 + ($ys-$y)**2 + ($zs-$z)**2 ),0); my ($suntransit,$sunrise,$sunset,$CivilTwilightMorning,$CivilTwilightEvening, $NauticTwilightMorning,$NauticTwilightEvening,$AstroTwilightMorning,$AstroTwilightEvening,$CustomTwilightMorning,$CustomTwilightEvening) = SunRise($JD0, $deltaT, $lon, $lat, $Date{zonedelta}, $Astro{ObsHorMorning}, $Astro{ObsHorEvening}, 0); $Astro{".SunTransit"} = $suntransit; $Astro{".SunRise"} = $sunrise; $Astro{".SunSet"} = $sunset; $Astro{".CivilTwilightMorning"} = $CivilTwilightMorning; $Astro{".CivilTwilightEvening"} = $CivilTwilightEvening; $Astro{".NauticTwilightMorning"} = $NauticTwilightMorning; $Astro{".NauticTwilightEvening"} = $NauticTwilightEvening; $Astro{".AstroTwilightMorning"} = $AstroTwilightMorning; $Astro{".AstroTwilightEvening"} = $AstroTwilightEvening; $Astro{".CustomTwilightMorning"} = $CustomTwilightMorning; $Astro{".CustomTwilightEvening"} = $CustomTwilightEvening; $Astro{SunTransit} = HHMM($suntransit); $Astro{SunRise} = HHMM($sunrise); $Astro{SunSet} = HHMM($sunset); $Astro{CivilTwilightMorning} = HHMM($CivilTwilightMorning); $Astro{CivilTwilightEvening} = HHMM($CivilTwilightEvening); $Astro{NauticTwilightMorning} = HHMM($NauticTwilightMorning); $Astro{NauticTwilightEvening} = HHMM($NauticTwilightEvening); $Astro{AstroTwilightMorning} = HHMM($AstroTwilightMorning); $Astro{AstroTwilightEvening} = HHMM($AstroTwilightEvening); $Astro{CustomTwilightMorning} = HHMM($CustomTwilightMorning); $Astro{CustomTwilightEvening} = HHMM($CustomTwilightEvening); #-- hours of day and night my $hoursofsunlight; my $hoursofnight; if ( (!defined($sunset) && !defined($sunrise)) || ($sunset !~ m/^\d+/ && $sunrise !~ m/^\d+/) ){ if ($Astro{SunAlt} > 0.) { $hoursofsunlight = 24.; $hoursofnight = 0.; } else { $hoursofsunlight = 0.; $hoursofnight = 24.; } } elsif (!defined($sunset) || $sunset !~ m/^\d+/) { $hoursofsunlight = 24. - $sunrise; $hoursofnight = 24. - $hoursofsunlight; } elsif (!defined($sunrise) || $sunrise !~ m/^\d+/) { $hoursofsunlight = 24. - $sunset; $hoursofnight = 24. - $hoursofsunlight; } else { my $ss = $sunset; $ss += 24. if ($sunrise > $sunset); $hoursofsunlight = $ss - $sunrise; $hoursofnight = 24. - $hoursofsunlight; } $Astro{".SunHrsVisible"} = $hoursofsunlight; $Astro{".SunHrsInvisible"} = $hoursofnight; $Astro{SunHrsVisible} = HHMM($hoursofsunlight); $Astro{SunHrsInvisible} = HHMM($hoursofnight); #-- calculate data for the moon at given time my $moonCoor = MoonPosition($sunCoor->{lon}, $sunCoor->{anomalyMean}, $TDT, $lon, $lat, $radius, $lmst*15.*$DEG); $Astro{MoonLon} = _round($moonCoor->{lon}*$RAD,1); $Astro{MoonLat} = _round($moonCoor->{lat}*$RAD,1); $Astro{".MoonRa"} = _round($moonCoor->{ra} *$RAD/15.,1); $Astro{MoonRa} = HHMM($Astro{".MoonRa"}); $Astro{MoonDec} = _round($moonCoor->{dec}*$RAD,1); $Astro{MoonAz} = _round($moonCoor->{az} *$RAD,1); $Astro{MoonAlt} = _round($moonCoor->{alt}*$RAD + Refraction($moonCoor->{alt}),1); # including refraction WARNUNG => *RAD ??? $Astro{MoonSign} = $tt->{$moonCoor->{sig}}; $Astro{MoonSignN} = $moonCoor->{sign}; $Astro{MoonDistance} = _round($moonCoor->{distance},0); $Astro{MoonDiameter} = _round($moonCoor->{diameter}*$RAD*60.,1); # angular diameter in arc seconds $Astro{MoonAge} = _round($moonCoor->{age}*$RAD,1); $Astro{MoonPhaseN} = _round($moonCoor->{phasen},2); $Astro{MoonPhaseI} = $moonCoor->{phasei}; $Astro{MoonPhaseS} = $tt->{$moonCoor->{phases}}; #-- calculate distance from the observer (on the surface of earth) to the center of the moon my ($xm,$ym,$zm) = EquPolar2Cart($moonCoor->{ra}, $moonCoor->{dec}, $moonCoor->{distance}); #Log 1," distance=".$moonCoor->{distance}." test=".sqrt( ($xm)**2 + ($ym)**2 + ($zm)**2 )." $xm $ym $zm"; #Log 1," distance=".$radius." test=".sqrt( ($x)**2 + ($y)**2 + ($z)**2 )." $x $y $z"; $Astro{".MoonDistanceObserver"} = sqrt( ($xm-$x)**2 + ($ym-$y)**2 + ($zm-$z)**2 ); $Astro{MoonDistanceObserver} = _round($Astro{".MoonDistanceObserver"},0); my ($moontransit,$moonrise,$moonset) = MoonRise($JD0, $deltaT, $lon, $lat, $radius, $Date{zonedelta}, 0); $Astro{".MoonTransit"} = $moontransit; $Astro{".MoonRise"} = $moonrise; $Astro{".MoonSet"} = $moonset; $Astro{MoonTransit} = HHMM($moontransit); $Astro{MoonRise} = HHMM($moonrise); $Astro{MoonSet} = HHMM($moonset); #-- moon visiblity my $moonvisible; my $mooninvisible; if ( (!defined($moonset) && !defined($moonrise)) || ($moonset !~ m/^\d+/ && $moonrise !~ m/^\d+/) ){ if ($Astro{MoonAlt} >= 0.) { $moonvisible = 24.; $mooninvisible = 0.; } else { $moonvisible = 0.; $mooninvisible = 24.; } } elsif (!defined($moonset) || $moonset !~ m/^\d+/) { $moonvisible = 24. - $moonrise; $mooninvisible = 24. - $moonvisible; } elsif (!defined($moonrise) || $moonrise !~ m/^\d+/) { $moonvisible = 24. - $moonset; $mooninvisible = 24. - $moonvisible; } else { my $ss = $moonset; $ss += 24. if ($moonrise > $moonset); $moonvisible = $ss - $moonrise; $mooninvisible = 24. - $moonvisible; } $Astro{".MoonHrsVisible"} = $moonvisible; $Astro{".MoonHrsInvisible"} = $mooninvisible; $Astro{MoonHrsVisible} = HHMM($moonvisible); $Astro{MoonHrsInvisible} = HHMM($mooninvisible); #-- fix date $Astro{ObsDate} = $Date{date}; $Astro{ObsTime} = $Date{time}; $Astro{ObsTimezone} = $Date{zonedelta}; $Astro{ObsTimezoneS} = $Date{tz} if ( $Date{tz} ); $Astro{ObsDayofyear} = $Date{dayofyear}; $Astro{ObsIsDST} = $Date{isdst}; $Astro{".timestamp"} = $Date{timestamp}; $Astro{".timeday"} = $Date{timeday}; $Astro{".year"} = $Date{year}; $Astro{".month"} = $Date{month}; $Astro{".day"} = $Date{day}; $Astro{".hour"} = $Date{hour}; $Astro{".min"} = $Date{min}; $Astro{".sec"} = $Date{sec}; $Astro{".wdayl"} = $Date{wdayl}; $Astro{".wdays"} = $Date{wdays}; $Astro{".monthl"} = $Date{monthl}; $Astro{".months"} = $Date{months}; $Astro{".datetime"} = $Date{datetime}; $Astro{".week"} = $Date{week}; $Astro{".wday"} = $Date{wday}; #-- check astro season my ($seasonn, $season) = Season($JD0, $deltaT, $Astro{ObsLat}); $Astro{ObsSeason} = $tt->{$season}; $Astro{ObsSeasonN} = $seasonn; delete local $ENV{TZ}; tzset() if ( exists &{'tzset'} ); return( undef ); }; ######################################################################################################## # # Moonwidget - SVG picture of the moon # # Parameter arg = argument array # ######################################################################################################## sub Moonwidget($){ my ($arg) = @_; my $name = $FW_webArgs{name}; $name =~ s/'//g if ($name); my $hash = $name && $name ne "" && defined($defs{$name}) ? $defs{$name} : (); my $mooncolor = 'rgb(255,220,100)'; my $moonshadow = 'rgb(70,70,100)'; $mooncolor = $FW_webArgs{mooncolor} if ($FW_webArgs{mooncolor} ); $moonshadow = $FW_webArgs{moonshadow} if ($FW_webArgs{moonshadow} ); my @size = split('x', ($FW_webArgs{size} ? $FW_webArgs{size} : '400x400')); $FW_RETTYPE = "image/svg+xml"; $FW_RET=""; FW_pO ''; my $ma = Get($hash,("","text","MoonAge")); my $mb = Get($hash,("","text","MoonPhaseS")); my ($radius,$axis,$dir,$start,$middle); $radius = 250; $axis = sin(($ma+90)*$DEG)*$radius; $axis = -$axis if ($axis < 0); if( (0.0 <= $ma && $ma <= 90) || (270.0 < $ma && $ma <= 360.0) ){ $dir = 1; }else{ $dir = 0; } if( 0.0 < $ma && $ma <= 180 ){ $start = $radius; $middle = -$radius; }else{ $start = -$radius; $middle = $radius; } FW_pO ''; FW_pO ''; FW_pO ''; FW_pO ''; #FW_pO ''.$mb.''; FW_pO ''; return ($FW_RETTYPE, $FW_RET); } ######################################################################################################## # # Update - Update readings # # Parameter hash = hash of the bus master # ######################################################################################################## sub Update($@) { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash); delete $hash->{NEXTUPDATE}; return undef if (IsDisabled($name)); my $tz = AttrVal( $name, "timezone", AttrVal( "global", "timezone", undef ) ); my $lang = AttrVal( $name, "language", AttrVal( "global", "language", undef ) ); my $lc_time = AttrVal( $name, "lc_time", AttrVal( "global", "lc_time", ( $lang ? lc($lang) . "_" . uc($lang) . ".UTF-8" : undef ) ) ); my $now = gettimeofday(); # conserve timestamp before recomputing SetTime(undef, $tz, $lc_time); Compute($hash); my @next; #-- add regular update interval time push @next, $now + $hash->{INTERVAL} if ( defined( $hash->{INTERVAL} ) && $hash->{INTERVAL} > 0 ); #-- add event times foreach my $comp ( defined( $hash->{RECOMPUTEAT} ) ? split( ',', $hash->{RECOMPUTEAT} ) : () ) { if ( $comp eq 'NewDay' ) { push @next, _timelocal_modern( 0, 0, 0, (localtime($now + 86400.))[3,4], (localtime($now + 86400.))[5]+1900. ); next; } my $k = ".$comp"; next unless ( defined( $Astro{$k} ) && $Astro{$k} =~ /^\d+(?:\.\d+)?$/ ); my $t = _timelocal_modern( 0, 0, 0, (localtime($now))[3,4], (localtime($now))[5]+1900. ) + $Astro{$k} * 3600.; $t += 86400. if ( $t < $now ); # that is for tomorrow push @next, $t; } #-- set timer for next update if (@next) { my $n = minNum( $next[0], @next ); $hash->{NEXTUPDATE} = FmtDateTime($n); InternalTimer( $n, "FHEM::Astro::Update", $hash, 1 ); } readingsBeginUpdate($hash); foreach my $key (keys %Astro){ readingsBulkUpdateIfChanged($hash,$key,encode_utf8($Astro{$key})); } readingsEndUpdate($hash,1); readingsSingleUpdate($hash,"state","Updated",1); } ######################################################################################################## # # FormatReading - Convert a reading into text format # # Parameter r = reading name, h = parameter hash, locale = locale string, val = optional value # ######################################################################################################## sub FormatReading($$;$$) { my ( $r, $h, $lc_numeric, $val ) = @_; my $ret; $val = $Astro{$r} unless ( defined($val) ); my $f = "%s"; #-- number formatting $f = "%2.1f" if ( $r eq "MoonAge" ); $f = "%2.1f" if ( $r eq "MoonAlt" ); $f = "%2.1f" if ( $r eq "MoonAz" ); $f = "%2.1f" if ( $r eq "MoonDec" ); $f = "%2.1f" if ( $r eq "MoonDiameter" ); $f = "%.0f" if ( $r eq "MoonDistance" ); $f = "%.0f" if ( $r eq "MoonDistanceObserver" ); $f = "%2.1f" if ( $r eq "MoonLat" ); $f = "%2.1f" if ( $r eq "MoonLon" ); $f = "%1.2f" if ( $r eq "MoonPhaseN" ); $f = "%.0f" if ( $r eq "ObsAlt" ); $f = "%d" if ( $r eq "ObsDayofyear" ); $f = "%2.1f" if ( $r eq "ObsHorEvening" ); $f = "%2.1f" if ( $r eq "ObsHorMorning" ); $f = "%.2f" if ( $r eq "ObsJD" ); $f = "%.5f" if ( $r eq "ObsLat" ); $f = "%.5f" if ( $r eq "ObsLon" ); $f = "%2d" if ( $r eq "ObsTimezone" ); $f = "%2.1f" if ( $r eq "SunAlt" ); $f = "%2.1f" if ( $r eq "SunAz" ); $f = "%2.1f" if ( $r eq "SunDec" ); $f = "%2.1f" if ( $r eq "SunDiameter" ); $f = "%.0f" if ( $r eq "SunDistance" ); $f = "%.0f" if ( $r eq "SunDistanceObserver" ); $f = "%2.1f" if ( $r eq "SunLon" ); $ret = $val ne "" ? sprintf( $f, $val ) : ""; $ret = UConv::decimal_mark( $ret, $lc_numeric ) unless ( $h && ref($h) && defined( $h->{html} ) && $h->{html} eq "0" ); $ret = ( $val == 1. ? $tt->{"dst"} : $tt->{"nt"} ) if ( $r eq "ObsIsDST" ); if ( $h && ref($h) && ( !$h->{html} || $h->{html} ne "0" ) ) { #-- add unit if desired if ( $h->{unit} || ( $h->{long} && ( !defined( $h->{unit} ) || $h->{unit} ne "0" ) ) ) { $ret .= "°" if ( $r eq "MoonAge" ); $ret .= "°" if ( $r eq "MoonAlt" ); $ret .= "°" if ( $r eq "MoonAz" ); $ret .= "°" if ( $r eq "MoonDec" ); $ret .= "′" if ( $r eq "MoonDiameter" ); $ret .= chr(0x00A0) . "km" if ( $r eq "MoonDistance" ); $ret .= chr(0x00A0) . "km" if ( $r eq "MoonDistanceObserver" ); $ret .= chr(0x00A0) . "h" if ( $r eq "MoonHrsInvisible" ); $ret .= chr(0x00A0) . "h" if ( $r eq "MoonHrsVisible" ); $ret .= "°" if ( $r eq "MoonLat" ); $ret .= "°" if ( $r eq "MoonLon" ); $ret .= chr(0x00A0) . "h" if ( $r eq "MoonRa" ); $ret .= chr(0x00A0) . "m" if ( $r eq "ObsAlt" ); $ret .= "." if ( $r eq "ObsDayofyear" ); $ret .= "°" if ( $r eq "ObsHorEvening" ); $ret .= "°" if ( $r eq "ObsHorMorning" ); $ret .= chr(0x00A0) . $tt->{"days"} if ( $r eq "ObsJD" ); $ret .= "°" if ( $r eq "ObsLat" ); $ret .= "°" if ( $r eq "ObsLon" ); $ret .= "°" if ( $r eq "SunAlt" ); $ret .= "°" if ( $r eq "SunAz" ); $ret .= "°" if ( $r eq "SunDec" ); $ret .= "′" if ( $r eq "SunDiameter" ); $ret .= chr(0x00A0) . "km" if ( $r eq "SunDistance" ); $ret .= chr(0x00A0) . "km" if ( $r eq "SunDistanceObserver" ); $ret .= chr(0x00A0) . "h" if ( $r eq "SunHrsInvisible" ); $ret .= chr(0x00A0) . "h" if ( $r eq "SunHrsVisible" ); $ret .= "°" if ( $r eq "SunLon" ); $ret .= chr(0x00A0) . "h" if ( $r eq "SunRa" ); } #-- add text if desired if ( $h->{long} ) { my $sep = " "; $sep = ": " if ( $h->{long} > 1. && ( $h->{long} < 4. || $r !~ /^Sun|Moon/ ) ); $sep = "" if ($ret eq ""); $ret = $tt->{"twilightastro"} . $sep . $ret if ( $r eq "AstroTwilightEvening" ); $ret = $tt->{"twilightastro"} . $sep . $ret if ( $r eq "AstroTwilightMorning" ); $ret = $tt->{"twilightcivil"} . $sep . $ret if ( $r eq "CivilTwilightEvening" ); $ret = $tt->{"twilightcivil"} . $sep . $ret if ( $r eq "CivilTwilightMorning" ); $ret = $tt->{"twilightcustom"} . $sep . $ret if ( $r eq "CustomTwilightEvening" ); $ret = $tt->{"twilightcustom"} . $sep . $ret if ( $r eq "CustomTwilightMorning" ); $ret = $tt->{"age"} . $sep . $ret if ( $r eq "MoonAge" ); $ret = $tt->{"alt"} . $sep . $ret if ( $r eq "MoonAlt" ); $ret = $tt->{"az"} . $sep . $ret if ( $r eq "MoonAz" ); $ret = $tt->{"dec"} . $sep . $ret if ( $r eq "MoonDec" ); $ret = $tt->{"diameter"} . $sep . $ret if ( $r eq "MoonDiameter" ); $ret = $ret . $sep . $tt->{"toce"} if ( $r eq "MoonDistance" ); $ret = $ret . $sep . $tt->{"toobs"} if ( $r eq "MoonDistanceObserver" ); $ret = $tt->{"hoursofvisibility"} . $sep . $ret if ( $r eq "MoonHrsVisible" ); $ret = $tt->{"latecl"} . $sep . $ret if ( $r eq "MoonLat" ); $ret = $tt->{"lonecl"} . $sep . $ret if ( $r eq "MoonLon" ); $ret = $tt->{"phase"} . $sep . $ret if ( $r eq "MoonPhaseN" ); $ret = $tt->{"phase"} . $sep . $ret if ( $r eq "MoonPhaseS" ); $ret = $tt->{"ra"} . $sep . $ret if ( $r eq "MoonRa" ); $ret = $tt->{"rise"} . $sep . $ret if ( $r eq "MoonRise" ); $ret = $tt->{"set"} . $sep . $ret if ( $r eq "MoonSet" ); $ret = $tt->{"sign"} . $sep . $ret if ( $r eq "MoonSign" ); $ret = $tt->{"transit"} . $sep . $ret if ( $r eq "MoonTransit" ); $ret = $tt->{"twilightnautic"} . $sep . $ret if ( $r eq "NauticTwilightEvening" ); $ret = $tt->{"twilightnautic"} . $sep . $ret if ( $r eq "NauticTwilightMorning" ); $ret = $ret . $sep . $tt->{"altitude"} if ( $r eq "ObsAlt" ); $ret = $tt->{"date"} . $sep . $ret if ( $r eq "ObsDate" ); $ret = $ret . $sep . $tt->{"dayofyear"} if ( $r eq "ObsDayofyear" ); $ret = $tt->{"alt"} . $sep . $ret if ( $r eq "ObsHorEvening" ); $ret = $tt->{"alt"} . $sep . $ret if ( $r eq "ObsHorMorning" ); $ret = ( $val == 1. ? $tt->{"switchtodst"} : $tt->{"switchtont"} ) if ( $r eq "ObsIsDST" ); $ret = $tt->{"jdate"} . $sep . $ret if ( $r eq "ObsJD" ); $ret = $tt->{"lmst"} . $sep . $ret if ( $r eq "ObsLMST" ); $ret = $ret . $sep . $tt->{"latitude"} if ( $r eq "ObsLat" ); $ret = $ret . $sep . $tt->{"longitude"} if ( $r eq "ObsLon" ); $ret = $tt->{"season"} . $sep . $ret if ( $r eq "ObsSeason" ); $ret = $tt->{"time"} . $sep . $ret if ( $r eq "ObsTime" ); $ret = $tt->{"timezone"} . $sep . $ret if ( $r eq "ObsTimezone" ); $ret = $tt->{"alt"} . $sep . $ret if ( $r eq "SunAlt" ); $ret = $tt->{"az"} . $sep . $ret if ( $r eq "SunAz" ); $ret = $tt->{"dec"} . $sep . $ret if ( $r eq "SunDec" ); $ret = $tt->{"diameter"} . $sep . $ret if ( $r eq "SunDiameter" ); $ret = $ret . $sep . $tt->{"toce"} if ( $r eq "SunDistance" ); $ret = $ret . $sep . $tt->{"toobs"} if ( $r eq "SunDistanceObserver" ); $ret = $tt->{"hoursofnight"} . $sep . $ret if ( $r eq "SunHrsInvisible" ); $ret = $tt->{"hoursofsunlight"} . $sep . $ret if ( $r eq "SunHrsVisible" ); $ret = $tt->{"lonecl"} . $sep . $ret if ( $r eq "SunLon" ); $ret = $tt->{"ra"} . $sep . $ret if ( $r eq "SunRa" ); $ret = $tt->{"rise"} . $sep . $ret if ( $r eq "SunRise" ); $ret = $tt->{"set"} . $sep . $ret if ( $r eq "SunSet" ); $ret = $tt->{"sign"} . $sep . $ret if ( $r eq "SunSign" ); $ret = $tt->{"transit"} . $sep . $ret if ( $r eq "SunTransit" ); #-- add prefix for Sun/Moon if desired if ( $h->{long} > 2. && $r =~ /^Sun|Moon/ ) { $ret = " " . $ret; #-- add a separator after prefix if desired $ret = ":" . $ret if ( $h->{long} > 3. ); $ret = $tt->{"sun"} . $ret if ( $r =~ /^Sun/ ); $ret = $tt->{"moon"} . $ret if ( $r =~ /^Moon/ ); } } } return encode_utf8($ret); } ######################################################################################################## # # Set - Implements SetFn function # # Parameter hash = hash of device addressed, a = array of arguments, h = hash of parameters # ######################################################################################################## sub Set($@) { my ($hash,$a,$h) = @_; my $name = shift @$a; if ( $a->[0] eq "update" ) { return "[FHEM::Astro::Set] $name is disabled" if ( IsDisabled($name) ); RemoveInternalTimer($hash); InternalTimer( gettimeofday() + 1, "FHEM::Astro::Update", $hash, 1 ); } else { return "[FHEM::Astro::Set] $name with unknown argument $a->[0], choose one of " . join( " ", map { defined( $sets{$_} ) ? "$_:$sets{$_}" : $_ } sort keys %sets ); } return ""; } ######################################################################################################## # # Get - Implements GetFn function # # Parameter hash = hash of the bus master a = argument array # ######################################################################################################## sub Get($@) { my ($hash,$a,$h,@a) = @_; my $name = "#APIcall"; my $type = "dummy"; #-- backwards compatibility for non-parseParams requests if (!ref($a)) { $hash = exists($defs{$hash}) ? $defs{$hash} : () if ($hash && !ref($hash)); unshift @a, $h; $h = undef; $type = $a; $a = \@a; } else { $type = shift @$a; } if (defined($hash->{NAME})) { $name = $hash->{NAME}; } else { $hash->{NAME} = $name; } my $wantsreading = 0; my $dayOffset = 0; my $now = gettimeofday(); my $html = defined( $hash->{CL} ) && $hash->{CL}{TYPE} eq "FHEMWEB" ? 1 : undef; my $tz = AttrVal( $name, "timezone", AttrVal( "global", "timezone", undef ) ); my $lang = AttrVal( $name, "language", AttrVal( "global", "language", undef ) ); my $lc_numeric = AttrVal( $name, "lc_numeric", AttrVal( "global", "lc_numeric", ( $lang ? lc($lang) . "_" . uc($lang) . ".UTF-8" : undef ) ) ); my $lc_time = AttrVal( $name, "lc_time", AttrVal( "global", "lc_time", ( $lang ? lc($lang) . "_" . uc($lang) . ".UTF-8" : undef ) ) ); if ( $h && ref($h) ) { $html = $h->{html} if ( defined( $h->{html} ) ); $tz = $h->{timezone} if ( defined( $h->{timezone} ) ); $lc_numeric = $h->{lc_numeric} if ( defined( $h->{lc_numeric} ) ); $lc_numeric = lc( $h->{language} ) . "_" . uc( $h->{language} ) . ".UTF-8" if ( !$lc_numeric && defined( $h->{language} ) ); $lc_time = $h->{lc_time} if ( defined( $h->{lc_time} ) ); $lc_time = lc( $h->{language} ) . "_" . uc( $h->{language} ) . ".UTF-8" if ( !$lc_time && defined( $h->{language} ) ); } #-- fill %Astro if it is still empty after restart to avoid warnings Compute($hash, $h) if (scalar keys %Astro == 0); #-- second parameter may be one or many readings my @readings; if( (int(@$a)>1) ) { @readings = split(',', $a->[1]); foreach (@readings) { if(exists($Astro{$_})) { $wantsreading = 1; last; } } } #-- last parameter may be indicating day offset if( (int(@$a)>4+$wantsreading && $a->[4+$wantsreading] =~ /^\+?([-+]\d+|yesterday|tomorrow)$/i) || (int(@$a)>3+$wantsreading && $a->[3+$wantsreading] =~ /^\+?([-+]\d+|yesterday|tomorrow)$/i) || (int(@$a)>2+$wantsreading && $a->[2+$wantsreading] =~ /^\+?([-+]\d+|yesterday|tomorrow)$/i) || (int(@$a)>1+$wantsreading && $a->[1+$wantsreading] =~ /^\+?([-+]\d+|yesterday|tomorrow)$/i) ){ $dayOffset = $1; pop @$a; $dayOffset = -1 if (lc($dayOffset) eq "yesterday"); $dayOffset = 1 if (lc($dayOffset) eq "tomorrow"); } if( int(@$a) > (1+$wantsreading) ) { my $str = (int(@$a) == (3+$wantsreading)) ? $a->[1+$wantsreading]." ".$a->[2+$wantsreading] : $a->[1+$wantsreading]; if( $str =~ /^(\d{2}):(\d{2})(?::(\d{2}))?$|^(?:(?:(\d{4})-)?(\d{2})-(\d{2}))(?:\D+(\d{2}):(\d{2})(?::(\d{2}))?)?$/){ return "[FHEM::Astro::Get] hours can only be between 00 and 23" if (defined($1) && $1 > 23.); return "[FHEM::Astro::Get] minutes can only be between 00 and 59" if (defined($2) && $2 > 59.); return "[FHEM::Astro::Get] seconds can only be between 00 and 59" if (defined($3) && $3 > 59.); return "[FHEM::Astro::Get] month can only be between 01 and 12" if (defined($5) && ($5 > 12. || $5 < 1.)); return "[FHEM::Astro::Get] day can only be between 01 and 31" if (defined($6) && ($6 > 31. || $6 < 1.)); return "[FHEM::Astro::Get] hours can only be between 00 and 23" if (defined($7) && $7 > 23.); return "[FHEM::Astro::Get] minutes can only be between 00 and 59" if (defined($8) && $8 > 59.); return "[FHEM::Astro::Get] seconds can only be between 00 and 59" if (defined($9) && $9 > 59.); SetTime( _timelocal_modern( defined($3) ? $3 : (defined($9) ? $9 : 0), defined($2) ? $2 : (defined($8) ? $8 : 0), defined($1) ? $1 : (defined($7) ? $7 : 12), (defined($5)? ($6,$5-1.) : (localtime($now))[3,4]), (defined($4)? $4 : (localtime($now))[5]+1900.), ) + ( $dayOffset * 86400. ), $tz, $lc_time ) }else{ return "[FHEM::Astro::Get] $name has improper time specification $str, use [YYYY-]MM-DD [HH:MM[:SS]] [-1|yesterday|+1|tomorrow]"; } }else{ SetTime($now + ($dayOffset * 86400.), $tz, $lc_time); } #-- disable automatic links to FHEM devices delete $FW_webArgs{addLinks}; if( $a->[0] eq "version") { return version->parse(FHEM::Astro::->VERSION())->normal; }elsif( $a->[0] eq "json") { Compute($hash, $h); #-- beautify JSON at cost of performance only when debugging if (ref($json) && AttrVal($name,"verbose",AttrVal("global","verbose",3)) > 3.) { $json->canonical; $json->pretty; } if( $wantsreading==1 ){ my %ret; foreach (@readings) { next unless(exists($Astro{$_}) && !ref($Astro{$_})); if ($h && ref($h) && ($h->{text} || $h->{unit} || $h->{long})) { $ret{text}{$_} = FormatReading($_, $h, $lc_numeric); } $ret{$_} = $Astro{$_}; } return $json->encode(\%ret) if (ref($json)); return toJSON(\%ret); }else{ if ($h && ref($h) && ($h->{text} || $h->{unit} || $h->{long})) { foreach (keys %Astro) { next if (ref($Astro{$_}) || $_ =~ /^\./); $Astro{text}{$_} = FormatReading($_, $h, $lc_numeric); } } return $json->encode(\%Astro) if (ref($json)); return toJSON(\%Astro); } }elsif( $a->[0] eq "text") { Compute($hash, $h); my $ret = ""; if ( $wantsreading==1 && $h && ref($h) && scalar keys %{$h} > 0 ) { foreach (@readings) { next if (!defined($Astro{$_}) || ref($Astro{$_})); $ret .= $html && $html eq "1" ? "
\n" : "\n" if ( $ret ne "" ); $ret .= FormatReading( $_, $h, $lc_numeric ) unless($_ =~ /^\./); $ret .= encode_utf8($Astro{$_}) if ($_ =~ /^\./); } $ret = "" . $ret . "" if (defined($html) && $html ne "0"); } elsif ( $wantsreading==1 ) { foreach (@readings) { next if (!defined($Astro{$_}) || ref($Astro{$_})); $ret .= $html && $html eq "1" ? "
\n" : "\n" if ( $ret ne "" ); $ret .= encode_utf8($Astro{$_}); } $ret = "" . $ret . "" if (defined($html) && $html ne "0"); } else { $h->{long} = 1 unless ( defined( $h->{long} ) ); $ret = FormatReading( "ObsDate", $h, $lc_numeric ) . " " . $Astro{ObsTime}; $ret .= ( ( $Astro{ObsIsDST} == 1 ) ? " (" . FormatReading( "ObsIsDST", undef ) . ")" : "" ); $ret .= ", " . FormatReading( "ObsTimezone", $h ) . "\n"; $ret .= FormatReading( "ObsJD", $h, $lc_numeric ) . ", " . FormatReading( "ObsDayofyear", $h, $lc_numeric ) . "\n"; $ret .= FormatReading( "ObsSeason", $h ) . "\n"; $ret .= $tt->{"coord"} . ": " . FormatReading( "ObsLon", $h, $lc_numeric ) . ", " . FormatReading( "ObsLat", $h, $lc_numeric ) . ", " . FormatReading( "ObsAlt", $h, $lc_numeric ) . "\n"; $ret .= FormatReading( "ObsLMST", $h ) . "\n\n"; $ret .= "\n" . $tt->{"sun"} . "\n"; $ret .= FormatReading( "SunRise", $h ) . " " . FormatReading( "SunSet", $h ) . " " . FormatReading( "SunTransit", $h ) . "\n"; $ret .= FormatReading( "SunHrsVisible", $h ) . " " . FormatReading( "SunHrsInvisible", $h ) . "\n"; $ret .= FormatReading( "CivilTwilightMorning", $h ) . " - " . $Astro{CivilTwilightEvening} . "\n"; $ret .= FormatReading( "NauticTwilightMorning", $h ) . " - " . $Astro{NauticTwilightEvening} . "\n"; $ret .= FormatReading( "AstroTwilightMorning", $h ) . " - " . $Astro{AstroTwilightEvening} . "\n"; $ret .= $tt->{"distance"} . ": " . FormatReading( "SunDistance", $h, $lc_numeric ) . " (" . FormatReading( "SunDistanceObserver", $h, $lc_numeric ) . ")\n"; $ret .= $tt->{"position"} . ": " . FormatReading( "SunLon", $h, $lc_numeric ) . ", " . FormatReading( "SunRa", $h, $lc_numeric ) . ", " . FormatReading( "SunDec", $h, $lc_numeric ) . "; " . FormatReading( "SunAz", $h, $lc_numeric ) . ", " . FormatReading( "SunAlt", $h, $lc_numeric ) . "\n"; $ret .= FormatReading( "SunDiameter", $h, $lc_numeric ) . ", " . FormatReading( "SunSign", $h ) . "\n\n"; $ret .= "\n" . $tt->{"moon"} . "\n"; $ret .= FormatReading( "MoonRise", $h ) . " " . FormatReading( "MoonSet", $h ) . " " . FormatReading( "MoonTransit", $h ) . "\n"; $ret .= FormatReading( "MoonHrsVisible", $h ) . "\n"; $ret .= $tt->{"distance"} . ": " . FormatReading( "MoonDistance", $h, $lc_numeric ) . " (" . FormatReading( "MoonDistanceObserver", $h, $lc_numeric ) . ")\n"; $ret .= $tt->{"position"} . ": " . FormatReading( "MoonLon", $h, $lc_numeric ) . ", " . FormatReading( "MoonLat", $h, $lc_numeric ) . "; " . FormatReading( "MoonRa", $h, $lc_numeric ) . ", " . FormatReading( "MoonDec", $h, $lc_numeric ) . ", " . FormatReading( "MoonAz", $h, $lc_numeric ) . ", " . FormatReading( "MoonAlt", $h, $lc_numeric ) . "\n"; $ret .= FormatReading( "MoonDiameter", $h, $lc_numeric ) . ", " . FormatReading( "MoonAge", $h, $lc_numeric ) . ", " . FormatReading( "MoonPhaseN", $h, $lc_numeric ) . " = " . $Astro{MoonPhaseS} . ", " . FormatReading( "MoonSign", $h ); if ($html && $html eq "1") { $ret = "" . $ret . ""; $ret =~ s/ /   /g; $ret =~ s/ /  /g; $ret =~ s/\n/\n/g; } } return $ret; }else { return "[FHEM::Astro::Get] $name with unknown argument $a->[0], choose one of ". join(" ", map { defined($gets{$_})?"$_:$gets{$_}":$_ } sort keys %gets); } } 1; =pod =encoding utf8 =item helper =item summary collection of various routines for astronomical data =item summary_DE Sammlung verschiedener Routinen für astronomische Daten =begin html

Astro

=end html =begin html_DE

Astro

=end html_DE =for :application/json;q=META.json 95_Astro.pm { "version": "v2.1.2", "author": [ "Prof. Dr. Peter A. Henning <>", "Julian Pawlowski <>", "Marko Oldenburg <>" ], "x_fhem_maintainer": [ "pahenning", "loredo", "CoolTux" ], "resources": { "x_wiki": { "web": "https://wiki.fhem.de/wiki/Modul_Astro" } }, "keywords": [ "astrology", "astronomy", "constellation", "moon", "moonrise", "moonset", "sun", "sunrise", "sunset", "star sign", "twilight", "zodiac", "Astrologie", "Astronomie", "Mond", "Mondaufgang", "Monduntergang", "Sonne", "Sternbild", "Sternzeichen", "Sonnenaufgang", "Sonnenuntergang", "Tierkreiszeichen", "Zodiak" ], "prereqs": { "runtime": { "requires": { "Encode": 0, "GPUtils": 0, "Math::Trig": 0, "POSIX": 0, "Time::HiRes": 0, "Time::Local": 0, "strict": 0, "utf8": 0, "warnings": 0 }, "recommends": { "JSON": 0 }, "suggests": { "Cpanel::JSON::XS": 0, "JSON::XS": 0 } } } } =end :application/json;q=META.json =cut