Seite 1 von 1

Threading und serielle Schnittstelle

Verfasst: Dienstag 4. Dezember 2012, 14:30
von maxe
Hallo!

Ich bin gerade dabei ein kleines Konsolenprogramm zu schreiben, das folgende Aufgabe erfüllen soll: An der seriellen Schnittstelle sollen Daten empfangen werden und in eine Datei geschrieben werden. Dies funktioniert soweit auch. Jetzt soll gleichzeitig die Dateigröße überwacht werden und ab einer bestimmten Größe eine Funktion ausgeführt werden. Dies funktioniert soweit auch, allerdings sollen die Daten auch weiterhin im Hintergrund aufgezeichnet werden, auch wenn die Funktion abgearbeitet wird.

Ich habe das Ganze mit threading versucht, allerdings bekomme ich immer eine Fehlermeldung:
SerialException: call to ClearCommError failed

Mein Code schaut so aus:

Code: Alles auswählen

import serial
import os.path
import threading

#configure the serial connections
ser = serial.Serial(
		port='COM1',
		baudrate=38400,
		parity=serial.PARITY_NONE,
		stopbits=serial.STOPBITS_ONE,
		bytesize=serial.EIGHTBITS,
)

counter = 0

logfile = open("log.txt", "a")

def Thread1():
    global counter, s
    while counter < 50:
        s = ser.readline()
        logfile.write(s)
        counter = counter +1

def Thread2():
    global counter, logfile
    while counter < 50:
        b = os.path.getsize("log.txt")
        if b > 5000:
            print "Filesize > 5kb"
        continue

th1=threading.Thread(target=Thread1)
th2=threading.Thread(target=Thread2)

th1.start()
th2.start()

ser.close()
logfile.close()
Ich teste im Moment unter Windows XP, später soll es aber auch auf Linux laufen.

Vielen Dank schonmal.

Grüsse,
Maxe

Re: Threading und serielle Schnittstelle

Verfasst: Dienstag 4. Dezember 2012, 15:13
von lunar
@maxe Zeige den vollständigen, mindestens aber lauffähigen Quelltext. Dein Beispiel führt zu einem NameError, da "ser" nirgendwo definiert wird.

Re: Threading und serielle Schnittstelle

Verfasst: Dienstag 4. Dezember 2012, 15:50
von maxe
Hab den Code im Eingangspost geändert. Ist jetzt vollständig.

Re: Threading und serielle Schnittstelle

Verfasst: Dienstag 4. Dezember 2012, 21:33
von BlackJack
@maxe: Du startest die beiden Threads und schliesst gleich danach die serielle Schnittstelle und die Logdatei. Dann können die Threads da schlecht drauf zugreifen.

Re: Threading und serielle Schnittstelle

Verfasst: Mittwoch 5. Dezember 2012, 01:23
von pillmuncher
Mir ist nicht ganz klar, was du als Ergebnis deines Programms erwartest. Du schreibst, es soll "ab einer bestimmten Größe eine Funktion ausgeführt werden". Vermutlich statt des print "Filesize > 5kb". So, wie du es programmiert hast, funktionierst du deinen Rechner zur Heizung um und vergeudest massiv Rechenzeit. Solange die Dateigröße < 5000 ist, können beliebig viele Schleifendurchläufe in Thread2 stattfinden, je nachdem, wie lange das Lesen von COM1 und das Schreiben in log.txt in Thread1 jedesmal dauert. Aber selbst wenn die Dateigröße >= 5000 kB ist, wird geloopt werden, was der Prozessor hergibt, nur dass dann bei jedem Durchlauf deine Funktion wieder und wieder gestartet wird, solange, bis entweder die Abbruchbedingung erfüllt ist, oder die Datei wieder kleiner als 5000 kB ist, was aber nicht passieren kann, da du ja immer nur in die Datei hineinschreibst, aber nie etwas löschst. Ist es wirklich das, was du erreichen willst?

Kommunikation über globale Variablen ist übrigens pfui, und Kommunikation zwischen Threads über globale Variablen ist doppelt pfui. Nimm lieber Queue.Queue.

Du solltest nicht im selben Thread von COM1 lesen und in eine Datei schreiben. File-IO ist blocking und sollte in einem eigenen Thread laufen, damit du nicht COM1 blockierst, sondern so schnell wie möglich wieder von dort lesen kannst.

Außerdem liefert os.path.getsize() die tatsächliche Größe der Datei auf der Festplatte, aber es ist nicht automatisch sichergestellt, dass schon alles in der Datei gelandet ist, was du hineingeschrieben hast, da File-IO buffered ist. Der Puffer wird erst geleert und alles daraus auf die Festplatte geschrieben, wenn die Datei geschlossen wird. Da du die Datei aber nicht für jeden Schreibvorgang erneut öffnen und danach wieder schließen möchtest, mußt du den Puffer nach jedem Schreibvorgang explizit mittels file.flush() und os.fsync(file.fileno()) leeren.

Und du solltest with-Statements verwenden, damit auch im Falle einer Exception Datei und Port wieder geschlossen werden.

Ich hab's mal umgebaut:

Code: Alles auswählen

import os
from serial import Serial, PARITY_NONE, STOPBITS_ONE, EIGHTBITS
from threading import Thread
from Queue import Queue


PORT_CONFIG = dict(
    port='COM1',
    baudrate=38400,
    parity=PARITY_NONE,
    stopbits=STOPBITS_ONE,
    bytesize=EIGHTBITS)

LOG_FILE_NAME = 'log.txt'
MAX_LINES = 50
THRESHOLD = 5000

POISON = object()


def read_port(send, port):
    try:
        for each in xrange(MAX_LINES):
            send(port.readline())
    finally:
        send(POISON)


def write_log(send, receive, logfile):
    try:
        while True:
            data = receive()
            if data is POISON:
                break
            logfile.write(data)
            logfile.flush()
            os.fsync(logfile.fileno())
            send(None)
    finally:
        send(POISON)


def loop_after_threshold_reached(receive, callback):
    while True:
        info = receive()
        if info is POISON:
            break
        if os.path.getsize(LOG_FILE_NAME) > THRESHOLD:
            callback(info)


def some_function(stuff):
    print 'Filesize > 5kb', stuff,


if __name__ == '__main__':
    with open(LOG_FILE_NAME, 'a') as logfile:
        with Serial(**PORT_CONFIG) as port:
            data_channel = Queue()
            info_channel = Queue()
            reader = Thread(target=read_port,
                            args=(data_channel.put, port))
            writer = Thread(target=write_log,
                            args=(info_channel.put, data_channel.get, logfile))
            looper = Thread(target=loop_after_threshold_reached,
                            args=(info_channel.get, some_function))
            reader.start()
            writer.start()
            looper.start()
            reader.join()
            writer.join()
            looper.join()
Hierbei werden die Schleifen in write_log() und loop_after_threshold_reached() immer nur genauso oft durchlaufen, wie in read_port(). Statt Millionen sinnloser Durchläufe gibt es also nur jeweils 50. Bei jedem Durchlauf könnte eine Dateigröße >= 5000 erreicht sein, aber es wird für jede tatsächliche Dateigröße nur je einmal getestet, nicht millionenmal.

Und übrigens: die letzten drei Zeilen lösen das Problem, nachdem du gefragt hattest.

Und zuletzt noch: deine Namen könnten besser sein. Wenn ich Code lese, möchte ich nicht zuerst wissen, was um für eine Art von Ding es sich handelt, auf das eine Variable verweist, sondern welche Rolle es im Programm spielt. Thread1 und Thread2 sagen gar nichts aus. Was findest du informativer:

Code: Alles auswählen

String1 = 'Hans'
String2 = 'Mustermann'
Zahl1 = 23
oder:

Code: Alles auswählen

Vorname = 'Hans'
Nachname = 'Mustermann'
Alter = 23
?