Python - globale Variable

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
sunshineh
User
Beiträge: 22
Registriert: Dienstag 3. Januar 2017, 11:21

Hallo,

ich bin Anfängerin in python und möchte gerne einen Eingang prüfen und mir durch eine Ausgabe mitteilen, wenn der Eingang länger als x-Sekunden auf 0 ist. Vom Grundgedanken hab ich mir das wie unten dargestellt gedacht:

Code: Alles auswählen

def cntUnterbrochen(u):
        if u == 0:
                cnt = cnt + 1
                print("cnt aufaddieren = ", cnt)
        if u == 1:
                cnt = 0
                print("cnt rucksetzen = ", cnt)
def loop():
        while True:
                time.sleep(1)
                cntUnterbrochen(GPIO.input(BtnPin))
                print("main")
              
if __name__ == '__main__':     # Program start from here
    setup()
    try:
        loop()
    except KeyboardInterrupt:  # When 'Ctrl+C' is pressed, the child program destroy() will be  executed.
        destroy()
Allerdings bekomm ich hier die Meldung
cnt = cnt + 1
UnboundLocalError: local variable "cnt" referenced before assignment

Wie kann ich das lösen? Wäre für einen Tipp sehr dankbar!!
Zuletzt geändert von Anonymous am Dienstag 3. Januar 2017, 11:40, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@sunshineh: *Nicht* lösen sollte man das mit globalen Variablen. Entweder Du machst `cntUnterbrochen()` mehr zu einer ”echten” Funktion und übergibst den Zählstand als Argument und den veränderten Zählstand als Rückgabewert an den Aufrufer, oder Du müsstest Du mal mit objektorientierter Programmierung (OOP) auseinandersetzen.

Der Funktionsname entspricht übrigens in der Schreibweise nicht nicht dem Style Guide for Python Code und inhaltlich verwendet man für Funktionen üblicherweise einen Namen der eine Tätigkeit beschreibt. Also die Tätigkeit die von der Funktion oder Methode ausgeführt wird.

Namen sollte man nicht kryptisch Abkürzen. Wenn man sich nicht traut so schmutzige Wörter wie „c*nt“ auszuschreiben, kann man auch was anderes, vielleicht auch passenderes verwenden, wie `count`. ;-) Einbuchstabige Namen ohne jegliche erkennbare Bedeutung sollte man komplett vermeiden. Ausnahmen wären sehr begrenzte Gültigkeitsbereiche, beispielsweise in „list comprehensions“, Generatorausdrücken, oder anonymen Funktionen, aber sicher nicht ein `u` als Argument einer normalen Funktion. Verschiedene natürliche Sprachen bei Namen zu mischen ist auch nicht so praktisch. Üblich ist Englisch.

Inhaltlich passt das Programm nicht so ganz zur Beschreibung, denn es wird nicht geprüft ob x Sekunden wirklich ununterbrochen der Stromkreis unterbrochen ist, sondern nur einmal pro Sekunde. Dazwischen kann sich der Zustand unerkannt beliebig oft ändern.

`destroy()` ist ziemlich sicher eine Funktion, oder allgemein auf jeden Fall ein „aufrufbares Objekt“, aber im Sprachgebrauch von Python kein Kindprogramm („child program“). Ausserdem vermute ich stark, dass dieser Aufruf eigentlich in einen ``finally``-Zweig zu dem ``try`` gehört und nicht nur bei Strg+C ausgeführt werden soll, sondern *immer* bevor das Programm endet.
BlackJack

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import time
from RPi import GPIO

BUTTON_PIN = 42


def setup():
    GPIO.setup(BUTTON_PIN, GPIO.IN)
    # ...


def destroy():
    GPIO.cleanup()
    # ...


def count_interrupted(is_interrupted, count):
    if is_interrupted:
        count += 1
        print('count aufaddieren =', count)
    else:
        count = 0
        print('count rucksetzen =', count)

    return count


def loop():
    interrupted_count = 0
    while True:
        time.sleep(1)
        interrupted_count = count_interrupted(
            not GPIO.input(BUTTON_PIN), interrupted_count
        )
        print('main')


def main():
    setup()
    try:
        loop()
    except KeyboardInterrupt:
        pass
    finally:
        destroy()
    

if __name__ == '__main__':
    main()
sunshineh
User
Beiträge: 22
Registriert: Dienstag 3. Januar 2017, 11:21

Hi,
vielen Dank für deine Antwort! Den Code hab ich mittlerweile so angepasst, bin aber über folgendes Punkte noch nicht glücklich:
1.Das Warten, bis der nächste Alarm ausgelöst werden darf, habe ich mit "time.sleep(xxx)" realisiert. Was natürlich dumm ist, da in dieser Zeit der Prozessor ja nicht sinnvoll andere Anfgaben übernehmen kann.
2.Das Programm kommt immer wieder ins Stoppen und "deaktiviert sich selbst" mit der Meldung:
"...login raise SMTPAuthenticationError(code, resp)
SMTPAuthenticationError: (454, '4.7.0 Too many loginattempts, please try again later. ...)
3.Könnte das Programm theoretisch so Jahre lang auf dem Raspberry laufen, oder würde die Shell vollgeschrieben und an den Printmeldungen eingehen?

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import RPi.GPIO as GPIO
import smtplib
import time
import paho.mqtt.client as mqtt

BtnPin = 13
Gpin   = 12
Rpin   = 11

TimeOut_Alert_sec = 90
TimeOut_Disconnected_sec = 600

fromaddr = 'Raspberry Pi Alarmsystem'
toaddrs  = 'xxx@googlemail.com'
username = 'xxx@googlemail.com'
password = 'xxx'

msg1_part1 = """From: Raspberry Pi Alarmsystem
To: xxxt@googlemail.com
Subject: """
msg1_part2 = """Zone17: Lichtschranke1

"""

msg2_part1 = """From: Raspberry Pi Alarmsystem
To: xxxt@googlemail.com
Subject: """
msg2_part2 = """Zone17: Lichtschranke1 (dauerhaft)

"""
def on_connect(client,userdata,flags,rc):
        print("Connected with result code "+str(rc))

client = mqtt.Client()
client.on_connect = on_connect

client.connect("localhost",1883,60)
client.loop_start()

def setup():
        GPIO.setmode(GPIO.BOARD)       # Numbers GPIOs by physical location
        GPIO.setup(Gpin, GPIO.OUT)     # Set Green Led Pin mode to output
        GPIO.setup(Rpin, GPIO.OUT)     # Set Red Led Pin mode to output
        GPIO.setup(BtnPin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)    # Set BtnPin's mode is input, and pull down to low
        GPIO.add_event_detect(BtnPin, GPIO.BOTH, callback=detect, bouncetime=1000)

def Led(x):
        if x == 1:
                GPIO.output(Rpin, 1)
                GPIO.output(Gpin, 0)
        if x == 0:
                GPIO.output(Rpin, 0)
                GPIO.output(Gpin, 1)

def SendMail_detected(cnt):  # x=Type, lastMail_time=letzte Zeit wann die Mail gesendet wurde
        # Sind weniger als x Sekunden seit dem letzten Aufruf dieser Funktion vergangen?
        # print("Mail wobei cnt ="+str(cnt))        
        server = smtplib.SMTP('smtp.gmail.com:587')
        server.starttls()
        server.login(username,password)
        date = time.strftime("%Y%m%d")
        zeit = time.strftime("%H:%M")
        if cnt < 1:
                WriteFile_detected()
                server.sendmail(fromaddr, toaddrs, msg1_part1+" "+date+" "+zeit+" "+msg1_part2+""+date+" "+zeit+" "+msg1_part2+"("+str(TimeOut_Alert_sec)+" Seconds blocked)")
                print("Lichtschranke unterbrochen > Mail gesendet")
                client.publish("alarm/lichtschranke",date+" "+zeit+" Zone17: Lichtschranke1")
        server.quit()
        # Zeitpunkt der letzten Ausfuehrung speichern:
        if cnt < 1:
                return 99
        else:
                return 0

def WriteFile_detected():
        date = time.strftime("%Y%m%d")
        zeit = time.strftime("%H:%M")
        fileout = open("/home/pi/Desktop/Lichtschranke.txt","a")
        time.sleep(0.1)
        fileout.write(date+";"+zeit+"; Die Lichtschranke wurde unterbrochen \n")
        time.sleep(0.1)
        fileout.close()
        print("Log-File geschrieben")

def SendMail_disconnected(count):  # x=Type, lastMail_time=letzte Zeit wann die Mail gesendet wurde
        if count != TimeOut_Disconnected_sec/TimeOut_Alert_sec:
                # print("Disconnected")
                return
        # Sind weniger als x Sekunden seit dem letzten Aufruf dieser Funktion vergangen?
        WriteFile_disconnected()
        server = smtplib.SMTP('smtp.gmail.com:587')
        server.starttls()
        server.login(username,password)
        date = time.strftime("%Y%m%d")
        zeit = time.strftime("%H:%M")
        server.sendmail(fromaddr, toaddrs, msg2_part1+" "+date+" "+zeit+" "+msg2_part2+""+date+" "+zeit+" "+msg2_part2+"("+str(TimeOut_Disconnected_sec/60)+" Minutes disconnected)")
        client.publish("alarm/lichtschranke",date+" "+zeit+" Zone17: Lichtschranke1 (dauerhaft)")
        #print("Lichtschranke immer noch unterbrochen > erneut Mail gesendet")
        server.quit()
        # Zeitpunkt der letzten Ausfuehrung speichern:

def WriteFile_disconnected():
        date = time.strftime("%x")
        zeit = time.strftime("%X")
        fileout = open("/home/pi/Desktop/Lichtschranke.txt","a")
        time.sleep(0.1)
        fileout.write(date+";"+zeit+"; Die Lichtschranke wurde dauerhaft unterbrochen \n")
        time.sleep(0.1)
        fileout.close()
        #print("Log-File disconnected geschrieben")


def detect(count):
        if GPIO.input(BtnPin) == GPIO.LOW:      # Lichtschranke wurde unterbrochen               
                back= SendMail_detected(count)				
                SendMail_disconnected(count)
                count += 1				
                Led(1)
                if back == 99:
                        time.sleep(TimeOut_Alert_sec)
                #print("detect count = "+str(count))

        if GPIO.input(BtnPin) == GPIO.HIGH:     # Lichtschranke wieder frei
                count = 0
                Led(0)
        #print("count ="+str(count)+" >> is back")                
        return count

def loop():
        count = 0
        while True:
                time.sleep(1)	
                #print("vor loop "+str(count))
                count = detect(count)
                #print("nach loop "+str(count))

def destroy():
        GPIO.output(Gpin, GPIO.HIGH)       # Green led off
        GPIO.output(Rpin, GPIO.HIGH)       # Red led off
        GPIO.cleanup()                     # Release resource

def main():     # Program start from here
        setup()
        try:
                loop()
        except KeyboardInterrupt:  # When 'Ctrl+C' is pressed, the child program destroy() will be  executed.
                pass
        finally:
                destroy()

if __name__ == '__main__':     # Program start from here
        main()

Zuletzt geändert von Anonymous am Sonntag 22. Januar 2017, 16:03, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@sunshineh: Du baust jede Sekunde eine Verbindung auf und wunderst Dich, dass das dem Server zu viel wird? Du detektierst gleichzeitig per add_event_detect als auch per loop Flanken. Was denn nun? Die Functionen SendMail_detected und SendMail_disconnected, sowie WriteFile_detected und WriteFile_disconnected sind quasi identisch. Versuche mal doppelten Code zu entfernen. Was sollen denn die sleep in WriteFile_detected bewirken?

Versuch erst einmal eine saubere Lichtschrankensteuerung hinzubekommen, bevor Du anfängst, alle möglichen Nachrichten zu verschicken. Wenn mal die Grundfunktionalität tut, kannst Du solche Gimmicks immer noch einfügen.
sunshineh
User
Beiträge: 22
Registriert: Dienstag 3. Januar 2017, 11:21

Hallo, dane für die Hinweise

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
import RPi.GPIO as GPIO
import smtplib
import time
import paho.mqtt.client as mqtt

BtnPin = 13
Gpin   = 12
Rpin   = 11
Counter = 0
TimeOut_Alert_sec = 90
TimeOut_Disconnected_sec = 600

fromaddr = 'Raspberry Pi Alarmsystem'
toaddrs  = 'xxx@googlemail.com'
username = 'xxx@googlemail.com'
password = 'xxx'

msg1_part1 = """From: Raspberry Pi Alarmsystem
To: xxxt@googlemail.com
Subject: """
msg1_part2 = """Zone17: Lichtschranke1

"""

msg2_part1 = """From: Raspberry Pi Alarmsystem
To: xxx@googlemail.com
Subject: """
msg2_part2 = """Zone17: Lichtschranke1 (dauerhaft)

"""
def on_connect(client,userdata,flags,rc):
        print("Connected with result code "+str(rc))

client = mqtt.Client()
client.on_connect = on_connect

client.connect("localhost",1883,60)
client.loop_start()

def setup():
        GPIO.setmode(GPIO.BOARD)       # Numbers GPIOs by physical location
        GPIO.setup(Gpin, GPIO.OUT)     # Set Green Led Pin mode to output
        GPIO.setup(Rpin, GPIO.OUT)     # Set Red Led Pin mode to output
        GPIO.setup(BtnPin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)    # Set BtnPin's mode is input, and pull down to low
        GPIO.add_event_detect(BtnPin, GPIO.BOTH, callback=detect, bouncetime=1000)

def Led(x):
        if x == 1:
                GPIO.output(Rpin, 1)
                GPIO.output(Gpin, 0)
        if x == 0:
                GPIO.output(Rpin, 0)
                GPIO.output(Gpin, 1)

def SendMail(reason):  # reason=detected / reason=disconnected       		
        date = time.strftime("%Y%m%d")
        zeit = time.strftime("%H:%M")
        WriteFile(reason)
        server = smtplib.SMTP('smtp.gmail.com:587')
        server.starttls()
        server.login(username,password)
        if reason == "detected":
                server.sendmail(fromaddr, toaddrs, msg1_part1+" "+date+" "+zeit+" "+msg1_part2+""+date+" "+zeit+" "+msg1_part2+"("+str(TimeOut_Alert_sec)+" Seconds blocked)")
                client.publish("alarm/lichtschranke",date+" "+zeit+" Zone17: Lichtschranke1")
        if reason ==  "disconnected":
                server.sendmail(fromaddr, toaddrs, msg2_part1+" "+date+" "+zeit+" "+msg2_part2+""+date+" "+zeit+" "+msg2_part2+"("+str(TimeOut_Disconnected_sec/60)+" Minutes disconnected)")
                client.publish("alarm/lichtschranke",date+" "+zeit+" Zone17: Lichtschranke1 (dauerhaft)")
        server.quit()
		
def WriteFile(reason):
        date = time.strftime("%Y%m%d")
        zeit = time.strftime("%H:%M")		
        fileout = open("/home/pi/Desktop/Lichtschranke.txt","a")
        time.sleep(0.1)
        if reason == "detected":
                fileout.write(date+";"+zeit+"; Die Lichtschranke wurde unterbrochen \n")
        if reason == "disconnected":
                fileout.write(date+";"+zeit+"; Die Lichtschranke wurde dauerhaft unterbrochen \n")		
        time.sleep(0.1)
        fileout.close()
        print("Log-File geschrieben")

def detect():
	if Counter < 1:
		SendMail("detected")
	if Counter == TimeOut_Disconnected_sec/TimeOut_Alert_sec:
		SendMail("disconnected")


def loop():
        global Counter
        while True:
                time.sleep(1)
                if GPIO.input(BtnPin) == GPIO.HIGH:     # Lichtschranke wieder frei					
                        Led(0)
                        Counter = 0
                if GPIO.input(BtnPin) == GPIO.LOW:     # Lichtschranke unterbrochen
                        Led(1)
                        Counter += 1

def destroy():
        GPIO.output(Gpin, GPIO.HIGH)       # Green led off
        GPIO.output(Rpin, GPIO.HIGH)       # Red led off
        GPIO.cleanup()                     # Release resource

def main():     # Program start from here
        setup()
        try:
                loop()
        except KeyboardInterrupt:  # When 'Ctrl+C' is pressed, the child program destroy() will be  executed.
                pass
        finally:
                destroy()

if __name__ == '__main__':     # Program start from here
        main()
	
Ist es nun besser so, oder kann man noch was verbessern?
Zuletzt geändert von Anonymous am Dienstag 24. Januar 2017, 23:16, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

Solange ein ``global`` im Code ist kann man immer etwas verbessern — das ``global`` loswerden.
sunshineh
User
Beiträge: 22
Registriert: Dienstag 3. Januar 2017, 11:21

Sorry, für mich ist Python ganz neu und ich versteh nicht wirklich wie ich den Parameter "Counter" sonst der Interrupt übergeben könnte.
Im Internet hab ich nur Beispiele mit "globalen Variablen" gefunden.

Kann ich eigentlich auch den Interrupt testen, ohne ständig durch die Lichtschranke rennen zu müssen?

Ich dachte eigentlich, wenn ich das Python Skritp mit F5 ausführe und ich keine Fehlermeldung erhalte, müsste das Programm fehlerfrei durchlaufen. Allerdings habe ich erst bei der wirklichen Interruptausführung folgenden Error erhalten:
"TypeError: detect() takes no arguments (1 given)"
BlackJack

Spätestens an dieser Stelle kommt man wohl um objektorientierte Programmierung nicht herum.

Wenn man dann zusätzlich noch darauf achtet leicht testbaren Code zu schreiben und tatsächlich Unittests dafür schreibt, kann man die Programmlogik auch testen ohne die Hardware auszulösen.

Wobei man zum Testen auch einen Aufbau mit Taster statt Lichtschrankte realisieren könnte damit man da nicht immer durchlaufen muss.

`client` ist auch eine globale Variable die da so nicht stehen sollte.

Beim ermitteln der Textfragmente für Datum und Uhrzeit machst Du einen Fehler: So etwas darf man nicht getrennt ermitteln, sondern immer nur beides aus *einem* Zeitpunkt. Sonst kann es passieren dass Du beispielsweise das Datum ganz knapp vor Mitternacht ermittelst und die Uhrzeit ganz knapp nach oder um Mitternacht und zusammengenommen sind die Informationen dann um fast 24 Stunden falsch.

Am besten verwendet man `datetime.now()` mit `datetime` aus dem `datetime`-Modul und verwendet die `format()`-Methode auf Zeichenketten um aus dem Objekt dann die Teilzeichenketten zu erstellen.

In dem Zusammenhang: Ebenfalls mit `format()` wird man diese Fragmente der E-Mail-Vorlagen und das wirklich sehr schwer lesbare zusammenstückeln mit ``+`` und `str()` los.

Laufzeitfehler treten erst zur Laufzeit auf. Wie sollte Python auch wissen welche Funktion oder Methode zur Laufzeit mit welchen Argumenten aufgerufen wird?
Antworten