pyserial und with

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.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

bastel gerade an einem Messgerät für die serielle Schnittstelle. Braucht kein Mensch - macht aber Freude !

https://informatik.bildung-rp.de/filead ... -RS232.pdf

Code: Alles auswählen

    def measure(self):
        try:
            with serial.Serial(self.device) as self.ser:
                end = None
                self.ser.setDTR(False)
                start = time.time()
                while self.ser.dsr == False:
                    end = time.time() - start
                    uf = end * 1000 - 80 * end
                    self.ser.setDTR(True)
                if end and uf >= 0.5:
                    self.display.delete("cap", "error")
                    self.display.create_text(
                        self.width / 2, 
                        self.height / 2, text="{0:2.2f} uf".format(uf), 
                        font = self.display_font,
                        fill = self.text_color,
                        tag = "cap")
                    self.ser.close()
                    self.after_id = self.after(int(end * 5000), 
                        self.measure)
                else:
                    self.display.delete("cap", "error")
                    self.start_stop_button.config(text = "START")
                    self.display.create_text(
                        self.width / 2, 
                        self.height / 2, 
                        text = "uf < 1 or not connected", 
                        font = self.error_font,  
                        tag = "error")
                    self.after_id = None
        except (serial.SerialException, IOError) as error:
            self.display.delete("cap", "error")
            self.display.create_text(
                self.width / 2, 
                self.height / 2, 
                text = "{0}".format(error), 
                font = self.error_font,  
                tag = "error")
            self.start_stop_button.config(text = "START")
            self.after_id = None
        finally:
            self.ser.close()
Öffne die Schnittstelle immer wieder, doch mit dem "With Statement" bekomme ich es nicht anders hin ? Die Messungen sind recht genau oder genau so ungenau, wie die meiner billigen Tester. Die ganze Spielerei mache ich über einen USB/RS232-Adapter.

Gruß Frank
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du with benutzt, warum schließt du dann noch mit self.ser.close()? Das ist doch genau Sinn und Zweck von with das man das NICHT muss. Und auch das binden an self ist in diesem Fall wiedersinnig. Das with statement definiert syntaktisch die Nutzbarkeit der Schnittstelle. Die dann an das länger-lebige self zu binden zerbricht diese Erwartungshaltung.

Prinzipiell spricht nichts gegen regelmäßige öffnen und schließen - bei einer seriellen Schnittstelle wird das wenig Überbau bedeuten. Aber with um jeden preis ist es nun auch nicht wert.

Es gibt eine Alternative wenn du die Schnittstelle vor der des umgebenden Objektes kennst. Dann kannst du mit

Code: Alles auswählen

with serial.... as ser:
       gui = DieGui(ser)
arbeiten. Da ist es natürlich legitim, in DieGui ser an self zu binden.

Wenn das alles nicht geht, dann ist es besser measure knapper zu halten. Da passiert eh viel zu viel. Die
Erfassung des Messwertes sollte von dessen Verarbeitung per verschiedener Methoden klar getrennt werden. Dann verrrödelt mAn sich auch nicht mit diversen closes und anderen komplex verschachtelten Kontrollstrukturen.

Stattdessen gibt’s dann zwei explizite Methoden zum öffnen und schließen der Schnittstelle. Zb wenn der benutzter per gui eine andere Schnittstelle auswählt.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kaytec: Das schliessen der Verbindung im `finally`-Zweig macht keinen Sinn, weil das ja bereits durch die ``with``-Anweisung passiert. Zudem kann das sogar zu einem `AttributError` führen wenn das erste öffnen schon nicht funktioniert, weil es das Attribut dann nicht gibt. Beziehungsweise sollte das natürlich nach der `__init__()` bereits existieren, ist dann aber wahrscheinlich `None`, und damit gibt das `close` dann den `AttributError` weil `None` das nicht besitzt.

Und auch das Schliessen innerhalb des ``with``-Blocks ist natürlich überflüssig.

Wenn das ``with`` richtig sein sollte, dann sollte `ser` aber auch überhaupt gar nicht als Attribut auf dem Objekt gesetzt werden, weil das dann ja nur lokal in dieser Methode verwendet wird.

`setDTR()` ist veraltet, `Serial` stellt die Leitungen mittlerweile als Properties zur Verfügung. Für das Lesen des Zustandes verwendest Du das ja bereits, statt `getDTR()`, warum also noch den alten Setter?

Auf literale Wahrheitswerte vergleicht man nicht. Statt ``while ser.dsr == False:`` verwendet man ``while not ser.dsr:``.

Das ist potentiell eine Endlosschleife. Wenn es zu so einer wird, dann friert Dir das Programm/die GUI ein.

Die Rechnung ``end * 1000 - 80 * end`` ist ein bisschen komisch. Das ist doch einfach ``end * 920``‽

Der Code für die Anzeige von Ergebnis oder Fehler kommt da fast identisch drei mal in der Methode vor. Das würde ich in eine eigene Methode auslagern.

`self.after_id` könnte man auch *einmal* ganz am Anfang auf `None` setzen, und am Ende der Methode dann verwenden um an *einer* Stelle zu entscheiden ob die Schaltfläche wieder den Text `START` bekommen soll.

`uf` ist kein wirklich guter Name. Würde ich eher `capacity` nennen.

Ungetestet:

Code: Alles auswählen

    def update_cap_display(self, text, is_error=False):
        self.display.delete('cap', 'error')
        self.display.create_text(
            self.width / 2, 
            self.height / 2, text='{0:2.2f} uf'.format(uf), 
            font=self.error_font if is_error else self.display_font,
            fill=self.text_color,
            tag='error' if is_error else 'cap',
        )

    def measure(self):
        self.after_id = None
        try:
            with Serial(self.device) as serial:
                end = None
                serial.dtr = False
                start = time.time()
                while not serial.dsr:
                    end = time.time() - start
                    capacity = end * 920
                    serial.dtr = True
                
                if end and capacity >= 0.5:
                    self.update_cap_display('{0:2.2f} µf'.format(capacity))
                    self.after_id = self.after(int(end * 5000), self.measure)
                else:
                    self.update_cap_display(
                        'capacity < 1 µf or not connected', True
                    )
        except (SerialException, IOError) as error:
            self.update_cap_display(str(error), True)

        if not self.after_id:
            self.start_stop_button.config(text='START')
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

danke für eure Hilfe.

Sicheres Schließen der Schnittstelle mit dem "With-Statement" hatte ich auch so in Erinnerung, doch ohne ser.close() in dem Abschnitt mit dem „after“ friert die Gui ein. So habe ich alles umgebastelt mit „ser.close“, da ich irgendwie vermutete habe es müsste vorhanden sein. Schließe ich das Programm über x an dem Fenster, dann brauche ich „ser.close()“ auch bei „def release(self)“ und somit auch self.ser.close().

Eine Auswahl der Schnittstellen ist für mich noch nicht so wichtig, da der Adapter immer "/dev/ttyUSB0" ist.

Die ganzen Fehlerausnahmen kommen beim Testen im Betrieb, denn ich habe mal alle möglichen Fehler des Nutzers probiert (abziehen im Betrieb des Adapters, Bauteile entfernen etc.). Alle Fehler die ich erzeugt habe sind so abgefangen worden.

Kalibriert habe ich die Schaltung mit einem Goldcap-Kondensator, da sie eine kleine Toleranz aufweisen (ist besitze auch gefährliches Halbwissen im Bereich der Elektronik !). Aus diesem Test stammt die „80“ ( uf = end * 1000 - 80 * end) und die Multiplikation mit der steigenden Messzeit hat auch einen korrektive Funktion. Meine Hauptschulmathematik sagt mir, dass so der Wert mit höheren Messzeiten steigt. Könnte auch völlig falsch liegen. Das „self.after(int(end * 5000)“ ist auch für die Messgenauigkeit reingebastelt – die Latenzzeiten der Adapter sollen recht hoch sein und daher geht nur langsames Messen (1uf > 1).

Schreibe es mal um und bastel weiter, wenn meine Kinder wieder schlafen und ich nicht mit ihnen eingeschlafen bin.

Gruß Frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,

Deine Version wirft Fehlermeldungen:

Code: Alles auswählen

>python -u "condensator_gui_rs232.py"
Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1544, in __call__
    return self.func(*args)
  File "condensator_gui_rs232.py", line 48, in start_stop
    self.measure()
  File "condensator_gui_rs232.py", line 91, in measure
    except (SerialException, IOError) as error:
NameError: global name 'SerialException' is not defined
>Exit code: 0
>python -u "condensator_gui_rs232.py"
Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1544, in __call__
    return self.func(*args)
  File "condensator_gui_rs232.py", line 48, in start_stop
    self.measure()
  File "condensator_gui_rs232.py", line 75, in measure
    with serial.Serial(self.device) as serial:
UnboundLocalError: local variable 'serial' referenced before assignment
Meine Version braucht "self" und "self.serial.close()" und "flaot" konnte ich nicht auf "isdigt()" prüfen. Verliert Tkinter die Referenz auf serial ?

Code: Alles auswählen

    def update_cap_display(self, text, is_error=False):
        self.display.delete('cap', 'error')
        #text = '{0:2.2f} uf'.format(text) if text.isdigit() else text
        self.display.create_text(
            self.width / 2, 
            self.height / 2, text = text, 
            font = self.error_font if is_error else self.display_font,
            fill = self.text_color,
            tag = "error" if is_error else "cap"
            ,)

    def measure(self):
        self.after_id = None
        try:
            with serial.Serial(self.device) as self.serial:
                end = None
                self.serial.dtr = False
                start = time.time()
                while not self.serial.dsr:
                    end = time.time() - start
                    capacity = "{0:2.2f} uf".format(end * 1000 - 80 * end)
                    self.serial.dtr = True
                
                if end and capacity >= 0.5:
                    self.update_cap_display(capacity)
                    self.serial.close()
                    self.after_id = self.after(int(end * 5000), self.measure)
                else:
                    self.update_cap_display(
                        'capacity < 1 µf or not connected', True
                    )
        except (serial.SerialException, IOError) as error:
            self.update_cap_display(error, True)

        if not self.after_id:
            self.start_stop_button.config(text='START')
oder so, da "self.serial.close()" "serial.dtr" auf False setzt:

Code: Alles auswählen

    def update_cap_display(self, text, is_error=False):
        self.display.delete('cap', 'error')
        #text = '{0:2.2f} uf'.format(text) if text.isdigit() else text
        self.display.create_text(
            self.width / 2, 
            self.height / 2, text = text, 
            font = self.error_font if is_error else self.display_font,
            fill = self.text_color,
            tag = "error" if is_error else "cap"
            ,)

    def measure(self):
        self.after_id = None
        try:
            with serial.Serial(self.device) as self.serial:
                end = None
                #serial.dtr = False
                start = time.time()
                while not self.serial.dsr:
                    end = time.time() - start
                    capacity = "{0:2.2f} uf".format(end * 1000 - 80 * end)
                    #serial.dtr = True
                
                if end and capacity >= 0.5:
                    self.update_cap_display(capacity)
                    self.serial.close()
                    self.after_id = self.after(int(end * 5000), self.measure)
                else:
                    self.update_cap_display(
                        'capacity < 1 µf or not connected', True
                    )
        except (serial.SerialException, IOError) as error:
            self.update_cap_display(error, True)

        if not self.after_id:
            self.start_stop_button.config(text='START')


Gruß Frank
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Code: Alles auswählen

File "condensator_gui_rs232.py", line 75, in measure
    with serial.Serial(self.device) as serial:
UnboundLocalError: local variable 'serial' referenced before assignment
Das ist eine oft übersehene Falle: Du hast Dir 'serial' überschrieben. __blackjack__ hat Dir den Code auch nicht genauso gezeigt, sondern ging davon aus, dass 'Serial' schon im Namespace vorlag und nicht über 'serial.Serial' angesprochen werden muss. Eine mögliche Lösung, bei der Du die import-Statements nicht ändern brauchst, ist die Umbenennung des lokalen 'serial' in beispielsweise 'connection' oder einen anderen passenden Namen:

Code: Alles auswählen

with serial.Serial(self.device) as connection:
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Du benutzt ja auch `serial` gleichzeitig als Modul und als lokale Veriable, das geht natürlich nicht. Das ist aber kein Grund, statt dessen self.serial zu schreiben.
In `update_cap_display` hängt ein Komma so unmotiviert vor der schließenden Klammer; das Komma gehört eine Zeile davor.
Variablen sollte man erst für die Ausgabe formatieren. Bei `capacity` ist das zu früh.
Warum Du ein serial.close brauchst, ist mir nicht klar.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Sirius3,

hat BlackJack so geschrieben und ich hatte gedacht, dass es überschrieben werden kann. Schließe ich die serielle Schnittstelle nicht, dann freiert die GUI ein.

Gruß
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Wollte die Formatierung auch in "update_cup_display" machen, doch bei einem "Error" ist ja text vorhanden und eine Unterscheidung mit

Code: Alles auswählen

text = '{0:2.2f} uf'.format(text) if text.isdigit() else text
hat nicht so funktioniert ?!

Hier das koplette Script:

Code: Alles auswählen

#/usr/bin/env python
# -*- coding: utf-8
import Tkinter as tk
import serial
import time

WIDTH = 200
HEIGHT = 100
DEVICE = "/dev/ttyUSB0"

class CapacitanceUI(tk.LabelFrame):
    
    def __init__(self, parent, width, height, device, text_color = "blue",
        error_font = "Arial 7 bold", display_font = "system 18 bold"):
        tk.LabelFrame.__init__(self, parent, text = "CAPACITANCE", 
            relief = "solid")
        self.ser = None
        self.after_id = None
        self.width = width
        self.height = height
        self.parent = parent
        self.device = device
        self.text_color = text_color
        self.error_font = error_font
        self.display_font = display_font
        self.display = tk.Canvas(self, 
            width = width, 
            height = height, 
            bg="cyan")
        self.display.pack(padx = 5, pady = 5)
        self.display.create_text(
            self.width / 2, 
            self.height / 2, 
            text = "00.00 uf", 
            font = self.display_font,
            fill = text_color,
            tag = "cap")
        button_frame = tk.Frame(self, bg = "gray")
        button_frame.pack(padx = 5, pady = 5)
        self.start_stop_button = tk.Button(button_frame, text = "START",
            command = self.start_stop)
        self.start_stop_button.pack()
    
    def start_stop(self):
        if not self.after_id:
            self.start_stop_button.config(text = "STOP")
            self.measure()
        else:
            self.display.delete("cap", "error")
            self.display.create_text(
                self.width / 2, 
                self.height / 2, 
                text = "00.00 uf", 
                font = self.display_font,
                fill = self.text_color,
                tag = "cap")
            self.start_stop_button.config(text = "START")
            self.after_cancel(self.after_id)
            self.after_id = None
                
    def update_cap_display(self, text, is_error = False):
        self.display.delete('cap', 'error')
        #text = '{0:2.2f} uf'.format(text) if text.isdigit() else text
        self.display.create_text(
            self.width / 2, 
            self.height / 2, text = text, 
            font = self.error_font if is_error else self.display_font,
            fill = self.text_color,
            tag = "error" if is_error else "cap")

    def measure(self):
        self.after_id = None
        try:
            with serial.Serial(self.device) as ser:
                end = None
                #ser.dtr = False
                start = time.time()
                while not ser.dsr:
                    end = time.time() - start
                    capacity = "{0:2.2f} uf".format(end * 1000 - 80 * end)
                    #ser.dtr = True
                if end and capacity >= 0.5:
                    self.update_cap_display(capacity)
                    ser.close()
                    self.after_id = self.after(int(end * 5000), self.measure)
                else:
                    self.update_cap_display(
                        'capacity < 1 µf or not connected', True)
        except (serial.SerialException, IOError) as error:
            self.update_cap_display(error, True)

        if not self.after_id:
            self.start_stop_button.config(text='START')
            
    def release(self):
        self.parent.destroy()
    
def main():
    root = tk.Tk()
    root.title('CAPACITANCE')
    capacitance_ui = CapacitanceUI(root, WIDTH, HEIGHT, DEVICE)
    capacitance_ui.pack(expand=tk.YES, padx=5, pady=5)
    root.protocol("WM_DELETE_WINDOW",capacitance_ui.release)
    root.mainloop()
        
if __name__ == '__main__':
    main()
ist noch python 2

Gruß
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kaytec: Ich habe bei meiner Version `Serial` und `SerialException` explizit aus dem `serial`-Modul importiert damit der Name `serial` frei wird um ihn für ein `Serial`-Exemplar zu verwenden. `ser` ist eine doofe Abkürzung, denn eigentlich soll das ja `serial` heissen. Das heisst die beiden Ausnahmen bekomme ich nicht. Sorry, ich hätte die Import-Zeile dazu auch mit in den gezeigten Code schreiben sollen. :oops:

Das Du `close()` innerhalb des ``with``-Blocks brauchst, kann ich nicht glauben, denn das einzige was zwischen diesem exoliziten `close()` und dem impliziten durch das ``with``-Blockende kommt ist der `after()`-Aufruf. Für den ist der Zustand der seriellen Schnittstelle aber völlig egal. Andererseits wenn das nur dazu da ist um `serial.dtr` auf `False` zu setzen, dann mach doch besser genau *das*, dann fragt sich eine Leser nicht warum in dem ``with`` ein `close()` steht.

Dann will man das aber vielleicht auch weiter nach vorne ziehen, beziehungsweise sogar direkt nach der ``while``-Schleife machen. Es wäre sowieso eine Überlegung Wert die Messung selbst aus dem GUI-Code heraus zu holen, damit man das auch mal separat testen und/oder wiederverwenden kann.

Wo friert Dir denn die GUI ein? Die offensichtlichste Stelle ist die ``while``-Schleife, denn die läuft ja potentiell ewig wenn die Hardware nicht das macht was man erwartet. Das sollte man auf jeden Fall irgendwie absichern.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaytec: die offensichtliche Stelle, wo `capacity` in einen String verwandelt werden sollte, ist genau dort, wo __blackjack__ es auch schon gemacht hat, beim Aufruf von `update_cap_display`, denn sonst ist auch die if-Abfrage davor kaputt.

Wenn etwas mit einer potentiellen Endlosschleife läuft, sollte man einen Time-Out definieren. Zusätzlich könnte das "Messen" in einem separaten Thread laufen, der per Queue an die GUI angeschlossen ist, dann friert sie nie ein.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,
danke für eure Hilfe.

Ohne "ser.close()" wird das display nicht akutalisiert, es bleibt auf 00.00 uf stehen und der Button bleibt gedrückt.

Code: Alles auswählen

    def measure(self):
        self.after_id = None
        try:
            with serial.Serial(self.device) as ser:
                end = None
                ser.dtr = False
                start = time.time()
                while not ser.dsr:
                    end = time.time() - start
                    capacity = "{0:2.2f} uf".format(end * 1000 - 80 * end)
                    ser.dtr = True
                if end and capacity >= 0.5:
                    self.update_cap_display(capacity)
                    #ser.close()
                    self.after_id = self.after(int(end * 5000), self.measure)
                else:
                    self.update_cap_display(
                        'capacity < 1 µf or not connected', True)
        except (serial.SerialException, IOError) as error:
            self.update_cap_display(error, True)
Gruß
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

den Test auf "float" habe ich so gelöst:

Code: Alles auswählen

text = '{0:2.2f} uf'.format(text) if str(text).replace( ".", "", 1).isdigit() else text
Gruß
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kaytec: Das ist reichlich umständlich und sollte nicht nötig sein wenn man das an der richtigen Stelle formatiert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo BlackJack,

Sirius3 hatte auf diese Stelle verwiesen und ich kann den Text bei einem "error" nicht formatieren. War so meine Lösung auf "float" zu reagieren.

Gruß
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kaytec: Das hat Sirius3 nicht gemeint. Die Ausgabe ist hier die `update_cap_display()`-Methode. Davor sollte die Ausgabe natürlich formatiert werden. Das ersetzt ja sozusagen ein `print()` wenn es keine GUI wäre. Wenn Du das in dieser Methode formatierst hast Du ja genau dieses Problem was Du da gerade so unschön löst, was man nicht hätte wenn man es davor machen würde. Was Sirius3 meinte das man die Ausgabe nicht schon dauernd in der ``while``-Schleife formatieren sollte, sondern erst dann wenn das Ergebnis das formatiert werden soll auch tatsächlich fest steht.

Es ist an der Stelle ja nicht nur so, dass da ein kleines bisschen mit Ressourcen in Form von Speicher und Rechenzeit jongliert wird, die Rechenzeit die das braucht, wirkt sich auch auf die Messgenauigkeit aus. Spätestens wenn man die Messung in eine eigene Funktion auslagert, fällt auf dass die Formatierung an der falschen Stelle ist, denn von einer Funktion möchte man ja eine Zahl bekommen bei der man dann entscheiden kann was man damit macht, und nicht eine fertige Zeichenkette für eine Ausgabe.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke BlackJack,

ich verstehe dich gerade nicht. Wo soll es jetzt genau formatiert werden ? Nicht beim Einlesen und auch nicht in der update_cap_display()`-Methode ?
Wie das Script abläuft ist mir schon klar und auch das Auslagern in eine eigene Methode (keine Wiederholung von gleichem Code). Die nötige Unterscheidung von Text/Zahl entsteht doch durch die Zusammenführung ?
Gruß Frank
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Der betreffende Teil sollte besser so geschrieben werden:

Code: Alles auswählen

while not ser.dsr:
    end = time.time() - start
    capacity = end * 1000 - 80 * end
    # Oder direkt:
    # capacity = end * 920
    ser.dtr = True
if end and capacity >= 0.5:
    self.update_cap_display("{0:2.2f} uf".format(capacity))
    # u.s.w.
Allein schon, weil es sonst beim Vergleich mittels >= knallt, denn Vergleiche zwischen Zeichenketten und Zahlen machen halt wenig Sinn.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo Snafu,

Danke !
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo,

habe es mit "queue und threads" versucht und es funktioniert, doch so richtig bin ich nicht zufrieden und evt. ist es ja auch Müll.

https://www.oreilly.com/library/view/py ... 09s07.html

Code: Alles auswählen

#/usr/bin/env python
# -*- coding: utf-8
import Tkinter as tk
import serial
from functools import partial
import time
import threading
import sys
import Queue


WIDTH = 200
HEIGHT = 100
DEVICE = "/dev/ttyUSB0"

class CapacitanceUI(tk.LabelFrame):
    
    def __init__(self, parent, width, height, queue, start_stop_measure, text_color = "blue",
        error_font = "system 5 bold", display_font = "system 18 bold"):
        tk.LabelFrame.__init__(self, parent, text = "CAPACITANCE", 
            relief = "solid")
        self.ser = None
        self.after_id = None
        self.parent = parent
        self.width = width
        self.height = height
        self.queue = queue
        self.start_stop_measure = start_stop_measure
        self.text_color = text_color
        self.error_font = error_font
        self.display_font = display_font
        self.display = tk.Canvas(self,
            width = width,
            height = height,
            bg="cyan")
        self.display.pack(padx = 5, pady = 5)
        self.update_cap_display("00.00 uf", False)
        button_frame = tk.Frame(self, bg = "gray")
        button_frame.pack(padx = 5, pady = 5)
        self.start_stop_button = tk.Button(button_frame, text = "START",
            command = self.start_stop)
        self.start_stop_button.pack()
    
    def start_stop(self):
        if self.start_stop_button["text"] == "START":
            self.start_stop_button.config(text = "STOP")
            self.start_stop_measure(DEVICE)
        else:
            self.update_cap_display("00.00 uf", False)
            self.start_stop_button.config(text = "START")
            self.start_stop_button.config(state = "disabled")
            self.after(3000, self.start_stop_button.config(state = "active"))
            self.start_stop_measure()
            
    def update_cap_display(self, text, is_error):
        self.display.delete("cap", "error")
        self.display.create_text(
            self.width / 2, 
            self.height / 2, text = text, 
            font = self.error_font if is_error else self.display_font,
            fill = self.text_color,
            tag = "error" if is_error else "cap")
            
    def measure(self):        
        """Handle all messages currently in the queue, if any."""
        while self.queue.qsize():
            try:
                text, state = self.queue.get(0)
                self.update_cap_display(text, state)
                # Check contents of message and do whatever is needed. As a
                # simple test, print it (in real life, you would
                # suitably update the GUI's display in a richer fashion).

            except Queue.Empty:
                # just on general principles, although we don't
                # expect this branch to be taken in this case
                pass

        
class ThreadedClient(object):
    """
    Launch the main part of the GUI and the worker thread. periodicCall and
    endApplication could reside in the GUI part, but putting them here
    means that you have all the thread controls in a single place.
    """
    def __init__(self, parent):
        """
        Start the GUI and the asynchronous threads. We are in the main
        (original) thread of the application, which will later be used by
        the GUI as well. We spawn a new thread for the worker (I/O).
        """
        self.device = None
        self.parent = parent
        self.after_id = None
        # Create the queue
        self.queue = Queue.Queue()
        
        # Set up the GUI part
        self.gui =  CapacitanceUI(parent, WIDTH, HEIGHT, self.queue, self.start_stop)
        self.gui.pack(expand=tk.YES, padx=5, pady=5)

        # Set up the thread to do asynchronous I/O
        # More threads can also be created and used, if necessary
        self.running = False
        #self.thread = threading.Thread(target=self.workerThread)
        #self.thread.start()

        # Start the periodic call in the GUI to check if the queue contains
        # anything
    def start_stop(self, device = None):
        self.device = device
        if self.running:
            self.parent.after_cancel(self.after_id)
            self.after_id = None
            self.running = None
        else:
            self.running = True
            self.thread = threading.Thread(target=self.workerThread)
            self.thread.start()
            self.call_periodic()
            

    def call_periodic(self):
        """
        Check every 100 ms if there is something new in the queue.
        """
        self.gui.measure()
        self.after_id = self.parent.after(100, self.call_periodic)

    def workerThread(self):
        """
        This is where we handle the asynchronous I/O. For example, it may be
        a 'select(  )'. One important thing to remember is that the thread has
        to yield control pretty regularly, by select or otherwise.
        """
        while self.running:
            try:
                with serial.Serial(self.device) as ser:
                    end = None
                    ser.dtr = False
                    start = time.time()
                    while not ser.dsr:
                        end = time.time() - start
                        capacity = end * 1000 - 60 * end
                    ser.dtr = True
                    if end and capacity >= 1:
                        self.queue.put(("{0:2.2f} uf".format(capacity), False))
                    else:
                        self.queue.put(("capacity < 1 µf or not connected", True))

            except (serial.SerialException, IOError) as error:
                self.queue.put((error, True))
                
            if end:
                time.sleep(end * 3)
            else:
                time.sleep(1)
        
    def release(self):
        self.running = None
        self.parent.destroy()
        sys.exit(1)
        
def main():
    root = tk.Tk()
    root.title("CAPACITANCE")
    client = ThreadedClient(root)
    root.protocol("WM_DELETE_WINDOW",client.release)
    root.mainloop()
    
if __name__ == '__main__':
    main()
    
- Kann ich immer einen neuen "Thread" starten, denn macht evt, Sinn und so könnten auch andere Schnitstellen gewählt werden ?
- Kann ich die "queue" auch leeren, da beim Start eines neuen "threads" ja keine Daten mehr darin sein sollten.
- Drücke Ich den "button" sehr schnell, dann gibt es Fehlmessungen - steuere ich zeitlich den "button" aus dem "ThreadedClient" ?

Gruß Frank
Antworten