Problem 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
Pacnos
User
Beiträge: 19
Registriert: Sonntag 8. Mai 2011, 20:17

Hallo zusammen,

ich arbeite gerade mit Subprocess und bin gerade auf ein Problem gestossen, wo ich einfach nicht weiter komme. Ich will mithilfe eines C-Programmes einen Sensor auslesen, da ich Ihn nicht direkt ansprechen kann :(

Dabei verwende ich folgenden Pythoncode mit dem Subrocess Modul:

Code: Alles auswählen

path = os.path.normcase(Config.getValue("path2digitemp") + "/digitemp")
       
        try:
            process = subprocess.Popen([path,'-i', '-s' + port, '-q'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        except (WindowsError, TypeError) as e:
            QtGui.QMessageBox.warning(None, "Fehler beim erzeugen suchen der Sensoren",
                                           "Fehler beim suchen der Sensoren \n" + str(e))
            return 0
        
        #Reading the Return
        #If error
        lineerr = None
        sensors = 0
        while process.poll() is None:
            lineerr = process.stderr.readline()
            if lineerr.decode("utf-8") != "":
                print(lineerr)
                process.terminate()
                QtGui.QMessageBox.warning(None, "Fehler beim erzeugen suchen der Sensoren",
                                           "Fehler beim suchen der Sensoren \n" + lineerr.decode("utf-8"))
                return 0
            #If Sensors are found
            line = process.stdout.readline().decode("utf-8")
            line = line[:-2]
            print(line)
            if (line != "") and (line[0:3] == "ROM"):
                sensors = sensors + 1
                print(line)
            print("fire")
                
            return sensors
Wenn ich den Befehl jetzt hier ausführe gibt er mir folgendes aus:
Turning off all DS2409 Couplers
fire
Wenn ich das C-Programm aber direkt über die Komanndozeile aufrufe, bekomme ich folgende Antwort:
Turning off all DS2409 Couplers
.
Searching the 1-Wire LAN
10A9AE9801080080 : DS1820 Temperature Sensor
ROM #0 : 10A9AE9801080080
Das heißt er liest einfach nur die erste Zeile. Habe Ihre eine Idee woran das liegen könnte?

Schonmal danke für eure Hilfe ...
Zuletzt geändert von Anonymous am Samstag 2. Juli 2011, 09:07, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@Pacnos: Der Programmablauf sieht ein wenig komisch aus. Die ``while``-Schleife kann maximal einmal durchlaufen werden, denn sie wird garantiert mit einem ``return`` verlassen. Du könntest aus dem ``while`` also auch ein ``if`` machen. Ist das wirklich das was Du ausdrücken wolltest?

Und der Rückgabewert der Funktion ist, zumindest laut diesem Quelltextaussschnitt entweder 0 oder 1. Der Code erweckt aber ein wenig den Eindruck als sollten Sensoren gezählt werden, also auch Rückgabewerte >1 möglich sein. Steht das ``return sensors`` wirklich an der richtigen Stelle?

Das unbedingte Lesen von Zeilen aus stderr und stdout vom externen Prozess sieht mir auch etwas fragil aus. So ein `readline()`-Aufruf blockiert so lange bis tatsächlich eine Zeile gelesen werden konnte. Mit den beiden ”Verbindungen” kann man auch in Verklemmungssituationen kommen, wenn die beiden Prozesse gegenseitig auf jeweils der anderen Datei warten, dass der andere Prozess etwas tut.

Brauchst Du das in ”Echtzeit” in einer Schleife, oder würde `Popen.communicate()` auch ausreichen?

Sonstige Anmerkungen: Du vermischst Logik und GUI. In einer Funktion, welche die Anzahl von angeschlossenen Sensoren ermittelt wird, hat GUI-Code nichts zu suchen.

Pfade sollte man nicht mit ``+`` sondern mit `os.path.join()` zusammensetzen.

``(line != "") and (line[0:3] == "ROM")`` kann man besser so ausdrücken: ``line.startswith('ROM')``. Und ``spam = spam + 1`` als ``spam += 1``.

Edit: Ohne den GUI-Kram (ungetestet):

Code: Alles auswählen

        path = os.path.normcase(
            os.path.join(Config.getValue('path2digitemp'), 'digitemp')
        )
        process = subprocess.Popen(
            [path, '-i', '-s' + port, '-q'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        output, error_output = process.communicate()
        if process.returncode:
            raise OSError(error_output)
        return sum(line.startswith('ROM') for line in output.splitlines())
Pacnos
User
Beiträge: 19
Registriert: Sonntag 8. Mai 2011, 20:17

Hallo BlackJack,

danke für deine sehr ausführliche Antwort, du hast natürlich recht, an dieser Stelle zähle ich die Anzahl der Sensoren. Deine Lösung funtkioniert bei mir. Ich habe jetzt nur noch in ein Problem, wenn ich den Sensor auslese gibt er mir eine Zeile zurück, wenn der Sensor aber aus irgend ein Grund nicht erreichbar ist, versucht das C Programm auf ewig den Sensor zu finden und gibt in regelmäßigen abständen eine Fehlermeldung aus, ich bräuchte also eine "Echtzeitüberwachung" um das Programm im Notfall hart abwürgen zu können.

Da ich diese Echtzeitüberwachung haben wollte, hatte ich dies "abenteuerliche" Konstruktion gebaut. Wie würde ich es dann richtig mit eine Echtzeitüberwachung machen, hast ud da zufälliger weise ein kleines Beispiel für mich?
BlackJack

@Pacnos: Kannst Du die Fehlerzeilen auch eindeutig am Inhalt identifizieren? Denn dann wäre es einfacher beide Streams vom externen Prozess über *ein* Datei-Objekt zu empfangen. Also die Fehlerausgabe in die normale Standardausgabe umzulenken und diese dann Zeile für Zeile zu analysieren. Ungetestet:

Code: Alles auswählen

        process = subprocess.Popen(
            [path, '-i', '-s' + port, '-q'],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT
        )
        result = 0
        for line in process.stdout:
            if test_auf_fehler(line):
                process.terminate()
                raise IOError(line)
            elif line.startswith('ROM'):
                result += 1
        process.wait()
        return result
Ich hatte im letzten Beitrag `OSError` missbraucht, aber vielleicht wäre `IOError` für einen Sensor, den man nicht auslesen kann, passender.

Wenn das nicht geht und man die beiden Streams wirklich getrennt aber quasi gleichzeitig verarbeiten muss, wird es komplexer. Dann müsste man mit dem `select`-Modul oder Threads arbeiten.
Pacnos
User
Beiträge: 19
Registriert: Sonntag 8. Mai 2011, 20:17

Hallo BlackJack,

ich habe jetzt einmal deinen Quelltext probiert, leider liest er das nicht wirklich live aus, sondern wartet erst bis das Programm durchgelaufen ist und gibt mir dann die gesamte Ausgabe aus. Ich habe deinen Quelltext in folgender Form modifiziert.

Code: Alles auswählen

path = os.path.normcase(
        os.path.join(Config.getValue('path2digitemp'), 'digitemp'))
        process = subprocess.Popen(
            [path, '-t0', '-n15'],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT
        )
        result = 0
        for line in process.stdout:
            print(line)
        process.wait()
BlackJack

@Pacnos: Und wo soll da jetzt der Unterschied sein? Das liest genau so ”live” aus wie mein Quelltext. Die ``for``-Schleife über die Ausgabe des Prozesses ist ja die selbe.
Pacnos
User
Beiträge: 19
Registriert: Sonntag 8. Mai 2011, 20:17

Hallo BlackJack,

sry ich glaube ich habe mich falsch ausgedrückt, ich habe das mit dem print() einfach nur zum testen eingebaut, ich habe jetzt nur das Problem, dass der Quelltext halt nicht "live" ausliest.

Wenn ich das Programm z.B mit dem Kommando digitemp -t0 -n5 aufrufe gibt er mir insgesamt 5 mal die folgende Zeile aus und beendet sich dann:
Jul 04 21:09:28 Sensor 0 C: 21.25 F: 70.25
Wenn ich das aber im Python script ausführe, wartet er ert 5 sekunden und gibt mir dann den gesamten batzen aus also nicht live, und wenn er in diesem Fall ein Error produziert, läuft er aus diesem grund dann in einer Endlosschleife.

Ich hoffe ich habe mein Problem jetzt etwas verständliche beschreiben.
BlackJack

@Pacnos: Naja, Endlosschleife nicht wirklich weil der (oder die) Puffer zwischen den beiden Prozessen irgendwann voll sind und dann die Daten schon übertragen werden. Das `stderr` ungepuffert, oder zumindest Zeilengepuffert wird, ist zwar üblich, aber auch nicht wirklich garantiert.

Dann bleibt also nur `select` oder `threading` um beide Kanäle ”gleichzeitig” zu verarbeiten.
Antworten