Sonnenaufgang / Sonnenuntergang: Raspberry Pi mit Python schalten

4 Kommentare Autor: Jürgen (jdo)

Nachdem ich mein PiFace Digital wieder entdeckt habe, wollte ich nun auch ein bisschen damit spielen. Ich habe mir überlegt, dass ich mit einem der Relais ein Nachtlicht schalten könnte. Ich wollte aber nicht grob schätzen, wann es dunkel ist, sondern mich an am aktuellen Sonnenaufgang und Sonnenuntergang orientieren.

Es ist natürlich egal, was Du damit schaltest. Reicht Dir die GPIO-Schnittstelle des Raspberry Pi aus, kannst Du auch ein LED schalten, wenn es Dunkel wird. Das reicht vielleicht sogar für ein Nachtlicht, ich muss es testen. Da das PiFace Digital nun schon auf dem Raspberry Pi steckt, habe ich das genommen. Mit der GPIO-Schnittstelle kannst Du auch ein anderes Relais schalten, IR einsetzen oder sogar die USB-Ports des Raspberry Pi an- und ausschalten.

Sonnenaufgang aus / Sonnenuntergang ein

Bei meiner Aufgabe waren mir drei Dinge wichtig:

  1. Uhrzeit von Sonnenaufgang und Sonnenuntergang des jeweiligen Tages holen.
  2. Überprüfe, ob ich mich zwischen Sonnenaufgang und Sonnenuntergang befinde, weil das Relais dann ausgeschaltet sein soll.
  3. Hole die Daten beim Tageswechsel wieder. Es ist nicht notwendig, die Daten dauernd abzufragen.
Bei Sonnenaufgang kann sich das Nachtlicht abschalten

Bei Sonnenaufgang kann sich das Nachtlicht abschalten

Das Foto oben hat es in leicht abgewandelter Form übrigens in die Hintergrundbilder / Wallpaper von Linux Mint 19.3 geschafft. 🙂

Wo bekomme ich die Daten für Sonnenaufgang / Sonnenuntergang her?

Ohne die Daten für Sonnenaufgang und Sonnenuntergang ist die ganze Übung natürlich Quatsch. Ich hole mir die Daten via sunrise-sunset.org. Für die Stadt Regensburg sieht mein Aufruf zum Beispiel so aus:

https://api.sunrise-sunset.org/json?lat=49.018718&lng=12.08948&formatted=0

Du musst also Breitengrad (lat) und Längengrad (lng) angeben. Der Aufruf oben gibt Dir dann diese Daten:

Regensburg: Sonnenaufgang, Sonnenuntergang, Mittag, Tageslänge, Dämmerung

Regensburg: Sonnenaufgang, Sonnenuntergang, Mittag, Tageslänge, Dämmerung

Du bekommst folgende Informationen:

  • Sonnenaufgang
  • Sonnenuntergang
  • Mittag
  • Tageslänge (in Sekunden)
  • Bürgerliche Dämmerung (civil) – hier ist Lesen im Freien noch problemlos möglich
  • Nautische Dämmerung (nautic)
  • Astronomische Dämmerung (astronomic)

Wichtig ist hier, dass alle Zeiten in UTC angegeben sind. Die Zeitzone wird nicht beachtet, was mir aber egal ist, weil ich einfach komplett in UTC rechne. Ich stelle die Zeitzone des Pis einfach nicht um, dann passt es auch wieder. Du könntest aber auch mit einfach x Stunden addieren oder subtrahieren (vielleicht mit timedelta(hours=x) ) oder natürlich gleich die Zeitzone definieren.

Mein Script arbeitet mit Sonnenaufgang und Sonnenuntergang. In vielen Fällen würde sich wahrscheinlich die bürgerliche Dämmerung besser eignen. Aber hier kannst Du selbst experimentieren.

Mein Script (Python 3) sieht (momentan) so aus

Die while-Schleife lasse ich jede Minute laufen, daher das sleep(60) am Ende. Da ich nicht bei jedem Durchlauf das REST API von sunrise-sunset.org abfrage, könnte ich es auch öfter laufen lassen. Aber auf die Sekunde geht es bei einem Nachtlicht wirklich nicht.

Die letzten beiden Zeilen überprüfen ganz einfach, ob die Zeichenkette vom momentanen Datum unterschiedlich zu der aus der URL-Abfrage ist. Wenn ja, holt das Programm einfach neue Daten. Somit wird im Idealfall das REST API der Website nur einmal pro Tag abgefragt.

Vielleicht wäre eine Abfrage von status noch sinnvoll. Kommt etwas anderes als OK raus, dann springe in eine Schleife, warte x Sekunden und frage wieder ab.

Für die Relais ist das ähnlich. Haben sie bereits den gewünschten Status, geht das Script einfach einen Schritt weiter.

Update: Ganz am Ende findest Du die neueste Version des Scripts. Es fängt nun Netzwerkfehler ab und verwendet eine Funktion.

Das Script als Download hier.

import requests
import json
from datetime import datetime, time, timedelta
from time import sleep
import pifacedigitalio as p # nur für PiFace Digital nötig

pifacedigital = p.PiFaceDigital()
url = 'https://api.sunrise-sunset.org/json?lat=49.018718&lng=12.08948&formatted=0' # URL definieren, lat und lng anpassen
r = requests.get(url) # Daten abfragen

while True:
    d = datetime.now()
    today_date = d.date() # Heutiges Datum
    time_now = d.time() # Momentane Uhrzeit

    data = json.loads(r.content)
    sunrise = data['results']['sunrise'] # Daten für Sonnenaufgang
    sunset = data['results']['sunset'] # Daten für Sonnenuntergang
    sunrise_time = time(int(sunrise[11:13]), int(sunrise[14:16])) # Sonnenaufgang in Zeit-Format umwandeln
    sunset_time = time(int(sunset[11:13]), int(sunset[14:16])) # Sonnenuntergang in Zeit-Format umwandeln

    if time_now > sunrise_time and time_now < sunset_time: # Zeit zwischen Sonnenaufgang / -untergang

        if pifacedigital.output_pins[1].value == 1: # Ist das Relais nicht aus
            pifacedigital.output_pins[1].turn_off() # schalte es aus
    else:
        if pifacedigital.output_pins[1].value != 1: # Ist das Relais nicht an
            pifacedigital.output_pins[1].turn_on() # schalte es an

    sleep(60)
    
    if str(sunrise[0:10]) != str(today_date): # Unterschiedliches Datum, hole neue Daten
        r = requests.get(url)

Ich habe das Script als sunrise-sunset.py abgespeichert, ausführbar gemacht

chmod +x sunrise-sunset.py

und wie folgt auf dem Raspberry Pi ausgeführt (damit läuft es im Hintergrund):

nohup python3 ./sunrise-sunset.py &

Beenden kannst Du es, indem Du zunächst die PID herausfindest (das wirft eine Zahl aus):

pgrep -f sunrise

oder

ps -ef | grep "sunrise" | awk '{print $2}'

Mit der Zahl killst Du dann das Programm:

kill <Zahl des vorigen Befehls>

Es würde auch das funktionieren:

pkill -f sunrise

In meinem Fall sollte das keine Probleme bereiten, da es nur ein Programm mit dem Schlüsselwort sunrise auf dem System gibt.

Alternativ dazu könntest Du das Programm in einer screen-Sitzung aufrufen und es bei Bedarf mit Strg+C abbrechen.

Script weiter verbessern

Das Programm ist nicht perfekt, das weiß ich selbst. Es bricht zum Beispiel ab, wenn die URL nicht erreicht werden kann – Netzwerkfehler, Wartungsarbeiten und so weiter. Den Fehler kann ich mit except requests.exceptions.ConnectionError abfangen. Ich werde noch eine Prüfung reinbasteln, ob die erhaltenen Daten auch in Ordnung sind. Sollte es einen Netzwerkfehler geben, dann warte einfach x Sekunden und versuche es erneut. Die Abfrage der URL wäre dann auch besser in einer Funktion aufgehoben, die ich aufrufen kann.

Weiterhin müsste ich Sonnenaufgang und Sonnenuntergang nicht bei jedem Durchlauf der while-Schleife neu setzen. Auch hier reicht einmal pro Tag. Legst Du eine Funktion für den Aufruf der URL an, dann würde das Setzen der Uhrzeiten hier Sinn ergeben. Im Laufe der Zeit werde ich mein Script noch verfeinern.

Variiere nach Belieben

Logischerweise kannst Du mit sunrise_time und sunset_time auch selbst eine Zeitspanne definieren, in der etwas passieren soll. Vielleicht hörst Du ja zwischen 13 und 14 Uhr zwanghaft Radio. Oder nimm einen Buzzer und lasse Dich 30 Minuten nach Sonnenaufgang 5 Minuten lang wecken.

Ich habe das nun ein paar Tage laufen lassen und es funktioniert. Es tut, was es soll. Für Verbesserungsvorschläge bin ich gerne offen, da ich nicht so viel programmiere.

Möglich wäre auch gewesen, ich frage nur den Sonnenaufgang ab, schalte das Relais aus, lasse das Programm für die Länge des Tageslichts schlafen und schalte es dann wieder an. Meine Version ist mir aber lieber, da ich hier einfacher mit den Dämmerungen variieren kann.

Hast Du einen Sense HAT? Bastle Dir einen Bitcoin-Ticker – das ist einfach!

Script mit gpiozero

Der Vollständigkeit halber gibt es hier noch das Script, wie es mit gpiozero funktionieren könnte – GPIO(17) wird geschaltet.

import requests
import json
from datetime import datetime, time, timedelta
from time import sleep
from gpiozero import LED

url = 'https://api.sunrise-sunset.org/json?lat=49.018718&lng=12.08948&formatted=0'
r = requests.get(url)

led = LED(17)

while True:

    d = datetime.now()
    today_date = d.date()
    time_now = d.time()

    data = json.loads(r.content)
    sunrise = data['results']['sunrise']
    sunset = data['results']['sunset']
    sunrise_time = time(int(sunrise[11:13]), int(sunrise[14:16]))
    sunset_time = time(int(sunset[11:13]), int(sunset[14:16]))

    if time_now > sunrise_time and time_now < sunset_time:
        if led.is_active:
            led.off()
    else:
        if not led.is_active:
            led.on()

    sleep(60)

    if str(sunrise[0:10]) != str(today_date):
        r = requests.get(url)

Du musst das Script nur an wenigen Stellen verändern und schaltest damit die Pins auf Deinem Raspberry Pi zum Sonnenaufgang und Sonnenuntergang.

Gpiozero ist bei Raspbian vorinstalliert, bei Raspbian Lite aber nicht. Betreibst Du Deinen Raspberry Pi headless mit der Lite-Variante, dann musst Du gpiozero möglicherweise erst installieren:

sudo apt install python3-gpiozero

Danach klappt die Sache.

Update: neueste Version des Scripts

Derzeit verwende ich diese Version. Sollte es zu einem Fehler bei der URL-Abfrage kommen, bricht das Script nicht mehr ab. Außerdem sind Dinge in eine Funktion gewandert, in der sie besser aufgehoben sind. Ich weiß, man soll kein global verwenden. Allerdings hat es sich in meinem Fall einfach angeboten. Das Prinzip ist gleich, aber das Script ist ein bisschen aufgeräumter und kann mit Netzwerkfehlern umgehen – also wenn die URL nicht erreichbar ist.

import requests
import json
from datetime import datetime, time, timedelta
from time import sleep
import pifacedigitalio as p

pifacedigital = p.PiFaceDigital()

def check_data():
    global r, url, sunrise, sunset, sunrise_time, sunset_time
    url = 'https://api.sunrise-sunset.org/json?lat=49.018718&lng=12.08948&formatted=0'
    r = "NoResponse"
    while not r or r == "NoResponse":
        try:
            r = requests.get(url)
        except requests.exceptions.ConnectionError as connErr: 
            r = "NoResponse"
            sleep(30)
    data = json.loads(r.content)
    sunrise = data['results']['sunrise']
    sunset = data['results']['sunset']
    sunrise_time = time(int(sunrise[11:13]), int(sunrise[14:16]))
    sunset_time = time(int(sunset[11:13]), int(sunset[14:16]))

check_data()

while True:

    d = datetime.now()
    today_date = d.date()
    time_now = d.time()

    if time_now > sunrise_time and time_now < sunset_time:
        if pifacedigital.output_pins[1].value == 1:
            pifacedigital.output_pins[1].turn_off()
    else:
       if pifacedigital.output_pins[3].value != 1:
           pifacedigital.output_pins[3].turn_on()

    sleep(60)
    
    if str(sunrise[0:10]) != str(today_date):
        check_data()

Nette Pi-Konstellation

Suchst Du ein VPN für den Raspberry Pi? NordVPN* bietet einen Client, der mit Raspberry Pi OS (32-Bit / 64-Bit) und Ubuntu für Raspberry Pi (64-Bit) funktioniert.




 Alle Kommentare als Feed abonnieren

4 Kommentare zu “Sonnenaufgang / Sonnenuntergang: Raspberry Pi mit Python schalten”

  1. micha says:

    Nett, aber warum hast du nicht einfach eine Fotodiode verbaut? Dann würde es auch funktionieren, wenn ein Vulkan ausbricht und alles verfinstert 😉

    • jdo says:

      Weil ich keine habe und ich einfach ein bisschen basteln wollte. Außerdem kann ich nun auch andere Zeiten schalten, die nicht mit hell und dunkel in Zusammenhang stehen.

      Das Nachtlicht ist in einem alten Taucherhelm und da ist es auch tagsüber recht dunkel. Bin mir nicht sicher, ob das mit einer Fotodiode funktioniert.

  2. DanteTHB says:

    Servus,

    um den Traffic zu vermeiden bzw. eine "offline" Lösung zu haben, empfehle ich die pythonbibliothek "Astral" -> https://pypi.org/project/astral/

    • jdo says:

      Danke, das hab ich mir ehrlich gesagt auch schon angeschaut. Ich kann mich nicht mehr erinnern, warum ich mich dann dagegen entschieden habe, aber irgendwas hat nicht funktioniert. Ich denke noch Mal drüber nach.