From 753b669fec51472ec2f2bfab0a3a90381dd8036f Mon Sep 17 00:00:00 2001 From: jpawlowski Date: Wed, 19 Jun 2019 10:21:35 +0000 Subject: [PATCH] 95_Astro: release v2.0.0 git-svn-id: https://svn.fhem.de/fhem/trunk@19645 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 12 + fhem/FHEM/95_Astro.pm | 2591 +++++++++++++++++++++++++++++++---------- fhem/MAINTAINER.txt | 2 +- 3 files changed, 2006 insertions(+), 599 deletions(-) diff --git a/fhem/CHANGED b/fhem/CHANGED index 1984c26e3..8b86581b2 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,17 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - update: 95_Astro: v2.0.0 + device attributes for language, timezone and locale settings + new set command "update" + new attribute recomputeAt + support for disable attribute + improved startup and readings update behaviour + different custom horizon for morning/evening twilight + unit and label support for text + regional settings for numeric values and time in text format + enhanced options for Astro_Get() + fix some typos + stability improvements - feature: 73_GardenaSmartBridge: change package code - feature: 73_AMADCommBridge: change package code - feature: 42_AptToDate: change every package code diff --git a/fhem/FHEM/95_Astro.pm b/fhem/FHEM/95_Astro.pm index 0bed4d6a9..669c3d7e9 100644 --- a/fhem/FHEM/95_Astro.pm +++ b/fhem/FHEM/95_Astro.pm @@ -30,13 +30,19 @@ # ######################################################################################## -package main; +package FHEM::Astro; use strict; use warnings; use POSIX; +use utf8; +use Encode; +use FHEM::Meta; +use GPUtils qw(GP_Import); use Math::Trig; +use Time::HiRes qw(gettimeofday); use Time::Local; +use UConv; #use Data::Dumper; my $DEG = pi/180.0; @@ -47,267 +53,728 @@ my $deltaT = 65; # Correction time in s my %Astro; my %Date; -my $astroversion = 1.52; +#-- These we may set on request +my %sets = ( + "update" => "noArg", +); #-- These we may get on request my %gets = ( - "version" => "V", - "json" => "J", - "text" => "T" + "json" => undef, + "text" => undef, + "version" => "noArg", ); -my $astro_tt; +#-- 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", + "hoursofsunlight" => "Hours of sunlight", + "hoursofnight" => "Hours of night", + "hoursofvisibility" => "Visibility", -my %astro_transtable_EN = ( - "overview" => "Summary", - "name" => "Name", - "time" => "Time", - "action" => "Action", - "type" => "Type", - "description" => "Description", - "profile" => "Profile", - #-- - "coord" => "Coordinates", - "position" => "Position", - "longitude" => "Longitude", - "latitude" => "Latitude", - "altitude" => "Height above sea", - "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", - "twilightcivil" => "Civil twilight", - "twilightnautic" => "Nautical twilight", - "twilightastro" => "Astronomical twilight", - "twilightcustom" => "Custom twilight", - "sign" => "Zodiac sign", - "dst" => "daylight saving time", - #-- - "today" => "Today", - "tomorrow" => "Tomorrow", - "weekday" => "Day of Week", - "date" => "Date", - "jdate" => "Julian date", - "dayofyear" => "day of year", - "days" => "days", - "timezone" => "Time Zone", - "lmst" => "Local Sidereal Time", - #-- - "monday" => ["Monday","Mon"], - "tuesday" => ["Tuesday","Tue"], - "wednesday" => ["Wednesday","Wed"], - "thursday" => ["Thursday","Thu"], - "friday" => ["Friday","Fri"], - "saturday" => ["Saturday","Sat"], - "sunday" => ["Sunday","Sun"], - #-- - "season" => "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" - ); - - my %astro_transtable_DE = ( - "overview" => "Zusammenfassung", - "name" => "Name", - "time" => "Zeit", - "action" => "Aktion", - "type" => "Typ", - "description" => "Beschreibung", - "profile" => "Profil", - #-- - "coord" => "Koordinaten", - "position" => "Position", - "longitude" => "Länge", - "latitude" => "Breite", - "altitude" => "Höhe ü.M.", - "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", - "twilightcivil" => "Bürgerliche Dämmerung", - "twilightnautic" => "Nautische Dämmerung", - "twilightastro" => "Astronomische Dämmerung", - "twilightcustom" => "Konfigurierte Dämmerung", - "sign" => "Tierkreiszeichen", - "dst" => "Sommerzeit", - #-- - "today" => "Heute", - "tomorrow" => "Morgen", - "weekday" => "Wochentag", - "date" => "Datum", - "jdate" => "Julianisches Datum", - "dayofyear" => "Tag d. Jahres", - "days" => "Tage", - "timezone" => "Zeitzone", - "lmst" => "Lokale Sternzeit", - #-- - "monday" => ["Montag","Mo"], - "tuesday" => ["Dienstag","Di"], - "wednesday" => ["Mittwoch","Mi"], - "thursday" => ["Donnerstag","Do"], - "friday" => ["Freitag","Fr"], - "saturday" => ["Samstag","Sa"], - "sunday" => ["Sonntag","So"], - #-- - "season" => "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" - ); - -my @zodiac=("aries","taurus","gemini","cancer","leo","virgo", + "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", + "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" => "tiempo 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", + "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", + "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" => "Standaard 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", + "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"); -my @phases = ("newmoon","waxingcrescent", "firstquarter", "waxingmoon", +our @phases = ("newmoon","waxingcrescent", "firstquarter", "waxingmoon", "fullmoon", "waningmoon", "lastquarter", "waningcrescent"); -my @seasons = ( - "winter","spring","summer","fall"); - -my %seasonn = ( - "spring" => [80,172], #21./22.3. - 20.6. - "summer" => [173,265], #21.06. bis 21./22.09. - "fall" => [266,353], #22./23.09. bis 20./21.12. - "winter" => [354,79] +our @seasons = ( "winter", "spring", "summer", "fall" ); + +our %seasonn = ( + "spring" => [ 80, 172 ], #21./22.3. - 20.6. + "summer" => [ 173, 265 ], #21.06. bis 21./22.09. + "fall" => [ 266, 353 ], #22./23.09. bis 20./21.12. + "winter" => [ 354, 79 ] +); + +#-- 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 + ) ); - -sub Astro_SunRise($$$$$$); -sub Astro_MoonRise($$$$$$$); - +} + +#-- Export to main context with different name +_Export( + qw( + Get + Initialize + ) +); + +_LoadOptionalPackages(); + +sub SunRise($$$$$$$$); +sub MoonRise($$$$$$$); +sub SetTime(;$$$); +sub Compute($;$); + ######################################################################################################## # -# Astro_Initialize +# Initialize # -# Parameter hash = hash of device addressed +# Parameter hash = hash of device addressed # ######################################################################################################## -sub Astro_Initialize ($) { +sub Initialize ($) { my ($hash) = @_; - $hash->{DefFn} = "Astro_Define"; - #$hash->{SetFn} = "Astro_Set"; - $hash->{GetFn} = "Astro_Get"; - $hash->{UndefFn} = "Astro_Undef"; - $hash->{AttrFn} = "Astro_Attr"; - $hash->{AttrList} = "interval longitude latitude altitude horizon ".$readingFnAttributes;; - - $data{FWEXT}{"/Astro_moonwidget"}{FUNC} = "Astro_moonwidget"; - $data{FWEXT}{"/Astr_moonwidget"}{FORKABLE} = 0; + $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; + + $data{FWEXT}{"/Astro_moonwidget"}{FUNC} = "FHEM::Astro::Moonwidget"; + $data{FWEXT}{"/Astro_moonwidget"}{FORKABLE} = 0; - return undef; + return FHEM::Meta::InitMod( __FILE__, $hash ); } ######################################################################################################## # -# Astro_Define - Implements DefFn function +# Define - Implements DefFn function # -# Parameter hash = hash of device addressed, def = definition string +# Parameter hash = hash of device addressed, a = array of arguments, h = hash of parameters # ######################################################################################################## -sub Astro_Define ($$) { - my ($hash, $def) = @_; - #my $now = time(); - my $name = $hash->{NAME}; - $hash->{VERSION} = $astroversion; - readingsSingleUpdate( $hash, "state", "Initialized", 1 ); +sub Define ($@) { + my ($hash,$a,$h) = @_; + my $name = shift @$a; + my $type = shift @$a; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + # $hash->{VERSION} = $VERSION; + $hash->{NOTIFYDEV} = "global"; + $hash->{INTERVAL} = 3600; + readingsSingleUpdate( $hash, "state", "Initialized", $init_done ); $modules{Astro}{defptr}{$name} = $hash; - - RemoveInternalTimer($hash); - - #-- Call us in n seconds again. - InternalTimer(gettimeofday()+ 60, "Astro_Update", $hash,0); + + # 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 +'; + } return undef; } ######################################################################################################## # -# Astro_Undef - Implements Undef function +# Undef - Implements Undef function # -# Parameter hash = hash of device addressed, def = definition string +# Parameter hash = hash of device addressed, arg = array of arguments # ######################################################################################################## -sub Astro_Undef ($$) { +sub Undef ($$) { my ($hash,$arg) = @_; RemoveInternalTimer($hash); @@ -317,13 +784,90 @@ sub Astro_Undef ($$) { ######################################################################################################## # -# Astro_Attr - Implements Attr function +# Notify - Implements Notify function # -# Parameter hash = hash of device addressed, ??? +# Parameter hash = hash of device addressed, dev = hash of device that triggered notification # ######################################################################################################## -sub Astro_Attr(@) { +sub Notify ($$) { + my ($hash,$dev) = @_; + my $name = $hash->{NAME}; + my $TYPE = $hash->{TYPE}; + my $devName = $dev->{NAME}; + my $devType = GetType($devName); + + 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}; @@ -331,28 +875,89 @@ sub Astro_Attr(@) { 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] set $name interval must be >= 0" if(int($value) < 0); + return "[Astro] $do $name attribute $key must be >= 0 seconds" + unless($value =~ m/^\d+$/); #-- update timer - $hash->{INTERVAL} = int($value); - if ($init_done) { - RemoveInternalTimer($hash); - InternalTimer(gettimeofday()+$hash->{INTERVAL}, "Astro_Update", $hash, 0); + $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 )); } - last; + $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 Astro_mod($$) { my ($a,$b)=@_;if( $a =~ /\d*\.\d*/){return($a-floor($a/$b)*$b)}else{return undef}; } -sub Astro_mod2Pi($) { my ($x)=@_;$x = Astro_mod($x, 2.*pi);return($x); } -sub Astro_round($$) { my ($x,$n)=@_; return int(10**$n*$x+0.5)/10**$n}; +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 Astro_tzoffset($) { +sub _tzoffset($) { my ($t) = @_; my $utc = mktime(gmtime($t)); #-- the following does not properly calculate dst @@ -366,26 +971,131 @@ sub Astro_tzoffset($) { return (($local - $utc)/36); } +######################################################################################################## +# +# _Export - Export references to main context using a different naming schema +# +######################################################################################################## + +sub _Export { + no strict qw/refs/; ## no critic + my $pkg = caller(0); + my $main = $pkg; + $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/g; + foreach (@_) { + *{ $main . $_ } = *{ $pkg . '::' . $_ }; + } +} + +######################################################################################################## +# +# _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; + } +} + ######################################################################################################## # # time fragments into minutes, seconds # ######################################################################################################## -sub Astro_HHMM($){ +sub HHMM($){ my ($hh) = @_; return("---") - if (!defined($hh) || $hh !~ /\d*\.\d*/) ; + if (!defined($hh) || $hh !~ /^-?\d+/ || $hh==0) ; my $h = floor($hh); my $m = ($hh-$h)*60.; return sprintf("%02d:%02d",$h,$m); } -sub Astro_HHMMSS($){ +sub HHMMSS($){ my ($hh) = @_; - return("") - if ($hh==0) ; + return("---") + if (!defined($hh) || $hh !~ /^-?\d+/ || $hh==0) ; my $m = ($hh-floor($hh))*60.; my $s = ($m-floor($m))*60; @@ -395,11 +1105,11 @@ sub Astro_HHMMSS($){ ######################################################################################################## # -# Astro_CalcJD - Calculate Julian date: valid only from 1.3.1901 to 28.2.2100 +# CalcJD - Calculate Julian date: valid only from 1.3.1901 to 28.2.2100 # ######################################################################################################## -sub Astro_CalcJD($$$) { +sub CalcJD($$$) { my ($day,$month,$year) = @_; my $jd = 2415020.5-64; # 1.1.1900 - correction of algorithm if ($month<=2) { @@ -413,11 +1123,11 @@ sub Astro_CalcJD($$$) { ######################################################################################################## # -# Astro_GMST - Julian Date to Greenwich Mean Sidereal Time +# GMST - Julian Date to Greenwich Mean Sidereal Time # ######################################################################################################## -sub Astro_GMST($){ +sub GMST($){ my ($JD) = @_; my $UT = ($JD-0.5) - int($JD-0.5); $UT = $UT*24.; # UT in hours @@ -425,51 +1135,51 @@ sub Astro_GMST($){ my $T = ($JD-2451545.0)/36525.0; my $T0 = 6.697374558 + $T*(2400.051336 + $T*0.000025862); - return( Astro_mod($T0+$UT*1.002737909,24.)); + return( _mod($T0+$UT*1.002737909,24.)); } ######################################################################################################## # -# Astro_GMST2UT - Convert Greenweek mean sidereal time to UT +# GMST2UT - Convert Greenweek mean sidereal time to UT # ######################################################################################################## -sub Astro_GMST2UT($$){ +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 = Astro_mod(6.697374558 + $T*(2400.051336 + $T*0.000025862), 24.); + my $T0 = _mod(6.697374558 + $T*(2400.051336 + $T*0.000025862), 24.); my $UT = 0.9972695663*(($gmst-$T0)); return($UT); } ######################################################################################################## # -# Astro_GMST2LMST - Local Mean Sidereal Time, geographical longitude in radians, +# GMST2LMST - Local Mean Sidereal Time, geographical longitude in radians, # East is positive # ######################################################################################################## -sub Astro_GMST2LMST($$){ +sub GMST2LMST($$){ my ($gmst, $lon) = @_; - my $lmst = Astro_mod($gmst+$RAD*$lon/15, 24.); + my $lmst = _mod($gmst+$RAD*$lon/15, 24.); return( $lmst ); } ######################################################################################################## # -# Astro_Ecl2Equ - Transform ecliptical coordinates (lon/lat) to equatorial coordinates (RA/dec) +# Ecl2Equ - Transform ecliptical coordinates (lon/lat) to equatorial coordinates (RA/dec) # ######################################################################################################## -sub Astro_Ecl2Equ($$$){ +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 = Astro_mod2Pi(atan2( ($sinlon*$coseps-tan($lat)*$sineps), cos($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); @@ -477,12 +1187,12 @@ sub Astro_Ecl2Equ($$$){ ######################################################################################################## # -# Astro_Equ2Altaz - Transform equatorial coordinates (RA/Dec) to horizonal coordinates +# Equ2Altaz - Transform equatorial coordinates (RA/Dec) to horizonal coordinates # (azimuth/altitude). Refraction is ignored # ######################################################################################################## -sub Astro_Equ2Altaz($$$$$){ +sub Equ2Altaz($$$$$){ my ($ra, $dec, $TDT, $lat, $lmst)=@_; my $cosdec = cos($dec); my $sindec = sin($dec); @@ -494,7 +1204,7 @@ sub Astro_Equ2Altaz($$$$$){ my $N = -$cosdec * $sinlha; my $D = $sindec * $coslat - $cosdec * $coslha * $sinlat; - my $az = Astro_mod2Pi( atan2($N, $D) ); + my $az = _mod2Pi( atan2($N, $D) ); my $alt = asin( $sindec * $sinlat + $cosdec * $coslha * $coslat ); return ($az,$alt); @@ -502,12 +1212,12 @@ sub Astro_Equ2Altaz($$$$$){ ######################################################################################################## # -# Astro_GeoEqu2TopoEqu - Transform geocentric equatorial coordinates (RA/Dec) to +# GeoEqu2TopoEqu - Transform geocentric equatorial coordinates (RA/Dec) to # topocentric equatorial coordinates # ######################################################################################################## -sub Astro_GeoEqu2TopoEqu($$$$$$$){ +sub GeoEqu2TopoEqu($$$$$$$){ my ($ra, $dec, $distance, $lon, $lat, $radius, $lmst) = @_; my $cosdec = cos($dec); @@ -524,18 +1234,18 @@ sub Astro_GeoEqu2TopoEqu($$$$$$$){ my $distanceTopocentric = sqrt($x*$x + $y*$y + $z*$z); my $decTopocentric = asin($z/$distanceTopocentric); - my $raTopocentric = Astro_mod2Pi( atan2($y, $x) ); + my $raTopocentric = _mod2Pi( atan2($y, $x) ); return ( ($distanceTopocentric,$decTopocentric,$raTopocentric) ); } ######################################################################################################## # -# Astro_EquPolar2Cart - Calculate cartesian from polar coordinates +# EquPolar2Cart - Calculate cartesian from polar coordinates # ######################################################################################################## -sub Astro_EquPolar2Cart($$$){ +sub EquPolar2Cart($$$){ my ($lon,$lat,$distance) = @_; my $rcd = cos($lat)*$distance; my $x = $rcd*cos($lon); @@ -546,13 +1256,13 @@ sub Astro_EquPolar2Cart($$$){ ######################################################################################################## # -# Astro_Observer2EquCart - Calculate observers cartesian equatorial coordinates (x,y,z in celestial frame) +# 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 Astro_Observer2EquCart($$$$){ +sub Observer2EquCart($$$$){ my ($lon, $lat, $height, $gmst ) = @_; my $flat = 298.257223563; # WGS84 flatening of earth @@ -574,7 +1284,7 @@ sub Astro_Observer2EquCart($$$$){ if ($lat < 0.0) { $y = -$y; } # adjust sign #-- convert from geocentric polar to geocentric cartesian, with regard to Greenwich - ($x,$y,$z) = Astro_EquPolar2Cart( $x, $y, $radius ); + ($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 @@ -586,13 +1296,13 @@ sub Astro_Observer2EquCart($$$$){ ######################################################################################################## # -# Astro_SunPosition - Calculate coordinates for Sun +# SunPosition - Calculate coordinates for Sun # Coordinates are accurate to about 10s (right ascension) # and a few minutes of arc (declination) # ######################################################################################################## -sub Astro_SunPosition($$$){ +sub SunPosition($$$){ my ($TDT, $observerlat, $lmst)=@_; my $D = $TDT-2447891.5; @@ -608,7 +1318,7 @@ sub Astro_SunPosition($$$){ my %sunCoor; - $sunCoor{lon} = Astro_mod2Pi($nu+$wg); + $sunCoor{lon} = _mod2Pi($nu+$wg); $sunCoor{lat} = 0; $sunCoor{anomalyMean} = $MSun; @@ -617,11 +1327,11 @@ sub Astro_SunPosition($$$){ $sunCoor{distance} = $distance*$a; # distance in km $sunCoor{parallax} = 6378.137/$sunCoor{distance}; # horizonal parallax - ($sunCoor{ra},$sunCoor{dec}) = Astro_Ecl2Equ($sunCoor{lon}, $sunCoor{lat}, $TDT); + ($sunCoor{ra},$sunCoor{dec}) = Ecl2Equ($sunCoor{lon}, $sunCoor{lat}, $TDT); #-- calculate horizonal coordinates of sun, if geographic positions is given if (defined($observerlat) && defined($lmst) ) { - ($sunCoor{az},$sunCoor{alt}) = Astro_Equ2Altaz($sunCoor{ra}, $sunCoor{dec}, $TDT, $observerlat, $lmst); + ($sunCoor{az},$sunCoor{alt}) = Equ2Altaz($sunCoor{ra}, $sunCoor{dec}, $TDT, $observerlat, $lmst); } $sunCoor{sig} = $zodiac[floor($sunCoor{lon}*$RAD/30)]; @@ -630,12 +1340,12 @@ sub Astro_SunPosition($$$){ ######################################################################################################## # -# Astro_MoonPosition - Calculate data and coordinates for the Moon +# MoonPosition - Calculate data and coordinates for the Moon # Coordinates are accurate to about 1/5 degree (in ecliptic coordinates) # ######################################################################################################## -sub Astro_MoonPosition($$$$$$$){ +sub MoonPosition($$$$$$$){ my ($sunlon, $sunanomalyMean, $TDT, $observerlon, $observerlat, $observerradius, $lmst) = @_; my $D = $TDT-2447891.5; @@ -667,11 +1377,11 @@ sub Astro_MoonPosition($$$$$$$){ my $N2 = $N-0.16*$DEG*sin($sunanomalyMean); my %moonCoor; - $moonCoor{lon} = Astro_mod2Pi( $N2 + atan2( sin($l3-$N2)*cos($i), cos($l3-$N2) ) ); + $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}) = Astro_Ecl2Equ($moonCoor{lon},$moonCoor{lat},$TDT); + ($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 @@ -688,19 +1398,19 @@ sub Astro_MoonPosition($$$$$$$){ if (defined($observerlat) && defined($observerlon) && defined($lmst) ) { #-- transform geocentric coordinates into topocentric (==observer based) coordinates my ($distanceTopocentric,$decTopocentric,$raTopocentric) = - Astro_GeoEqu2TopoEqu($moonCoor{ra}, $moonCoor{dec}, $moonCoor{distance}, $observerlon, $observerlat, $observerradius, $lmst); + 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})= Astro_Equ2Altaz($moonCoor{ra}, $moonCoor{dec}, $TDT, $observerlat, $lmst); + ($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} = Astro_mod2Pi($l3-$sunlon); + $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 = Astro_mod($moonCoor{age}, 90.*$DEG); + my $p = _mod($moonCoor{age}, 90.*$DEG); if ($p < $mainPhase || $p > 90*$DEG-$mainPhase){ $p = 2*floor($moonCoor{age} / (90.*$DEG)+0.5); }else{ @@ -716,11 +1426,11 @@ sub Astro_MoonPosition($$$$$$$){ ######################################################################################################## # -# Astro_Refraction - Input true altitude in radians, Output: increase in altitude in degrees +# Refraction - Input true altitude in radians, Output: increase in altitude in degrees # ######################################################################################################## -sub Astro_Refraction($){ +sub Refraction($){ my ($alt) = @_; my $altdeg = $alt*$RAD; if ($altdeg<-2 || $altdeg>=90){ @@ -761,7 +1471,7 @@ sub Astro_Refraction($){ ######################################################################################################## # -# Astro_GMSTRiseSet - returns Greenwich sidereal time (hours) of time of rise +# 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 @@ -769,16 +1479,16 @@ sub Astro_Refraction($){ # ######################################################################################################## -sub Astro_GMSTRiseSet($$$$$){ +sub GMSTRiseSet($$$$$){ my ($ra, $dec, $lon, $lat, $h) = @_; $h = (defined($h)) ? $h : 0.0; # set default value - #Log 1,"-------------------> Called Astro_GMSTRiseSet with $ra $dec $lon $lat $h"; + #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,"[Astro_GMSTRiseSet] Parameters $ra $dec $lon $lat $h give complex angle"; + Log 5,"[FHEM::Astro::GMSTRiseSet] Parameters $ra $dec $lon $lat $h give complex angle"; return( ("---","---","---") ); }; my $tagbogen = acos($tagbarg); @@ -787,34 +1497,34 @@ sub Astro_GMSTRiseSet($$$$$){ 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 Astro_mod, the day number goes missing. This may get a problem for the moon - $transit = Astro_mod($transit, 24); - $rise = Astro_mod($rise, 24); - $set = Astro_mod($set, 24); + #--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) ); } ######################################################################################################## # -# Astro_InterpolateGMST - Find GMST of rise/set of object from the two calculated +# InterpolateGMST - Find GMST of rise/set of object from the two calculated # (start)points (day 1 and 2) and at midnight UT(0) # ######################################################################################################## -sub Astro_InterpolateGMST($$$$){ +sub InterpolateGMST($$$$){ my ($gmst0, $gmst1, $gmst2, $timefactor) = @_; return( ($timefactor*24.07*$gmst1- $gmst0*($gmst2-$gmst1)) / ($timefactor*24.07+$gmst1-$gmst2) ); } ######################################################################################################## # -# Astro_RiseSet +# RiseSet # // JD is the Julian Date of 0h UTC time (midnight) # ######################################################################################################## -sub Astro_RiseSet($$$$$$$$$$$){ +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' @@ -822,8 +1532,8 @@ sub Astro_RiseSet($$$$$$$$$$$){ my $alt = (!defined($altip)) ? 0.5*$diameter-$parallax+34./60*$DEG : 0.; my $altitude = (!defined($altip)) ? 0. : $altip; - my ($transit1, $rise1, $set1) = Astro_GMSTRiseSet($ra1, $dec1, $lon, $lat, $altitude); - my ($transit2, $rise2, $set2) = Astro_GMSTRiseSet($ra2, $dec2, $lon, $lat, $altitude); + 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 "---") ){ @@ -838,7 +1548,7 @@ sub Astro_RiseSet($$$$$$$$$$$){ $set2 += 24 if ($set1 > $set2 && abs($set1-$set2)>18); - my $T0 = Astro_GMST($jd0UT); + 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; @@ -863,16 +1573,16 @@ sub Astro_RiseSet($$$$$$$$$$$){ my $y = asin(sin($alt)/sin($psi)); my $dt = 240*$RAD*$y/cos($decMean)/3600; # time correction due to refraction, parallax - my $transit = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $transit1, $transit2, $timeinterval) ); - my $rise = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $rise1, $rise2, $timeinterval) - $dt ); - my $set = Astro_GMST2UT( $jd0UT, Astro_InterpolateGMST( $T0, $set1, $set2, $timeinterval) + $dt ); + 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) ); } ######################################################################################################## # -# Astro_SunRise - Find (local) time of sunrise and sunset, and twilights +# 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 @@ -881,22 +1591,22 @@ sub Astro_RiseSet($$$$$$$$$$$){ # ######################################################################################################## -sub Astro_SunRise($$$$$$){ - my ($JD, $deltaT, $lon, $lat, $zone, $recursive) = @_; +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 = Astro_SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef); + my $sunCoor1 = SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef); #-- calculations for next day's UTC midnight - my $sunCoor2 = Astro_SunPosition($jd0UT+1.+$deltaT/24./3600.,undef,undef); + my $sunCoor2 = SunPosition($jd0UT+1.+$deltaT/24./3600.,undef,undef); #-- rise/set time in UTC - my ($transit,$rise,$set) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, + 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,"[Astro_SunRise] no solution possible - maybe the sun never sets ?"; + Log 3,"[FHEM::Astro::SunRise] no solution possible - maybe the sun never sets ?"; return( ($transit,$rise,$set) ); } @@ -906,7 +1616,7 @@ sub Astro_SunRise($$$$$$){ 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) = Astro_SunRise($JD+1, $deltaT, $lon, $lat, $zone, 1); + ($transittemp,$risetemp,$settemp) = SunRise($JD+1, $deltaT, $lon, $lat, $zone, $horM, $horE, 1); $transit = $transittemp if ($transit>=24-$zone); $rise = $risetemp @@ -917,76 +1627,82 @@ sub Astro_SunRise($$$$$$){ }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) = Astro_SunRise($JD-1, $deltaT, $lon, $lat, $zone, 1); - $rise = $risetemp - if ($rise<-$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 = Astro_mod($transit+$zone, 24.); - $rise = Astro_mod($rise +$zone, 24.); - $set = Astro_mod($set +$zone, 24.); + $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) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, + ($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,"[Astro_SunRise] no solution possible for civil twilight - maybe the sun never sets below -6 degrees?"; + Log 4,"[FHEM::Astro::SunRise] no solution possible for civil twilight - maybe the sun never sets below -6 degrees?"; $CivilTwilightMorning = "---"; $CivilTwilightEvening = "---"; }else{ - $CivilTwilightMorning = Astro_mod($risetemp +$zone, 24.); - $CivilTwilightEvening = Astro_mod($settemp +$zone, 24.); + $CivilTwilightMorning = _mod($risetemp +$zone, 24.); + $CivilTwilightEvening = _mod($settemp +$zone, 24.); } #-- nautical twilight time in UTC. my $NauticTwilightMorning; my $NauticTwilightEvening; - ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, + ($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,"[Astro_SunRise] no solution possible for nautical twilight - maybe the sun never sets below -12 degrees?"; + Log 4,"[FHEM::Astro::SunRise] no solution possible for nautical twilight - maybe the sun never sets below -12 degrees?"; $NauticTwilightMorning = "---"; $NauticTwilightEvening = "---"; }else{ - $NauticTwilightMorning = Astro_mod($risetemp +$zone, 24.); - $NauticTwilightEvening = Astro_mod($settemp +$zone, 24.); + $NauticTwilightMorning = _mod($risetemp +$zone, 24.); + $NauticTwilightEvening = _mod($settemp +$zone, 24.); } #-- astronomical twilight time in UTC. my $AstroTwilightMorning; my $AstroTwilightEvening; - ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, + ($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,"[Astro_SunRise] no solution possible for astronomical twilight - maybe the sun never sets below -18 degrees?"; + Log 4,"[FHEM::Astro::SunRise] no solution possible for astronomical twilight - maybe the sun never sets below -18 degrees?"; $AstroTwilightMorning = "---"; $AstroTwilightEvening = "---"; }else{ - $AstroTwilightMorning = Astro_mod($risetemp +$zone, 24.); - $AstroTwilightEvening = Astro_mod($settemp +$zone, 24.); + $AstroTwilightMorning = _mod($risetemp +$zone, 24.); + $AstroTwilightEvening = _mod($settemp +$zone, 24.); } #-- custom twilight time in UTC my $CustomTwilightMorning; my $CustomTwilightEvening; - ($transittemp,$risetemp,$settemp) = Astro_RiseSet($jd0UT, $sunCoor1->{diameter}, $sunCoor1->{parallax}, - $sunCoor1->{ra}, $sunCoor1->{dec}, $sunCoor2->{ra}, $sunCoor2->{dec}, $lon, $lat, 1, $Astro{ObsHor}*$DEG); - if( $transittemp eq "---" ){ - Log 4,"[Astro_SunRise] no solution possible for custom twilight - maybe the sun never sets below ".$Astro{ObsHor}." degrees?"; + ($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{ - $CustomTwilightMorning = Astro_mod($risetemp +$zone, 24.); - $CustomTwilightEvening = Astro_mod($settemp +$zone, 24.); - } + $CustomTwilightEvening = _mod($settemp +$zone, 24.); + } return( ($transit,$rise,$set,$CivilTwilightMorning,$CivilTwilightEvening, $NauticTwilightMorning,$NauticTwilightEvening,$AstroTwilightMorning,$AstroTwilightEvening,$CustomTwilightMorning,$CustomTwilightEvening) ); @@ -997,7 +1713,7 @@ sub Astro_SunRise($$$$$$){ ######################################################################################################## # -# Astro_MoonRise - Find local time of moonrise and moonset +# 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 @@ -1006,22 +1722,22 @@ sub Astro_SunRise($$$$$$){ # ######################################################################################################## -sub Astro_MoonRise($$$$$$$){ +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 = Astro_SunPosition($jd0UT+ $deltaT/24./3600.,undef,undef); - my $moonCoor1 = Astro_MoonPosition($sunCoor1->{lon}, $sunCoor1->{anomalyMean}, $jd0UT+ $deltaT/24./3600.,undef,undef,undef,undef); + 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 = Astro_SunPosition($jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef); - my $moonCoor2 = Astro_MoonPosition($sunCoor2->{lon}, $sunCoor2->{anomalyMean}, $jd0UT +$timeinterval + $deltaT/24./3600.,undef,undef,undef,undef); + 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) = Astro_RiseSet($jd0UT, $moonCoor1->{diameter}, $moonCoor1->{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); @@ -1030,7 +1746,7 @@ sub Astro_MoonRise($$$$$$$){ if ( $recursive==0 ) { if ($zone>0) { # recursive call to MoonRise returns events in UTC - ($transitprev,$riseprev,$setprev) = Astro_MoonRise($JD-1., $deltaT, $lon, $lat, $radius, $zone, 1); + ($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 @@ -1041,7 +1757,7 @@ sub Astro_MoonRise($$$$$$$){ if ($rise >= 24.-$zone || $rise < -$zone) { # rise time is tomorrow local time if ($riseprev < 24.-$zone){ - $rise = ""; # there is no moontransit today + $rise = ""; # there is no moonrise today }else{ $rise = $riseprev; } @@ -1049,7 +1765,7 @@ sub Astro_MoonRise($$$$$$$){ if ($set >= 24.-$zone || $set < -$zone) { # set time is tomorrow local time if ($setprev < 24.-$zone){ - $set = ""; # there is no moontransit today + $set = ""; # there is no moonset today }else{ $set = $setprev; } @@ -1057,7 +1773,15 @@ sub Astro_MoonRise($$$$$$$){ }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) = Astro_MoonRise($JD+1., $deltaT, $lon, $lat, $radius, $zone, 1); + ($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 @@ -1066,16 +1790,8 @@ sub Astro_MoonRise($$$$$$$){ } } - if ($transit < -zone){ - if ($transittemp > -zone){ - $transit = ''; # there is no moonset today - }else{ - $transit = $transittemp; - } - } - - if ($set < -zone){ - if ($settemp > -zone){ + if ($set < -$zone){ + if ($settemp > -$zone){ $set = ''; # there is no moonset today }else{ $set = $settemp; @@ -1084,11 +1800,11 @@ sub Astro_MoonRise($$$$$$$){ } } #-- correct for time zone, if time is valid - $transit = Astro_mod($transit +$zone, 24.) + $transit = _mod($transit +$zone, 24.) if( $transit ne ""); - $rise = Astro_mod($rise +$zone, 24.) + $rise = _mod($rise +$zone, 24.) if ($rise ne ""); - $set = Astro_mod($set +$zone, 24.) + $set = _mod($set +$zone, 24.) if ($set ne ""); } return( ($transit,$rise,$set) ); @@ -1096,27 +1812,103 @@ sub Astro_MoonRise($$$$$$$){ ######################################################################################################## # -# Astro_Compute - sequential calculation of properties +# 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(); + + $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(); + + setlocale(LC_TIME, ""); + setlocale(LC_TIME, $old_lctime); + no locale; + + return (undef); +} + +######################################################################################################## +# +# Compute - sequential calculation of properties # ######################################################################################################## -sub Astro_Compute($){ - my ($hash) = @_; - +sub Compute($;$){ + my ($hash,$params) = @_; + undef %Astro; my $name = $hash->{NAME}; - - #-- readjust language - my $lang = AttrVal("global","language","EN"); - if( $lang eq "DE"){ - $astro_tt = \%astro_transtable_DE; - }else{ - $astro_tt = \%astro_transtable_EN; - } - + 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"})}) + ){ + $tt = $transtable{uc($params->{"language"})}; + }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(); + #-- geodetic latitude and longitude of observer on WGS84 - if( defined($attr{$name}{"latitude"}) ){ + 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"}; @@ -1124,7 +1916,9 @@ sub Astro_Compute($){ $Astro{ObsLat} = 50.0; Log3 $name,3,"[Astro] No latitude attribute set in global device, using 50.0°"; } - if( defined($attr{$name}{"longitude"}) ){ + 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"}; @@ -1133,22 +1927,33 @@ sub Astro_Compute($){ Log3 $name,3,"[Astro] No longitude attribute set in global device, using 10.0°"; } #-- altitude of observer in meters above WGS84 ellipsoid - if( defined($attr{$name}{"altitude"}) ){ + 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($attr{$name}{"horizon"}) ){ - $Astro{ObsHor} = $attr{$name}{"horizon"}; - }else{ - $Astro{ObsHor} = 0.0; - Log3 $name,5,"[Astro] No horizon attribute defined, using 0.0°"; - } - + 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; @@ -1159,114 +1964,221 @@ sub Astro_Compute($){ # return; #} - my $JD0 = Astro_CalcJD( $Date{day}, $Date{month}, $Date{year} ); + my $JD0 = CalcJD( $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} = Astro_round($JD,2); + $Astro{".ObsJD"} = $JD; + $Astro{ObsJD} = _round($JD,2); - my $gmst = Astro_GMST($JD); - $Astro{ObsGMST} = Astro_HHMMSS($gmst); - my $lmst = Astro_GMST2LMST($gmst, $lon); - $Astro{ObsLMST} = Astro_HHMMSS($lmst); + 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) = Astro_Observer2EquCart($lon, $lat, $height, $gmst); + my ($x,$y,$z,$radius) = Observer2EquCart($lon, $lat, $height, $gmst); #-- calculate data for the sun at given time - my $sunCoor = Astro_SunPosition($TDT, $lat, $lmst*15.*$DEG); - $Astro{SunLon} = Astro_round($sunCoor->{lon}*$RAD,1); + my $sunCoor = SunPosition($TDT, $lat, $lmst*15.*$DEG); + $Astro{SunLon} = _round($sunCoor->{lon}*$RAD,1); #$Astro{SunLat} = $sunCoor->{lat}*$RAD; - $Astro{SunRa} = Astro_round($sunCoor->{ra} *$RAD/15,1); - $Astro{SunDec} = Astro_round($sunCoor->{dec}*$RAD,1); - $Astro{SunAz} = Astro_round($sunCoor->{az} *$RAD,1); - $Astro{SunAlt} = Astro_round($sunCoor->{alt}*$RAD + Astro_Refraction($sunCoor->{alt}),1); # including refraction WARNUNG => *RAD ??? - $Astro{SunSign} = $astro_tt->{$sunCoor->{sig}}; - $Astro{SunDiameter}=Astro_round($sunCoor->{diameter}*$RAD*60,1); #angular diameter in arc seconds - $Astro{SunDistance}=Astro_round($sunCoor->{distance},0); + $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{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) = Astro_EquPolar2Cart($sunCoor->{ra}, $sunCoor->{dec}, $sunCoor->{distance}); - $Astro{SunDistanceObserver} = Astro_round(sqrt( ($xs-$x)**2 + ($ys-$y)**2 + ($zs-$z)**2 ),0); + 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) = - Astro_SunRise($JD0, $deltaT, $lon, $lat, $Date{zonedelta}, 0); - $Astro{SunTransit} = Astro_HHMM($suntransit); - $Astro{SunRise} = Astro_HHMM($sunrise); - $Astro{SunSet} = Astro_HHMM($sunset); - $Astro{CivilTwilightMorning} = Astro_HHMM($CivilTwilightMorning); - $Astro{CivilTwilightEvening} = Astro_HHMM($CivilTwilightEvening); - $Astro{NauticTwilightMorning} = Astro_HHMM($NauticTwilightMorning); - $Astro{NauticTwilightEvening} = Astro_HHMM($NauticTwilightEvening); - $Astro{AstroTwilightMorning} = Astro_HHMM($AstroTwilightMorning); - $Astro{AstroTwilightEvening} = Astro_HHMM($AstroTwilightEvening); - $Astro{CustomTwilightMorning} = Astro_HHMM($CustomTwilightMorning); - $Astro{CustomTwilightEvening} = Astro_HHMM($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 = Astro_MoonPosition($sunCoor->{lon}, $sunCoor->{anomalyMean}, $TDT, $lon, $lat, $radius, $lmst*15.*$DEG); - $Astro{MoonLon} = Astro_round($moonCoor->{lon}*$RAD,1); - $Astro{MoonLat} = Astro_round($moonCoor->{lat}*$RAD,1); - $Astro{MoonRa} = Astro_round($moonCoor->{ra} *$RAD/15.,1); - $Astro{MoonDec} = Astro_round($moonCoor->{dec}*$RAD,1); - $Astro{MoonAz} = Astro_round($moonCoor->{az} *$RAD,1); - $Astro{MoonAlt} = Astro_round($moonCoor->{alt}*$RAD + Astro_Refraction($moonCoor->{alt}),1); # including refraction WARNUNG => *RAD ??? - $Astro{MoonSign} = $astro_tt->{$moonCoor->{sig}}; - $Astro{MoonDistance} = Astro_round($moonCoor->{distance},0); - $Astro{MoonDiameter} = Astro_round($moonCoor->{diameter}*$RAD*60.,1); # angular diameter in arc seconds - $Astro{MoonAge} = Astro_round($moonCoor->{age}*$RAD,1); - $Astro{MoonPhaseN} = Astro_round($moonCoor->{phasen},2); + 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{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} = $astro_tt->{$moonCoor->{phases}}; + $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) = Astro_EquPolar2Cart($moonCoor->{ra}, $moonCoor->{dec}, $moonCoor->{distance}); + 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} = Astro_round(sqrt( ($xm-$x)**2 + ($ym-$y)**2 + ($zm-$z)**2 ),0); - - my ($moontransit,$moonrise,$moonset) = Astro_MoonRise($JD0, $deltaT, $lon, $lat, $radius, $Date{zonedelta}, 0); - $Astro{MoonTransit} = Astro_HHMM($moontransit); - $Astro{MoonRise} = Astro_HHMM($moonrise); - $Astro{MoonSet} = Astro_HHMM($moonset); + $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}= sprintf("%02d.%02d.%04d",$Date{day},$Date{month},$Date{year}); - $Astro{ObsTime}= sprintf("%02d:%02d:%02d",$Date{hour},$Date{min},$Date{sec}); - $Astro{ObsTimezone}= $Date{zonedelta}; - $Astro{ObsIsDST}= $Date{isdst}; - - #-- check season - my $doj = $Date{dayofyear}; - $Astro{ObsDayofyear} = $doj; - + $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 $doj = $Astro{ObsDayofyear}; + for( my $i=0;$i<4;$i++){ my $key = $seasons[$i]; if( (($seasonn{$key}[0] < $seasonn{$key}[1]) && ($seasonn{$key}[0] <= $doj) && ($seasonn{$key}[1] >= $doj)) || (($seasonn{$key}[0] > $seasonn{$key}[1]) && (($seasonn{$key}[0] <= $doj) || ($seasonn{$key}[1] >= $doj))) ){ - $Astro{ObsSeason} = $astro_tt->{$key}; + $Astro{ObsSeason} = $tt->{$key}; $Astro{ObsSeasonN} = $i; last; } } - + + delete local $ENV{TZ}; + tzset(); + return( undef ); }; -######################################################################################## +######################################################################################################## # -# Astro_moonwidget - SVG picture of the moon +# Moonwidget - SVG picture of the moon # -# Parameter hash = hash of the bus master a = argument array +# Parameter arg = argument array # -######################################################################################## +######################################################################################################## -sub Astro_moonwidget($){ +sub Moonwidget($){ my ($arg) = @_; my $name = $FW_webArgs{name}; - $name =~ s/'//g; - my $hash = $defs{$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)'; @@ -1281,8 +2193,8 @@ sub Astro_moonwidget($){ $FW_RETTYPE = "image/svg+xml"; $FW_RET=""; FW_pO ''; - my $ma = Astro_Get($hash,("","text","MoonAge")); - my $mb = Astro_Get($hash,("","text","MoonPhaseS")); + my $ma = Get($hash,("","text","MoonAge")); + my $mb = Get($hash,("","text","MoonPhaseS")); my ($radius,$axis,$dir,$start,$middle); $radius = 250; @@ -1312,164 +2224,536 @@ sub Astro_moonwidget($){ return ($FW_RETTYPE, $FW_RET); } -######################################################################################## +######################################################################################################## # -# Astro_Update - Update readings +# Update - Update readings # -# Parameter hash = hash of the bus master a = argument array +# Parameter hash = hash of the bus master # -######################################################################################## +######################################################################################################## -sub Astro_Update($@) { +sub Update($@) { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash); - my $interval = ( defined($hash->{INTERVAL})) ? $hash->{INTERVAL} : 3600; - - InternalTimer(gettimeofday()+ $interval, "Astro_Update", $hash,1) - if( $interval > 0 ); + 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( 0, 0, 0, ( localtime( $now + 86400. ) )[ 3, 4, 5 ] ); + next; + } + my $k = ".$comp"; + next unless ( defined( $Astro{$k} ) && $Astro{$k} =~ /^\d+(?:\.\d+)?$/ ); + my $t = + timelocal( 0, 0, 0, ( localtime($now) )[ 3, 4, 5 ] ) + $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 ); + } - #-- Current time will be used - my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time); - $year += 1900; - $month += 1; - $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)/100; - $Date{zonedelta} = Astro_tzoffset(time)/100; - #-- half broken in windows - $Date{dayofyear} = 1*strftime("%j", localtime); - - Astro_Compute($hash); - readingsBeginUpdate($hash); - foreach my $key (keys %Astro){ - readingsBulkUpdateIfChanged($hash,$key,$Astro{$key}); + foreach my $key (keys %Astro){ + readingsBulkUpdateIfChanged($hash,$key,encode_utf8($Astro{$key})); } readingsEndUpdate($hash,1); readingsSingleUpdate($hash,"state","Updated",1); } -######################################################################################## +######################################################################################################## # -# Astro_Get - Implements GetFn function +# FormatReading - Convert a reading into text format +# +# Parameter r = reading name, h = parameter hash, locale = locale string +# +######################################################################################################## + +sub FormatReading($$;$) { + my ( $r, $h, $lc_numeric ) = @_; + my $ret; + + 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 = sprintf( $f, $Astro{$r} ); + $ret = UConv::decimal_mark( $ret, $lc_numeric ) + unless ( $h && ref($h) && defined( $h->{html} ) && $h->{html} eq "0" ); + + 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} ) { + $ret = $tt->{"twilightastro"} . " " . $ret + if ( $r eq "AstroTwilightEvening" ); + $ret = $tt->{"twilightastro"} . " " . $ret + if ( $r eq "AstroTwilightMorning" ); + $ret = $tt->{"twilightcivil"} . " " . $ret + if ( $r eq "CivilTwilightEvening" ); + $ret = $tt->{"twilightcivil"} . " " . $ret + if ( $r eq "CivilTwilightMorning" ); + $ret = $tt->{"twilightcustom"} . " " . $ret + if ( $r eq "CustomTwilightEvening" ); + $ret = $tt->{"twilightcustom"} . " " . $ret + if ( $r eq "CustomTwilightMorning" ); + $ret = $tt->{"age"} . " " . $ret if ( $r eq "MoonAge" ); + $ret = $tt->{"alt"} . " " . $ret if ( $r eq "MoonAlt" ); + $ret = $tt->{"az"} . " " . $ret if ( $r eq "MoonAz" ); + $ret = $tt->{"dec"} . " " . $ret if ( $r eq "MoonDec" ); + $ret = $tt->{"diameter"} . " " . $ret if ( $r eq "MoonDiameter" ); + $ret = $ret . " " . $tt->{"toce"} + if ( $r eq "MoonDistance" ); + $ret = $ret . " " . $tt->{"toobs"} + if ( $r eq "MoonDistanceObserver" ); + $ret = $tt->{"hoursofvisibility"} . " " . $ret + if ( $r eq "MoonHrsVisible" ); + $ret = $tt->{"latecl"} . " " . $ret if ( $r eq "MoonLat" ); + $ret = $tt->{"lonecl"} . " " . $ret if ( $r eq "MoonLon" ); + $ret = $tt->{"phase"} . " " . $ret if ( $r eq "MoonPhaseN" ); + $ret = $tt->{"phase"} . " " . $ret if ( $r eq "MoonPhaseS" ); + $ret = $tt->{"ra"} . " " . $ret if ( $r eq "MoonRa" ); + $ret = $tt->{"rise"} . " " . $ret if ( $r eq "MoonRise" ); + $ret = $tt->{"set"} . " " . $ret if ( $r eq "MoonSet" ); + $ret = $tt->{"sign"} . " " . $ret if ( $r eq "MoonSign" ); + $ret = $tt->{"transit"} . " " . $ret if ( $r eq "MoonTransit" ); + $ret = $tt->{"twilightnautic"} . " " . $ret + if ( $r eq "NauticTwilightEvening" ); + $ret = $tt->{"twilightnautic"} . " " . $ret + if ( $r eq "NauticTwilightMorning" ); + $ret = $ret . " " . $tt->{"altitude"} if ( $r eq "ObsAlt" ); + $ret = $tt->{"date"} . " " . $ret if ( $r eq "ObsDate" ); + $ret = $ret . " " . $tt->{"dayofyear"} if ( $r eq "ObsDayofyear" ); + $ret = $tt->{"alt"} . " " . $ret if ( $r eq "ObsHorEvening" ); + $ret = $tt->{"alt"} . " " . $ret if ( $r eq "ObsHorMorning" ); + $ret = ( $Astro{$r} == 1. ? $tt->{"dst"} : $tt->{"nt"} ) + if ( $r eq "ObsIsDST" ); + $ret = $tt->{"jdate"} . " " . $ret if ( $r eq "ObsJD" ); + $ret = $tt->{"lmst"} . " " . $ret if ( $r eq "ObsLMST" ); + $ret = $ret . " " . $tt->{"latitude"} if ( $r eq "ObsLat" ); + $ret = $ret . " " . $tt->{"longitude"} if ( $r eq "ObsLon" ); + $ret = $tt->{"season"} . " " . $ret if ( $r eq "ObsSeason" ); + $ret = $tt->{"time"} . " " . $ret if ( $r eq "ObsTime" ); + $ret = $tt->{"timezone"} . " " . $ret if ( $r eq "ObsTimezone" ); + $ret = $tt->{"alt"} . " " . $ret if ( $r eq "SunAlt" ); + $ret = $tt->{"az"} . " " . $ret if ( $r eq "SunAz" ); + $ret = $tt->{"dec"} . " " . $ret if ( $r eq "SunDec" ); + $ret = $tt->{"diameter"} . " " . $ret if ( $r eq "SunDiameter" ); + $ret = $ret . " " . $tt->{"toce"} + if ( $r eq "SunDistance" ); + $ret = $ret . " " . $tt->{"toobs"} + if ( $r eq "SunDistanceObserver" ); + $ret = $tt->{"hoursofnight"} . " " . $ret + if ( $r eq "SunHrsInvisible" ); + $ret = $tt->{"hoursofsunlight"} . " " . $ret + if ( $r eq "SunHrsVisible" ); + $ret = $tt->{"lonecl"} . " " . $ret if ( $r eq "SunLon" ); + $ret = $tt->{"ra"} . " " . $ret if ( $r eq "SunRa" ); + $ret = $tt->{"rise"} . " " . $ret if ( $r eq "SunRise" ); + $ret = $tt->{"set"} . " " . $ret if ( $r eq "SunSet" ); + $ret = $tt->{"sign"} . " " . $ret if ( $r eq "SunSign" ); + $ret = $tt->{"transit"} . " " . $ret if ( $r eq "SunTransit" ); + + #-- add prefix for Sun/Moon if desired + if ( $h->{long} > 1. && $r =~ /^Sun|Moon/ ) { + $ret = " " . $ret; + + #-- add a separator after prefix if desired + $ret = ":" . $ret if ( $h->{long} > 2. ); + + $ret = $tt->{"sun"} . $ret + if ( $r =~ /^Sun/ ); + $ret = $tt->{"moon"} . $ret + if ( $r =~ /^Moon/ ); + } + } + } + + return $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 Astro_Get($@) { - my ($hash, @a) = @_; - - my $name = $hash->{NAME}; - - my $wantsreading = 0; - - #-- second parameter may be a reading - if( (int(@a)>2) && exists($Astro{$a[2]})) { - $wantsreading = 1; - #Log 1,"=================> WANT as ".$a[1]." READING ".$a[2]." GET READING ".$Astro{$a[2]}; +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; } - - if( int(@a) > (2+$wantsreading) ) { - my $str = (int(@a) == (4+$wantsreading)) ? $a[2+$wantsreading]." ".$a[3+$wantsreading] : $a[2+$wantsreading]; - if( $str =~ /(\d{4})-(\d{2})-(\d{2})(\D*(\d{2}):(\d{2})(:(\d{2}))?)?/){ - $Date{year} = $1; - $Date{month}= $2; - $Date{day} = $3; - $Date{hour} = (defined($5)) ? $5 : 12; - $Date{min} = (defined($6)) ? $6 : 0; - $Date{sec} = (defined($8)) ? $8 : 0; - my $fTot = timelocal($Date{sec},$Date{min},$Date{hour},$Date{day},$Date{month}-1,$Date{year}); - #-- broken on windows - #$Date{zonedelta} = (strftime "%z", localtime($fTot))/100; - $Date{zonedelta} = Astro_tzoffset($fTot)/100; - $Date{isdst} = (localtime($fTot))[8]; - #-- half broken in windows - $Date{dayofyear} = 1*strftime("%j", localtime($fTot)); + else { + $type = shift @$a; + } + if (defined($hash->{NAME})) { + $name = $hash->{NAME}; + } else { + $hash->{NAME} = $name; + } + + my $wantsreading = 0; + my $dayOffset = 0; + 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}))?)?$/){ + SetTime( + timelocal( + defined($3) ? $3 : (defined($9) ? $9 : 0), + defined($2) ? $2 : (defined($8) ? $8 : 0), + defined($1) ? $1 : (defined($7) ? $7 : 12), + (defined($4)? ($6,$5-1,$4) : (localtime(gettimeofday()))[3,4,5]) + ) + ( $dayOffset * 86400. ), $tz, $lc_time + ) }else{ - return "[Astro_Get] $name has improper time specification $str, use YYYY-MM-DD HH:MM:SS"; + return "[FHEM::Astro::Get] $name has improper time specification $str, use YYYY-MM-DD [HH:MM:SS] [-1|yesterday|+1|tomorrow]"; } }else{ - #-- Current time will be used - my ($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = localtime(time); - $year += 1900; - $month += 1; - $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)/100; - $Date{zonedelta} = Astro_tzoffset(time)/100; - #-- half broken in windows - $Date{dayofyear} = 1*strftime("%j", localtime); + SetTime(gettimeofday() + ($dayOffset * 86400.), $tz, $lc_time); } - if( $a[1] eq "version") { - return $astroversion; + #-- disable automatic links to FHEM devices + delete $FW_webArgs{addLinks}; + + if( $a->[0] eq "version") { + return version->parse(FHEM::Astro::->VERSION())->normal; - }elsif( $a[1] eq "json") { - Astro_Compute($hash); + }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 ){ - return toJSON($Astro{$a[2]}); + 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[1] eq "text") { - - Astro_Compute($hash); - if( $wantsreading==1 ){ - return $Astro{$a[2]}; - }else{ - my $ret=sprintf("%s %s %s",$astro_tt->{"date"},$Astro{ObsDate},$Astro{ObsTime}); - $ret .= (($Astro{ObsIsDST}==1) ? " (".$astro_tt->{"dst"}.")\n" : "\n" ); - $ret .= sprintf("%s %.2f %s, %d %s\n",$astro_tt->{"jdate"},$Astro{ObsJD},$astro_tt->{"days"},$Astro{ObsDayofyear},$astro_tt->{"dayofyear"}); - $ret .= sprintf("%s %s, %s %2d\n",$astro_tt->{"season"},$Astro{ObsSeason},$astro_tt->{"timezone"},$Astro{ObsTimezone}); - $ret .= sprintf("%s %.5f° %s, %.5f° %s, %.0fm %s\n",$astro_tt->{"coord"},$Astro{ObsLon},$astro_tt->{"longitude"}, - $Astro{ObsLat},$astro_tt->{"latitude"},$Astro{ObsAlt},$astro_tt->{"altitude"}); - $ret .= sprintf("%s %s \n\n",$astro_tt->{"lmst"},$Astro{ObsLMST}); - $ret .= "\n".$astro_tt->{"sun"}."\n"; - $ret .= sprintf("%s %s %s %s %s %s\n",$astro_tt->{"rise"},$Astro{SunRise},$astro_tt->{"set"},$Astro{SunSet},$astro_tt->{"transit"},$Astro{SunTransit}); - $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightcivil"},$Astro{CivilTwilightMorning},$Astro{CivilTwilightEvening}); - $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightnautic"},$Astro{NauticTwilightMorning},$Astro{NauticTwilightEvening}); - $ret .= sprintf("%s %s - %s\n",$astro_tt->{"twilightastro"},$Astro{AstroTwilightMorning},$Astro{AstroTwilightEvening}); - $ret .= sprintf("%s: %.0fkm %s (%.0fkm %s)\n",$astro_tt->{"distance"},$Astro{SunDistance},$astro_tt->{"toce"},$Astro{SunDistanceObserver},$astro_tt->{"toobs"}); - $ret .= sprintf("%s: %s %2.1f°, %s %2.2fh, %s %2.1f°; %s %2.1f°, %s %2.1f°\n", - $astro_tt->{"position"},$astro_tt->{"lonecl"},$Astro{SunLon},$astro_tt->{"ra"}, - $Astro{SunRa},$astro_tt->{"dec"},$Astro{SunDec},$astro_tt->{"az"},$Astro{SunAz},$astro_tt->{"alt"},$Astro{SunAlt}); - $ret .= sprintf("%s %2.1f', %s %s\n\n",$astro_tt->{"diameter"},$Astro{SunDiameter},$astro_tt->{"sign"},$Astro{SunSign}); - $ret .= "\n".$astro_tt->{"moon"}."\n"; - $ret .= sprintf("%s %s %s %s %s %s\n",$astro_tt->{"rise"},$Astro{MoonRise},$astro_tt->{"set"},$Astro{MoonSet},$astro_tt->{"transit"},$Astro{MoonTransit}); - $ret .= sprintf("%s: %.0fkm %s (%.0fkm %s)\n",$astro_tt->{"distance"},$Astro{MoonDistance},$astro_tt->{"toce"},$Astro{MoonDistanceObserver},$astro_tt->{"toobs"}); - $ret .= sprintf("%s: %s %2.1f°, %s %2.1f°; %s %2.2fh, %s %2.1f°; %s %2.1f°, %s %2.1f°\n", - $astro_tt->{"position"},$astro_tt->{"lonecl"},$Astro{MoonLon},$astro_tt->{"latecl"},$Astro{MoonLat},$astro_tt->{"ra"}, - $Astro{MoonRa},$astro_tt->{"dec"},$Astro{MoonDec},$astro_tt->{"az"},$Astro{MoonAz},$astro_tt->{"alt"},$Astro{MoonAlt}); - $ret .= sprintf("%s %2.1f', %s %2.1f°, %s %1.2f = %s, %s %s\n",$astro_tt->{"diameter"}, - $Astro{MoonDiameter},$astro_tt->{"age"},$Astro{MoonAge},$astro_tt->{"phase"},$Astro{MoonPhaseN},$Astro{MoonPhaseS},$astro_tt->{"sign"},$Astro{MoonSign}); - return $ret; + }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 .= encode_utf8(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; + + $ret = FormatReading( "ObsDate", $h, $lc_numeric ) . " " . $Astro{ObsTime}; + $ret .= ( + ( $Astro{ObsIsDST} == 1 ) + ? " (" . FormatReading( "ObsIsDST", $h ) . ")" + : "" + ); + $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 "[Astro_Get] $name with unknown argument $a[1], choose one of ". - join(" ", sort keys %gets); + 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 @@ -1486,11 +2770,12 @@ sub Astro_Get($@) {
Defines the Astro device (only one is needed per FHEM installation).

Readings with prefix Sun refer to the sun, with prefix Moon refer to the moon. - The suffixes for these readings are + The suffixes for these readings are:

Readings with prefix Obs refer to the observer. - In addition to some of the suffixes gives above, the following may occur + In addition to some of the suffixes gives above, the following may occur:

An SVG image of the current moon phase may be obtained under the link - <ip address of fhem>/fhem/Astro_moonwidget?name='<device name>' + <ip address of fhem>/fhem/Astro_moonwidget?name='<device name>'. Optional web parameters are [&size='<width>x<height>'][&mooncolor=<color>][&moonshadow=<color>]

Notes:

+ +

Set

+

Get

Attention: Get-calls are NOT written into the readings of the device. Readings change only through periodic updates.

Attributes

=end html_DE -=cut \ No newline at end of file +=for :application/json;q=META.json 95_Astro.pm +{ + "version": "v2.0.0", + "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", + "sun", + "star sign", + "twilight", + "zodiac", + "Astrologie", + "Astronomie", + "Mond", + "Sonne", + "Sternbild", + "Sternzeichen", + "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 diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 7d1d4bcda..76f07a7cb 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -431,7 +431,7 @@ FHEM/93_Log2Syslog.pm DS_Starter Automatisierung FHEM/93_PWMR.pm jamesgo Heizungssteuerung/Raumklima FHEM/94_PWM.pm jamesgo Heizungssteuerung/Raumklima FHEM/95_Alarm.pm pahenning Unterstützende Dienste -FHEM/95_Astro.pm pahenning Unterstützende Dienste +FHEM/95_Astro.pm pahenning,loredo,CoolTux Unterstützende Dienste FHEM/95_Babble.pm pahenning Unterstützende Dienste FHEM/95_Dashboard.pm DS_Starter/orphan Frontends FHEM/95_FLOORPLAN.pm ulimaass Frontends/FLOORPLAN