Threads auf Module auslagern möglich

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo, bin auch jemand der in Sachen Raspberry und Python noch am Einarbeiten ist. Ich habe ein Projekt in dem ich mehrere Sachen auf einmal erledigen möchte.

    1. Signalüberwachung: Alle von mir definierten EIngänge sollen überwacht werden und bei Änderungen soll ein Eintrag in eine Tabelle (SQLITE) gemacht werden

    2. Signaländerungen melden: Die von Punkt 1. eingetragenen Meldungen sollen wenn eine Verbindung besteht versendet werden

    3. Ein TCP/IP Server soll eingehende Nachrichten verarbeiten können

    Da die Aufgaben zum Teil auf gleiche Variablen (lesend) zugreifen möchte ich diese Aufgaben mit Threads in einem Programm realisieren. Doch wie ich es von anderen Softwareprojekten kenne wird ein Projek,t wenn es nicht von Anfang grob durchgeplant wurde, sehr schnell unübersichtlich. Vielleicht bin ich verwöhnt von anderen Programmiersprachen aber wenn ich mir vorstelle mein gesamter Code steckt am Ende nur in einer Datei ist mir nicht wohl dabei. Dehalb mache ich mir zunächst gedanken um die Grundstruktur bevor es mit dem Tippen losgeht.

    Mein Wunsch: Das Projekt Modular aufzubauen

    Modul 1: main.py (hier werden die Threads gestartet)
    Modul 2: Thread1 für Signalueberwachung
    Modul 3: Thread2 für die Verarbeitung der Signaländerungen
    Modul 4: Thread2 für die TCP/IP Kommunikation
    usw.

    Frage:
    Ist ein solcher Aufbau mit Python möglich oder gibt es eine bessere Methode um ein solches Projekt übersichtlich aufzubauen? Wichtig ist, dass die Threads auf globale Variablen und evtl. auch auf globale Funktionen, die natürlich für jeden Thread gesperrt werden müssen, zugreifen können. Für konstruktive Tipps und Vorschläge bin euch dankbar"

  • Zu 1.: Interrupts
    Zu 2.: Siehe 1.
    Zu 3.: threading. Socket Server

    Alles über nur eine Scriptdatei machbar. Sofern du das sauber programmierst spielt es keine Rolle ob mehrere Funktionen die selben Variablen nutzen, die sind normalerweise eh nur innerhalb der Funktion gültig.

    Nur wenn der Code umfangreich wird, also sagen wir mal mehr als 20 Zeilen pro Funktion, machts Sinn diese in separate Dateien auszulagern - aber ansonsten macht es sone Aufsplittung nur gegenteilig: unübersichtlich


    Aber bitte halte Abstand von globalen Variablen

  • Hallo,

    also Threads sind nicht unbedingt die Wahl der Waffen unter Python... Was mit Threads geht (und was nicht) und auch wie man Daten zwischen Threads austauscht ist in der PMOTW-Serie ganz gut erklärt.

    Und Threads laufen zwar auch unter Python parallel, nichts desto trotz können sie sich auch gegenseitig ausbremsen, was am Kern von (C)Python an sich liegt. blackjack hat das in diesem Post im deutschen Python-Forum ganz gut erklärt.

    Nach meinem Verständnis benötigst du aber (eigentlich) eher eine asynchrone Verarbeitung von Daten. Dazu gibt es in Python concurrent.futures. Und wenn die Verarbeitung länger dauern kann oder auch viele Ereignisse auf einmal auftreten können, dann kann man zu so was wie Celery greifen.

    Die Signalüberwachung brauchst du aber auch IMHO nicht in einen Thread auslagern - zumindest nicht für die GPIO-Pins. Dazu kannst du eine Callback-Funktion definieren, die aufgerufen wird,wenn ein "Event" an einem Pin auftritt. Beispiele findest du dazu sicherlich reichlich - auch hier im Forum - auch z.B. auf dieser Webseite.

    Gruß, noisefloor

  • Den ersten 3 Absätzen will ich keinesfalls widersprechen @ noisefloor - wobei ich selbst für mein RoPi Projekt in einem Script 2 asyncrone Threads hab und keine Probleme ;)

    Aber zum letzten Absatz muss ich anmerken das diese "Signalüberwachung" aka Interrupts, vom "event handler" selbstständig in einen Thread abgesetzt wird, und das ist auch gut so ;). Also selber machen brauch mans nicht, wird nämlich automatisch bereits gemacht :fies:


    Also um mal Tacheles zu reden:

    Seine ersten beiden Wünsche sind über nur eine Sache realisierbar: Interrupts
    Warum er das allerdings in eine SQLite db schreiben will versteh ich nicht, wäre imho überflüssig.
    Bei Signaländerung wird halt eine Funktion zum versenden (von was auch immer) angesprochen...

    "Interrupt"

    [code=php]#!/usr/bin/python
    #
    # Interrupt löst ungewünscht aus
    # [Python] Hilfe bei Python script für Drehzahlmesser mit IR lichtschranke
    # Auf Flanken reagieren und mehrer Befehle ausführen
    #
    # -*- coding: utf-8 -*-
    import RPi.GPIO as GPIO
    import signal

    gpioPin1 = 4
    gpioPin2 = 7
    gpioPin3 = 11
    gpioPin4 = 12

    # only one of following:
    PULL = GPIO.PUD_DOWN #GPIO -> GND
    #PULL = GPIO.PUD_UP #GPIO -> 3V3

    # to use RaspberryPi gpio# (BCM) or pin# (BOARD)
    GPIO.setmode(GPIO.BCM)
    #GPIO.setmode(GPIO.BOARD)

    GPIO.setwarnings(False)
    GPIO.setup(gpioPin1, GPIO.IN, pull_up_down = PULL)
    GPIO.setup(gpioPin2, GPIO.IN, pull_up_down = PULL)
    GPIO.setup(gpioPin3, GPIO.IN, pull_up_down = PULL)
    GPIO.setup(gpioPin4, GPIO.IN, pull_up_down = PULL)

    def Interrupt_event(pin):
    if GPIO.input(pin): # if pin == 1
    print "Rising edge detected on %s" % pin
    else:
    print "Falling edge detected on %s" % pin

    if GPIO.input(gpioPin1) == GPIO.HIGH:
    print "special Taster"

    try:
    GPIO.add_event_detect(gpioPin1, GPIO.BOTH, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(gpioPin2, GPIO.BOTH, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(gpioPin3, GPIO.BOTH, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(gpioPin4, GPIO.BOTH, callback=Interrupt_event, bouncetime=150)

    #keep script running
    signal.pause()

    except KeyboardInterrupt:
    print "\nQuit\n"

    GPIO.cleanup()[/php]

    Seinen 3.Wunsch kann man im selben Script nutzen, da die Interrupts bereits selbstständig ausgelagert werden. Also einfach dort wo ich jetzt 'signal' verwendet hab um das Script am beenden zu hindern den Socket Server initialisieren. Wenn man den aber nicht in einem Thread ausführt, wird der nur ein einziges mal ausgeführt und sobald ein mal die Verbindung getrennt wurde, wars das, kein SocketServer mehr, finito.
    Deshalb sprach ich hier von threading und könnte wie folgt aussehen:

    "Socket Server"

    [code=php]#!/usr/bin/python
    # -*- coding: utf-8 -*-
    #
    # Starts a Thread for Socket Server waiting for Connections
    #
    # socketserver docu: https://docs.python.org/2/library/socketserver.html
    #
    # DONT use CTRL+C to Terminate, else Port is blocked
    import threading
    import time
    import sys
    import socket
    import subprocess
    try:
    import SocketServer as socketserver
    except ImportError:
    import socketserver

    #-------------------------------------------------------------------
    DEBUG = True
    #DEBUG = False
    SocketHost = "0.0.0.0"
    SocketPort = 7060
    #-------------------------------------------------------------------

    try:
    DEBUG
    except NameError:
    DEBUG = False

    if DEBUG:
    print("Enabling DEBUG mode!")
    else:
    print("Disabling DEBUG mode!")

    def printD(message):
    if DEBUG:
    print(message)
    sys.stdout.flush()


    #parse Socket request (e.g. from WebSocketControl.py)
    def ParseSocketRequest(request):
    printD("Parsing SocketRequest: "+request)
    returnlist = ""
    request = request.strip()
    requestsplit = request.split(':')
    requestsplit.append("dummy")
    command = requestsplit[0]
    value = requestsplit[1]

    #...do some stuff...

    returnlist += "\n "+command+":ok"
    return returnlist


    #socket server
    class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
    def handle(self):
    # self.rfile is a file-like object created by the handler;
    # we can now use e.g. readline() instead of raw recv() calls
    self.data = self.rfile.readline().strip()
    printD("SocketRequest: {}".format(self.data))
    try:
    self.wfile.write(ParseSocketRequest(self.data))
    except Exception, e2:
    print("Error in ThreadedTCPRequestHandler: " + str(e2))
    except (KeyboardInterrupt, SystemExit):
    sys.exit()

    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


    if __name__ == '__main__':
    try:
    HOST, PORT = SocketHost, SocketPort
    socket_server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    ip, port = socket_server.server_address
    # Start a thread with the server - that thread will then start one more thread for each connect
    socket_server_thread = threading.Thread(target=socket_server.serve_forever)
    socket_server_thread.start()

    print("socket_server_thread ist aktiv: %s" % socket_server_thread.isAlive())
    print("Socket Server loop running in thread: %s" % socket_server_thread.name)
    thread_count = threading.active_count()
    print("Insgesamt sind %s Threads aktiv." % thread_count)

    except Exception, e1:
    print("Error...: " + str(e1))
    except (KeyboardInterrupt, SystemExit):
    print("Schliesse Programme..")
    sys.exit()

    # this is the main (1.) thread which exits after all is done, so Dont close here!
    [/php]

  • Also erstmal danke für die beiden aufschlussreichen Beiträge.

    Wenn ich mein Vorhaben 1 ereignisgesteuert mit Interrupts realisieren könnte wäre das natürlich fein. Allerdings habe ich in meiner Anfangsbeschreibung nicht erwähnt, dass ich für die IO-Geschichte ein Piface benutze, welches zwar laut Spezi interruptfähig sein soll allerdings konnte ich hierzu nichts genaueres finden.

    Warum ich für meine Aufgabe 2 eine SQLite Tabelle ist damit ich unabhängig von der Internetvernbindung bin. Er arbeitet wie ein Spooler. Kommt es zu einer Pegeländerung soll Thread1 oder evtl. jetzt das Event einen Eintrag in eine Tabelle machen. Hierbei wird ein Feld "Status" mit 0 initialisiert. Mein Thread in Aufgabe 2 soll nichts anderes machen als bei einer bestehenden Internetverbindung die Einträge der Tabelle chronologisch abarbeiten und nach erfolgreichem versenden den Status auf 1 setzen. Nebenbei dient die Tabelle dann auch noch als Logbuch.

  • Also erstmal danke für die informativen Beiträge!

    Wenn ich mein Vorhaben 1 ereignisgesteuert mit Interrupts realisieren könnte wäre das natürlich sehr fein. Allerdings habe ich in meiner Anfangsbeschreibung nicht erwähnt, dass ich für die IO-Geschichte ein Piface benutze, welches zwar laut Spezi interruptfähig sein soll allerdings konnte ich hierzu nichts genaueres finden. Ich ging davon aus, dass ich einen Task brauche der die IOs in einer Schleife abklappert und Änderungen selbst feststellen muss.

    Zitat

    Warum er das allerdings in eine SQLite db schreiben will versteh ich nicht, wäre imho überflüssig.


    In Aufgabe 2 möchte ich die SQLite Tabelle als eine Art Spooler verwenden damit ich unabhängiger von der Internetvernbindung bin. Kommt es zu einer Pegeländerung soll Thread1 oder evtl. jetzt das Event einen Eintrag in eine Tabelle machen. Hierbei wird ein Statusfeld mit 0 initialisiert. Mein Thread in Aufgabe 2 soll also nichts anderes machen als bei einer bestehenden Internetverbindung die Einträge der Tabelle abklappern und bei bestehender Internetverbindung die Statusänderungen versenden und im Anschluss den Status auf der Tabelle hochsetzen. Nebenbei dient die Tabelle dann auch noch als Logbuch.

    noisefloor
    Danke für die Links. Dachte mit Threads könnte ich nichts falsch machen ...

    meigrafd
    danke für die beiden Beispiele. Die GPIOs spreche ich ja leider nicht direkt an aber den Socketserver habe ich erfolgreich ausprobieren können. Denke das wäre etwas für meine Aufgabe 3. Aber was meinst du mit

    Zitat

    Aber bitte halte Abstand von globalen Variablen

    Denke um ein paar werde ich nicht herumkommen, oder?

    Fazit: Wenn ich mit dem Piface Interrupts handeln könnte wären meine Probleme deutlich reduziert :)

  • Naja es gibt verschiedene 'globale Variablen'... Jene die außerhalb Funktionen definiert werden, zum Beispiel am Anfang des Scripts um GPIO Pins zu definieren oder welche SQLite-Datenbank-Datei verwendet werden soll... Oder eben solche die meist aus Bequemlichkeit in Funktionen global gesetzt werden um diese eben auch ausserhalb der Funktion verfügbar zu haben.
    Letzteres sollte man so gut es geht vermeiden, stattdessen lieber mit 'return's arbeiten

    Ein Beispiel hierfür siehst du hier:

    Spoiler anzeigen

    [code=php]#!/usr/bin/python
    # -*- coding: utf-8 -*-
    #
    # v0.2 by meigrafd
    #
    import sys
    from time import *
    import mpd
    import RPi.GPIO as GPIO
    import signal
    #------------------------------------------------------------------------

    MPD_HOST = "localhost"
    MPD_PORT = "6600"
    MPD_PASSWORD = "volumio" # password for Volumio / MPD

    #------------------------------------------------------------------------

    # to use RaspberryPi gpio# (BCM) or pin# (BOARD)
    GPIO.setmode(GPIO.BCM)
    #GPIO.setmode(GPIO.BOARD)

    #GPIO pins
    GPIO_PlayStop = 17
    GPIO_VolumePlus = 27
    GPIO_VolumeMinus = 22
    GPIO_PlayNext = 23
    GPIO_PlayPrev = 24

    # only one of following:
    PULL = GPIO.PUD_DOWN #GPIO -> GND
    #PULL = GPIO.PUD_UP #GPIO -> 3V3

    # set up GPIO input channels
    GPIO.setup(GPIO_PlayStop, GPIO.IN, pull_up_down=PULL)
    GPIO.setup(GPIO_VolumePlus, GPIO.IN, pull_up_down=PULL)
    GPIO.setup(GPIO_VolumeMinus, GPIO.IN, pull_up_down=PULL)
    GPIO.setup(GPIO_PlayNext, GPIO.IN, pull_up_down=PULL)
    GPIO.setup(GPIO_PlayPrev, GPIO.IN, pull_up_down=PULL)

    #------------------------------------------------------------------------

    def connectMPD(client):
    try:
    client.timeout = 10
    client.connect(MPD_HOST, MPD_PORT)
    except mpd.ConnectionError, err:
    if "Already connected" in err:
    print err
    return True, None
    else:
    return False, err
    if MPD_PASSWORD:
    try:
    client.password = MPD_PASSWORD
    except mpd.CommandError, err:
    disconnectMPD(client)
    return False, err
    return True, None

    def disconnectMPD(client):
    try:
    client.disconnect()
    except mpd.ConnectionError:
    pass

    def reconnectMPD(client):
    connected = False
    while not connected:
    connected, error = connectMPD(client)
    if not connected:
    print "Error: %s" % error
    print "Couldn't connect. Retrying"
    sleep(5)
    return connected

    def _ping(client):
    try:
    client.ping()
    except mpd.ConnectionError, e:
    con = reconnectMPD(client)

    def Interrupt_event(pin):
    _ping(client)
    PlayStat = client.status()
    if GPIO.input(Play) == GPIO.HIGH:
    if PlayStat['state'] == "stop" or PlayStat['state'] == "pause":
    client.load("MyRadio")
    client.play()
    elif PlayStat['state'] == "play":
    client.stop()
    client.clear()
    elif GPIO.input(Lauter) == GPIO.HIGH:
    vol = int(PlayStat['volume'])
    if vol >= 95:
    client.setvol(100)
    else:
    SetVol = vol + 5
    client.setvol(SetVol)
    elif GPIO.input(Leiser) == GPIO.HIGH:
    vol = int(PlayStat['volume'])
    if vol <= 5:
    client.setvol(0)
    else:
    SetVol = vol - 5
    client.setvol(SetVol)
    elif GPIO.input(Next) == GPIO.HIGH:
    client.next()
    client.play()
    elif GPIO.input(Prev) == GPIO.HIGH:
    client.previous()
    client.play()
    else:
    print "ERROR! Unknown GPIO pin triggered: %s" % pin

    #------------------------------------------------------------------------


    try:
    GPIO.add_event_detect(Play, GPIO.RISING, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(Lauter, GPIO.RISING, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(Leiser, GPIO.RISING, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(Next, GPIO.RISING, callback=Interrupt_event, bouncetime=150)
    GPIO.add_event_detect(Prev, GPIO.RISING, callback=Interrupt_event, bouncetime=150)
    client = mpd.MPDClient()
    connected, error = connectMPD(client)
    if error:
    print(error)
    raise SystemExit

    #keep script running
    signal.pause()
    except (KeyboardInterrupt, SystemExit):
    disconnectMPD(client)
    GPIO.cleanup()
    print("\nQuit\n")
    [/php]

    Auch mit dem PiFace sind Interrupts möglich, einfach mal nach "PiFace Interrupt" googlen und die ersten Treffer angucken ;)


  • Auch mit dem PiFace sind Interrupts möglich, einfach mal nach "PiFace Interrupt" googlen und die ersten Treffer angucken ;)

    Danke, hab ich dann auch gefunden und bereits erfolgreich getestet ...

    ... und angehängt falls jemand Interesse hat :)

    Der Code initialisert den Trigger und gibt bei Pegeländerungen Adresse und Zustand des betroffenen Signals aus.

    Spoiler anzeigen

    [code=php]import pifacedigitalio

    # Wird ein Eingangspegel geändert wird diese Funktion aufgerufen
    def EreignisSignalaenderung(event):
    print("Signaländerung Adresse: " + str(event.pin_num) + " Neuer Zustand: " + str(event.direction))

    pifacedigital = pifacedigitalio.PiFaceDigital()
    listener = pifacedigitalio.InputEventListener(chip=pifacedigital)
    AnzahlEingaenge = 8

    # Jeder Eingang muss mit der Ereignisfunktion verknbüpft werden
    for i in range(0, AnzahlEingaenge):
    listener.register(i, pifacedigitalio.IODIR_FALLING_EDGE, EreignisSignalaenderung)
    listener.register(i, pifacedigitalio.IODIR_RISING_EDGE, EreignisSignalaenderung)

    # aktiviert den Interuppthandler
    listener.activate()[/php]

    Also Aufgabe 1 denke ich kann ich soweit lösen. Aufgabe 2 werde ich wohl wie Aufgabe 3 in einem Thread versuchen. Vielen Dank nochmal für die Anregungen! Achja und das mit den globalen Variablen schau ich mir an!

    Einmal editiert, zuletzt von iosam (1. März 2015 um 15:59)

  • Ich versteh immer noch nicht wieso du 2. ebenfalls in einen Thread packen willst :s

    Sobald ein Interrupt ausgelöst wurde führst du eine Funktion aus welche dann deine Datenbank updated, oder was auch immer. Da der Interrupt bereits in einem Thread läuft wird dieser durch den Aufruf nicht blockiert.

    Also zB:

    [code=php]
    #!/usr/bin/python

    import pifacecommon.core
    import pifacecommon.interrupts
    import os, time
    import sqlite3 as sqlite
    import signal

    #------------------------------------------
    DBfile = '/var/scores.db'
    #------------------------------------------


    def updateDB(Table, Rows, Values):
    try:
    timestamp = int(time.time())
    con = sqlite.connect(DBfile)
    cur = con.cursor()
    cur.execute( "INSERT INTO "+ Table +" (?,?) VALUES (?);", (timestamp,Rows,Values) )
    con.commit()
    except sqlite.Error, e:
    print 'SQLite Error: %s' % e
    except Exception, err:
    print 'SQLite Error: %s' % err
    except:
    print 'SQLite Error'
    finally:
    if con:
    con.close()

    def print_flag(event):
    print 'You pressed button', event.pin_num, '.'
    if event.pin_num == "0":
    updateDB('taster0', 'state', '1')
    elif event.pin_num == "1":
    updateDB('taster1', 'state', '1')

    pifacecommon.core.init()

    # GPIOB is the input ports, including the four buttons.
    port = pifacecommon.core.GPIOB

    listener = pifacecommon.interrupts.PortEventListener(port)

    # set up listeners for all buttons
    listener.register(0, pifacecommon.interrupts.IODIR_ON, print_flag)
    listener.register(1, pifacecommon.interrupts.IODIR_ON, print_flag)
    listener.register(2, pifacecommon.interrupts.IODIR_ON, print_flag)


    def signal_handler(signal, frame):
    print 'Script Interrupted! (Control-C)'
    raise SystemExit

    try:
    # Start listening for events. This spawns a new thread.
    listener.activate()
    # keep script running until CTRL+C pressed
    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()
    except (KeyboardInterrupt, SystemExit):
    print '\nQuit\n'
    listener.deactivate()
    [/php]


    Die vermutlich beste Möglichkeit anstatt threading wäre aber mithilfe multiprocessing

  • Ich möchte 2. nur in einem Thread laufen lassen wegen der Spoolertabelle. Im Thread soll in einer Endlosschleife die Tabelle, welche in 1. befüllt wird, überprüft werden. Sind neue Datensätze (Signaländerungen) dazugekommen schaut der Thread nach ob eine Internetverbindung besteht. Wenn ja wird die Signaländerung direkt versendet und der Status des Datensatz auf "versendet" gesetzt. Ist keine Internetverbindung vorhanden wird das Gleiche beim nächsten Durchlauf nochmal gemacht.

    Hast du auch ein Beispiel für Multiprocessing in Python? Was ist der Unterschied zum Multithreading?

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!