Archiv für den Monat: Dezember 2013

Sonnenaufgang und Sonnenuntergang in Bash-Skript berechnen

Um ein Fritz!DECT 200, welche eine Außenbeleuchtung steuern soll, so anzusprechen, wie ich es mir vorgestellt habe, brauche ich eine automatisierte Steuerung. Die von AVM mitgelieferten Einstellmöglichkeiten auf der Fritz!Box sind schon recht umfangreich, bilden aber leider genau meinen Anwendungsfall nicht ab.

  • Schalte morgens ab 07:00 Uhr bis Sonnenaufgang die Außenbeleuchtung ein. Falls der Sonnenaufgang vor 07:00 Uhr war, schalte die Beleuchtung nicht ein.
  • Schalte abends ab Sonnenuntergang bis 23:00 Uhr die Außenbeleuchtung ein. Falls der Sonnenuntergang nach 23:00 Uhr war (ich weiß, ist hier nicht möglich, aber die Schaltzeit könnte ja auch früher sein), schalte die Beleuchtung nicht ein.
  • „Gäste-Schaltung“: Wenn nach 23:00 Uhr (und nach Sonnenuntergang) der Schalter manuell betätigt wurde, dann spende für 10-20 Minuten Licht, schalte danach das Licht wieder aus. (Prüfe alle 10 Minuten den Stand, wenn er zweimal hintereinander „eingeschaltet“ ist, obwohl laut Zeitplan eigentlich „ausgeschaltet“ sein sollte, schalte ab.)

Die erste Herausforderung für ein solches Skript ist zunächst, den korrekten Zeitpunkt des Sonnenaufgangs bzw. -untergangs zu berechnen. Dazu muss man wissen, dass natürlich die Dämmmerung vom Wetter abhängig ist, aber der Erdtag auch gar nicht exakt 24 Stunden lang ist, sondern sich nur „im Jahresmittel“ ausgleicht. Das führt zu Abweichungen, welche man – zumindest bis 2027 – recht exakt berechnen kann.

Das nachfolgende Skript berechnet für den aktuellen Tag die Zeitpunkte und gibt diese gerundet auf Minuten aus. Man sollte vorher seine Position in den Variablen posLaenge und posBreite korrigieren, denn 51°/10° ist grob in der Mitte Deutschlands, zwischen der westlichsten Stadt Deutschlands Isenbruch/Selfkant (51.05°, 5.866944°) und der östlichsten Stadt Deutschlands Neißeaue (51.247°, 14.975°) liegen ca. 35 Minuten.

Das Skript nutzt für die detaillierten Berechnungen bc, welches auf dem eingesetzten Unix-System vorhanden sein sollte. Da bc kein arccos(x) kann, musste ich mir mit der Umrechnung acos helfen.

Wer das Skript übrigens alle Vorberechnungen erledigen lässt, um darauf später zurückzugreifen, sollte das per übergebene Datum wie date --date="+1year" immer mit einer Uhrzeit nach 3 Uhr nachts versehen, damit die Berechnung der Winter- bzw. Sommerzeit mit greift. Sonst findet die Berechnung einen Tag zu spät statt. Die Uhren werden ja um 2 bzw. 3 Uhr nachts umgestellt, nicht bereits um Mitternacht.

#!/bin/bash
# Unsere Position
posLaenge="10.0"
posBreite="51.0"
 
# Notwendige Vorberechnungen
zoneinfo=$(date +%z) # Zeitzone
T=`date +%j` # Tag im Jahr
pi="3.14159265358979323844" # pi=`echo "4*a(1)" | bc -l`
rad=$(echo "${pi}/180" | bc -l)
h=$(echo "-(5/6)*(${rad})" | bc -l) # Höhe des Sonnenmittelpunkts bei Aufgang: Radius+Refraktion
BreiteRAD=$(echo "${posBreite}*${rad}" | bc -l)
 
# Welcher Tag ist heute?
echo "Heute ist $(date +%d.%m.%y), der $(date +%j). Tag im Jahr"
 
echo -n "Wir nutzen die Zeitzone $(date +%Z), dies entspricht $(date +%z) und damit "
echo "${zoneinfo:0:3}"
 
sonnendekl=`echo "0.409526325277017*s(0.0169060504029192*(${T}-80.0856919827619))" | bc -l`
sonnendeklDEG=$(echo "${sonnendekl} / ${rad}" | bc -l)
 
arccosint=$(echo "(s(${h})-s(${BreiteRAD})*s(${sonnendekl}))/(c(${BreiteRAD})*c(${sonnendekl}))" | bc -l)
arccosintsign=${arccosint:0:1}
if [ ${arccosintsign} == "-" ]; then
  usesign="+"
else
  usesign="-"
fi
arc2cosint=$(echo "(${arccosint}) * (${arccosint})" | bc -l)
acoszeit=$(echo "${pi}/2 ${usesign} a(sqrt(${arc2cosint} / (1 - (${arc2cosint}) ) ) ) " | bc -l)
 
zeitdiff=$(echo "12*${acoszeit}/${pi}" | bc -l) # KORREKT!
 
zeitgleich=$(echo "-0.170869921174742*s(0.0336997028793971 * ${T} + 0.465419984181394) - 0.129890681040717*s(0.0178674832556871*${T} - 0.167936777524864)" | bc -l)
aufgang=$(echo "12-(${zeitdiff})-(${zeitgleich})-(${posLaenge}/15)${zoneinfo:0:3}" | bc -l)
untergang=$(echo "12+(${zeitdiff})-(${zeitgleich})-(${posLaenge}/15)${zoneinfo:0:3}" | bc -l)
 
if [ ${aufgang:1:1} == "." ]; then
  # Ist ein einstelliges Ergebnis der Form x.xxxx, wir brauchen noch eine 0 vorne
  aufgang=$(echo 0${aufgang})
fi
# Fuer unsere Breitengrade ueberfluessig, nur der Vollstaendigkeit halber:
#if [ ${untergang:1:1} == "." ]; then
# Ist ein einstelliges Ergebnis der Form x.xxxx, wir brauchen noch eine 0 vorne
#  untergang=$(echo 0${untergang})
#fi
 
# Umrechnung in Stunden (trivial) und Minuten (runden!)
AufgangMinute=$(echo "(${aufgang} - ${aufgang:0:2}) * 60" | bc | xargs printf "%02.0f\n")
if [ ${AufgangMinute} == "60" ]; then
  AufgangMinute="00"
  AufgangStunde=$(echo "${aufgang:0:2} + 1" | bc | xargs printf "%02.0f")
else
  AufgangStunde=${aufgang:0:2}
fi
echo "Aufgang (hh:mm): ${AufgangStunde}:${AufgangMinute}" # Immer ein zweistelliges Ergebnis
 
 
UntergangMinute=$(echo "(${untergang} - ${untergang:0:2}) * 60" | bc | xargs printf "%02.0f\n")
if [ ${UntergangMinute} == "60" ]; then
  UntergangMinute="00"
  UntergangStunde=$(echo "${untergang:0:2} + 1" | bc | xargs printf "%02.0f")
else
  UntergangStunde=${untergang:0:2}
fi
echo "Untergang (hh:mm): ${UntergangStunde}:${UntergangMinute}" # Immer ein zweistelliges Ergebnis