Programmfehler erkennen und melden.

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Stevo
User
Beiträge: 5
Registriert: Dienstag 21. November 2017, 17:30

Moin moin zusammen :)
Ich bin neu hier und habe sehr wenig Python Erfahrungen, bin aber auh kein Programmierneuling.
Nun bräuchte ich einmal Hilfe oder Anregungen zu folgendem Sachverhalt:

Auf einem Raspi habe ich ein kleines Python Skript, welches bei bestimmten Ereignissen eine Statusmeldung in ein Logfile schreibt oder eine SMS versenden soll. Der SMS Fall wird wahrscheinlich nie eintreffen und die Logdatei täglich zu überprüfen ist unsinnig.

Dazu möchte ich eine Art Überwachung für das Programm einbauen. Wenn es ausfällt soll einfach ein Ausgang am Pi angesprochen werden, den ich dann abgreife und mit einer kleinen Sirene verbinde.

Ich bin mir nun nicht sicher, ob man die Abfrage in das Programm selber implementieren soll, oder ob es eine Linux Status Abfrage wird.
Das Script wird auch mit systemd bei jedem boot gestartet.

Für Tipps und Anregungen wäre ich sehr dankbar.
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Stevo hat geschrieben: Ich bin mir nun nicht sicher, ob man die Abfrage in das Programm selber implementieren soll, oder ob es eine Linux Status Abfrage wird.
Das Script wird auch mit systemd bei jedem boot gestartet.
In Systemd Units kann man über die Option "OnFailure" andere Units angeben, die gestartet werden, wenn der Prozess fehlschlägt. Damit kann man so eine Logik, wie du sie beschreibst, umsetzen. Dein eigentliches Skript wird über die primäre Service-Unit gestartet, das "Nofallskript" über eine sekundäre Service-Unit (z. B. vom Typ "oneshot"), die du in der ersten als "OnFailure" hinterlegst.

Ich kenne dein Skript nicht, allgemein würde ich statt einer externen Lösung eher dazu neigen, den Code so zu strukturieren, dass Skript nicht einfach so "ausfällt", sondern Exceptions angemessen behandelt werden und diese Notfallfunktion ggf. integriert ist.
Stevo
User
Beiträge: 5
Registriert: Dienstag 21. November 2017, 17:30

@nezzcarth
Danke für den Vorschlag, dass ist eine gute Idee :)

Der Vollständigkeit halber möchte ich einmal genauer erläutern, worum es geht.
Ich habe zu Hause eine Alarmanlage, welche bisher lediglich einen lokalen Alarm ausgibt. Diese möchte ich um eine selbstgebaute Übertragungseinrichtung erweitern. Die Events Störung sowie Scharf/Unscharf sollen lediglich in einer Log Datei gespeichert werden, echte Alarme zusätzlich per SMS an mich und weitere Personen versand werden. Da ich mir nicht täglich eine Routine SMS zusenden möchte, suche ich nach einem Weg, dieses Skript ausfallsicher zu machen bzw. eine Benachrichtigung zu erhalten, wenn das System nicht mehr läuft.

Im folgendem Skript habe ich dazu einfach einen Ausgang in die Schleife gebaut. Diesen will ich abgreifen und auf ein frei programmierbares Relais der Alarmanlage legen. Wenn das Programm ausfällt ändert sich der Zustand des Kontaktes und die eigentliche Alarmanlage soll ein Störungsalarm senden.
Ich bin mir nur nicht sicher, wie valide dieses Verfahren ist.

Hier ist das vorläufige Skript. Für Verbesserungsvorschläge im Code bin ich auch sehr dankbar :)

Code: Alles auswählen

#!/usr/bin/python3
import os
import RPi.GPIO as IO
import time

TelephonNumbers = open("telephonnumbers.txt","r")
IO.setmode(IO.BCM)
AlarmPin = 22
ArmPin = 5
ErrorPin = 17
SystemStatus = 26
IO.setup(SystemStatus, IO.OUT)
IO.setup(AlarmPin, IO.IN, pull_up_down=IO.PUD_DOWN)
IO.setup(ArmPin, IO.IN, pull_up_down=IO.PUD_DOWN)
IO.setup(ErrorPin, IO.IN, pull_up_down=IO.PUD_DOWN)


def ArmUnarm(channel):
    if IO.input(ArmPin) == 0:
        os.system('echo `date` Scharf >> /home/pi/Alarm/alarm-system-dialer/log.txt')

    else:
        os.system('echo `date` Unscharf >> /home/pi/Alarm/alarm-system-dialer/log.txt')

def Error(channel):
    if IO.input(ErrorPin) == 0:
        os.system('echo `date` Stoerung >> /home/pi/Alarm/alarm-system-dialer/log.txt')

    else:
        os.system('echo `date` Stoerung zurueckgesetzt >> /home/pi/Alarm/alarm-system-dialer/log.txt')

def AlarmDetection(channel):
    if IO.input(AlarmPin) == 0:
        os.system('echo `date` Alarm >> /home/pi/Alarm/alarm-system-dialer/log.txt')
       # for number in TelephonNumbers.readlines():
           # os.system('echo Alarmanlage meldet: Einbruch! | sudo gammu-smsd-inject TEXT %s' %(number))

    else:
        os.system('echo `date` Alarm zurueckgesetzt >> /home/pi/Alarm/alarm-system-dialer/log.txt')
       # os.system('echo Alarmanlage meldet: Alarm zurueckgesetzt | sudo gammu-smsd-inject TEXT %s' %(number))

IO.add_event_detect(AlarmPin, IO.BOTH, callback = AlarmDetection, bouncetime = 100)
IO.add_event_detect(ArmPin, IO.BOTH, callback = ArmUnarm, bouncetime = 100)
IO.add_event_detect(ErrorPin, IO.BOTH, callback = Error, bouncetime = 100)

try:
    while True:
        time.sleep(0.1)
        IO.output(SystemStatus, IO.LOW)

except KeyboardInterrupt:
IO.cleanup()
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Stevo: aus welchem Grund soll das Programm abbrechen? Und wenn Du sowieso systemd benutzt, wird ein abgebrochenes Programm eh neu gestartet. Der Fall, dass das Programm nicht mehr läuft, ist damit so gut wie ausgeschlossen.

Zeile 6: eine Datei die man öffnet, sollte man auch wieder schließen. Global irgendwelche Dateien aufzumachen, ist daher seltsam.
Zeile 36: wenn man einmal die Datei gelesen hat, ist der Dateizeiger am Ende und weitere Leseversuche enden mit leerem Ergebnis.
Statt os.system sollte man eine passende Methode aus `subprocess` benutzen. Hier aber, um eine Datei zu schreiben, benutzt man direkt Python-Routinen:

Code: Alles auswählen

def arm_unarm(_):
    msg = "Scharf" if IO.input(ArmPin) == 0 else "Unscharf"
    with open(LOGFILE, "a") as log:
        log.write('{} {}\n'.format(datetime.datetime.now(), msg)
Besser noch, das schreiben in ein Logfile als eigene Funktion definieren, oder gleich `logging´ benutzen. Statt an 5 Stellen jeweils den selben Dateinamen zu schreiben, definiert man eine Konstante am Anfang des Programms.

Zur Schreibweise: Variablen und Funktionen schreibt man komplett klein_mit_unterstrich und Konstanten GROSS_MIT_UNTERSTRICH (ALARM_PIN).
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

@Sirius3: Meines Wissens werden Services unter systemd nicht automatisch neu gestartet. Dieses Verhalten muss in eigenen Units konfiguriert werden (Restart=…), bei mitgelieferten Units ist das oft schon der Fall.
Antworten