Starten und Beenden eines Programms mit subprocess?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Ich hab einen Pi mit Kamera und PIR (Bewegungsmelder). Was bisher geht: Bewegung wird erkannt > SMS wird verschickt > Bild wird aufgenommen > Video wird aufgenommen > Bild wird per Mail verschickt.
Nun möchte ich das Programm/Skript per Mail beenden und wieder starten. Ich hab da schon mal mit dem threading Modul rumgespielt. Beenden geht mit der Buroalo-Methode: sys.exit(0)
Nun stellt sich mir die Frage, ob ich nicht ein neues Skript erstellen soll/muss, welches nur auf E-Mail-Befehle hört und dann die jeweiligen Befehle ausführt:

Code: Alles auswählen

class EmailThread(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        while True:
                          ...

                if habe_mail == 1:
                    if var_subject == 'STOP':
			# subprocess.Popen("Eine Lösung, wie ich die PID eruiere und dann kille")
                        sys.exit(0)

#                    elif var_subject == 'START':
#                        subprocess.Popen(['ueberwachung.py'])
                    else:
                        print 'ungültiger Befehl'
                time.sleep(360) # google dreht durch, wenn innerhalb weniger Minuten mehrere Anfragen an den Server gehen.
Da ich mich mit Linux noch nicht wirklich gut auskenne, wollte ich mal fragen, wie man - sofern dies die richtige Lösung ist - sauber ein Programm startet und beendet?

mfg
BlackJack

@lackschuh: Warum willst Du das denn tun?

Wenn das ein Dienst/Daemon ist, dann würde ich so etwas wie supervisord verwenden um zu überwachen ob das Programm läuft und ggf. neu gestartet werden muss. Das Programm selbst muss sich dann nur selbst beenden können. Und dafür ist `sys.exit()` die richtige Funktion.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Die Idee ist die:
Ich geh aus dem Haus und schalte die "Ueberwachungskamera" mittels eMail ein. Wenn ich dann wieder nach Hause komme, muss ich das Programm beenden, bevor ich gefilmt werde und meine limitierten SMS aufbrauche. Wie gesagt, wenn ich das threading Modul im Hauptprogramm einbinde, dann kann ich das Programm bequem per Mail beenden. Starten muss ich es bis jetzt aber manuell über SSH. Und genau das versuche ich nun mittels Mail zum Laufen zu kriegen.

mfg
BlackJack

@lackschuh: Um es per Mail zu starten muss es doch bereits laufen um überhaupt auf eine Mail reagieren zu können. Warum musst Du es denn überhaupt beenden und starten, warum nicht einfach per Mail dem Programm sagen es soll überwachen / nicht überwachen, ohne das man es beenden muss?
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

BlackJack hat geschrieben:..warum nicht einfach per Mail dem Programm sagen es soll überwachen / nicht überwachen, ohne das man es beenden muss?
Genau das wäre ja eigentlich die Ideallösung nur fehlt mir dafür die Logik. Ich müsste also nur den Bewegungsmelder ein- und ausschalten?

Hier mal das Wichtigste im Überblick. Die EmailThread Klasse steht weiter oben:

Code: Alles auswählen

import RPi.GPIO as GPIO

# instead of physical pin numbers
GPIO.setmode(GPIO.BCM)
GPIO_PIR = 4
# Set pin as input
GPIO.setup(GPIO_PIR,GPIO.IN)

aktueller_zustand  = 0
vorherigen_zustand = 0

def main():
    try:
        # Loop until PIR output is 0
        while GPIO.input(GPIO_PIR) == 1:
            aktueller_zustand  = 0
        print "Ready"
        # Loop until users quits with CTRL-C
        while True:
            # Read PIR state
            aktueller_zustand = GPIO.input(GPIO_PIR)
            if aktueller_zustand == 1 and vorherigen_zustand == 0:
                # PIR is triggered
                start_time = time.time()
                print "Bewegung erkannt"
                #send_sms()
                #Mail Befehl
                email_thread = EmailThread()
                email_thread.start()
                # Record previous state
                vorherigen_zustand = 1

            elif aktueller_zustand == 0 and vorherigen_zustand == 1:
                # PIR has returned to ready state
                stop_time = time.time()
                print "Ready!",
                elapsed_time = int(stop_time-start_time)
                print "(Abgelaufene Zeit: " + str(elapsed_time) + " secs)"
                vorherigen_zustand = 0
Original Skript von hier.
BlackJack

@lackschuh: Musst Du das mit der E-Mail denn in einen eigenen Thread auslagern? Kann man das nicht in die Schleife integrieren in dem man prüft wie lange der letzte Test her ist und falls länger als `x` Schleifendurchläufe dann nach E-Mail schaut? Dann braucht man nur noch ein Flag das entscheidet ob eine SMS versendet werden soll oder nicht.

Ansonsten würde ich das Programm grundsätzlich mehr und anders aufteilen; alles was parallel passieren soll in eigenen Threads ausführen und im Hauptprogramm eine Schleife schreiben die Ereignisse aus einer Queue.Queue abarbeitet, in die die anderen Threads ihre Ereignisse melden, also zum Beispiel „Mail bekommen” oder „Zustand vom Sensor verändert”.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Ich komm leider erst jetzt dazu, hier weiter zu machen, da ich mein RPi zwischenzeitlich für was anderes gebraucht habe.
BlackJack hat geschrieben:Musst Du das mit der E-Mail denn in einen eigenen Thread auslagern?
Weiss es ehrlich gesagt nicht.

Das mit subprocess ist keine gute Idee bzw. nützt hier gar nichts. Ich müsste eher in der run() Methode der Klasse EmailThread() irgendwie versuchen, den Infrarotsensor mit der GPIO Nr. 4 zu deaktivieren und dann bei Bedarf wieder zu aktivieren. Der Aufbau des Programms sieht so aus:

Code: Alles auswählen

import RPi.GPIO as GPIO
# instead of physical pin numbers
GPIO.setmode(GPIO.BCM)
# Define GPIO to use on Pi
GPIO_PIR = 4
# Set pin as input
GPIO.setup(GPIO_PIR,GPIO.IN)  

pfad_bilder = '/home/pi/Python/Camera/bilder/'
pfad_videos = '/home/pi/Python/Camera/videos/'
log = '/home/pi/Python/Camera/log'

def log_to_file(nachricht):
    pass

def send_sms():
    pass

def bild_aufnehmen():
    pass

def video_aufnehmen():
    pass

def send_mail(to, subject, text, files=[]):
    pass

class EmailThread(threading.Thread):
    #Code ist weiter oben

def main():
    # Code ist weiter oben
[/size]

Mit GPIO.cleanup() werden GPIO Einstellungen, also die Zuweisungen der Pins, zurückgesetzt. Wäre dies ggf. eine Möglichkeit, aus der EmailThread() Klasse heraus, den Sensor zu deaktivieren? Allerdings gäbe es dann wohl massive Probleme mit der main()-Funktion und dessen Schleife.
BlackJack

@lackschuh: Du müsstest den Bewegungsmelder ja nicht einmal (de)aktivieren, sondern Dir nur vermerken ob/wie auf dessen Signale reagiert werden soll. Protokollieren kann man Änderungen ja zum Beispiel immer noch, nur Alarm sollte es halt nicht auslösen.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Ja, der Bewegungsmelder könnte man auch laufen lassen. Mir fehlt aber gerade die Logik, wie ich dies umsetzen könnte. Also sobald per Mail ein Befehl wie 'START' oder eben 'STOP' eingeht, muss reagiert werden. Aber genau an diesem Punkt klemmts bei mir :oops:
BlackJack

@lackschuh: Wenn Start/Stop-Signale, zum Beispiel per Mail kommen, setzt man ein Flag das angibt ob die Alarmanlage „scharf” geschaltet ist, und eventuell bei aktivem Alarm bei Stop den Alarm beendet. Und bei Signalen vom Sensor schaut man auf das Flag ob man Alarm geben muss oder nicht.
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Ok, danke für die Infos. Da muss ich mich zuerst einlesen bzw. mich mit den Basics auseinandersetzen. Ich glaube, du meinst, dass ich mich mit Boolean also True und False auseinandersetzen soll/muss?

sry für meine Bescheidenheit :roll:
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Hallo

Hab mir heute Nachmittag wieder mal ein wenig Zeit genommen. Zu Beginn des Programms habe ich eine Variable oder von mir aus auch ein Flag namens alarm_system mit dem Wert 0 gesetzt. In der EmailThread Klasse wird bzw. sollte dann je nach Befehl der Wert von 0 auf 1 gesetzt werden. Soweit auch so schlecht. Denn sobald der erste Befehl eingegangen ist, läuft die 'endlos'-Schleife durch und es wird nicht mehr reagiert auf ein weiterer Befehl in der EmailThread Klasse. Was habe ich da vergessen zu berücksichtigen bzw wie kann man in einer while True Schleife auf Werte in der EmailThread Klasse zugreifen?


Code: Alles auswählen

alarm_system = 0

class EmailThread(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        global alarm_system
        while True:
			.......
            s.close()
            s.logout()

            if habe_mail == 1:
                if var_subject == 'STOP':
                    print 'System stop'
                    alarm_system = 0
                    return alarm_system
                
                elif var_subject == 'START':
                    print 'System gestartet'
                    alarm_system = 1
                    return alarm_system
                    
            print '****** Checke Mail ******'
            time.sleep(60)

def main():
    global alarm_system
    email_thread = EmailThread()
    email_thread.start()

    while True:
        print alarm_system
        if alarm_system == 1:
            print 'Alarm scharf'
        
        elif alarm_system == 0:
            print 'Alarm abgeschaltet'

        time.sleep(30)

if __name__ == '__main__':
    main()
[/size]

Wäre es Sinnvoll eine Funktion zu erstellen, welche in regelmässigen Abständen den Status in der EmailThread Klasse überprüft zB

Code: Alles auswählen

def check_alarm():
    global alarm_system
    email_thread = EmailThread()
    email_thread.start()
    print 'check_alarm', alarm_system
    time.sleep(10)
[/size]

Wenn ich die Funktion verwende und in der while True Schleife aufrufe, dann wird augenscheinlich die EmailThread Klasse drei mal gleichzeitig überprüft:

Code: Alles auswählen

check_alarm 0
****** Checke Mail ******
0
Alarm ist aus
****** Checke Mail ******
check_alarm 0
START
1
Alarm ist scharf
****** Checke Mail ******
check_alarm 1
****** Checke Mail ******
****** Checke Mail ******
1
Alarm ist scharf
check_alarm 1
****** Checke Mail ******
****** Checke Mail ******
1
Alarm ist scharf
****** Checke Mail ******
****** Checke Mail ******
check_alarm 1
****** Checke Mail ******
1
Alarm ist scharf
STOP
check_alarm 0
****** Checke Mail ******
****** Checke Mail ******
****** Checke Mail ******
0
Alarm ist aus
****** Checke Mail ******
****** Checke Mail ******
****** Checke Mail ******
[/size]
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@lackschuh: wenn etwas in der Email-Funktion schief läuft, solltest Du auch die Email-Funktion posten. Wenn Du bei jedem check_alarm einen neuen Email-Thread startest, gibt das natürlich ein Durcheinander. Zur Interprozesskommunikation nimmt man normalerweise auch keine globalen Variablen, sondern Semaphoren.
Der gezeigte Programmablauf passt nicht zum gezeigten Code, was soll uns das also sagen?
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Nachtrag:

Code: Alles auswählen

class EmailThread(threading.Thread):
    
    def __init__(self):
        threading.Thread.__init__(self)
        
    def run(self):
        global alarm_system
        while True:
            var_subject = 'leer'
            habe_mail = 0
            s = imaplib.IMAP4_SSL('xxx.xxxxx.xx', 993)
            s.login(USERNAME,PASSWORD)
            s.select("INBOX")
            
            typ, data = s.SEARCH(None, 'FROM', 'xxxxxx@xxxxx.com')
            if data[0] != '':
                habe_mail = 1
                typ2, data2 = s.fetch(data[0], '(RFC822)')
                for response_part in data2:
                    if isinstance(response_part, tuple):
                        msg2 = email.message_from_string(response_part[1])
                        var_subject = msg2['subject']
                        print var_subject
                s.store(data[0], "+FLAGS", '(\\Deleted)')
                s.expunge()
            s.close()
            s.logout()

            if habe_mail == 1:
                if var_subject == 'STOP':
                    alarm_system = 0
                    return alarm_system
                
                elif var_subject == 'START':
                    alarm_system = 1
                    return alarm_system
                    
            print '****** Checke Mail ******'
            time.sleep(60)

lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

@Sirius3

Danke für die Info. Ja, die Funktion ist sinnlos, da diese bei jedem Aufruf einen neuen Thread erzeugt. Wenn ich aber den EmailThread in der Hauptfunktion aufrufe

Code: Alles auswählen

def main():
    email_thread = EmailThread()
    email_thread.start()
    while True:
        print threading.enumerate()
        if alarm_system == 0:
            print 'Alarm ist aus'
        
        elif alarm_system == 1:
            print 'Alarm ist scharf'

        time.sleep(30)
[/size]

dann habe ich das Problem, dass sobald der erste Befehl per Mail eingegangen ist, nicht mehr weiter die 'while True' Schleife in der EmailThread Klasse durchlaufen wird. Was müsste ich da noch berücksichtigen?

Code: Alles auswählen

[<EmailThread(Thread-1, started 5836)>, <_MainThread(MainThread, started 7180)>]
Alarm ist aus
****** Checke Mail ******
[<EmailThread(Thread-1, started 5836)>, <_MainThread(MainThread, started 7180)>]
Alarm ist aus
[<EmailThread(Thread-1, started 5836)>, <_MainThread(MainThread, started 7180)>]
Alarm ist aus
START
[<_MainThread(MainThread, started 7180)>]
Alarm ist scharf
[/size]
BlackJack

@lackschuh: Du verlässt diese ``while``-Schleife mit ``return``. Lass das einfach bleiben. Der Rückgabewert wird ja sowieso nirgends verwendet, warum machst Du das überhaupt?
lackschuh
User
Beiträge: 281
Registriert: Dienstag 8. Mai 2012, 13:40

Vielen Dank BlackJack, es läuft nun durch.
Ich hab in dieser Klasse soviel rumgepfuscht treu nach dem Motto 'probieren statt studiere' so dass ich den Überblick und die Logik nicht mehr überblickte.
Antworten