While True wird durch den break-befehl nicht beendet..

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
linuxer7
User
Beiträge: 14
Registriert: Mittwoch 12. Mai 2021, 18:24

Hallo Leute,
ich habe ein kleines Problem mit meinem Programm und verstehe nicht so ganz wieso es nicht das tut was es tun sollte.
Bin jetzt schon seit Stunden im Internet unterwegs, konnte aber leider noch keine Lösung finden.
Ich bin nicht so ganz Fit mit Python und eigentlich immer noch am lernen. Da ich nur ab und zu ein Programm schreibe fehlt mir da wohl die nötige "Übung".
Okay zum Problem.
Ich benutze 2 Rechner (Linux) um ein Filterrad zu Steuern. Die Verbindung zwischen den Programmen läuft über ein Socket-Programm.
Der Steuer-Rechner sendet einen Befehl an den Socket-Rechner und das Socket-Programm wertet es auf dem 2ten Rechner aus und steuert das Filterrad.

Code: Alles auswählen

import socket
import os
import serial


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("192.168.2.15", 50000))                                                 #Adresse auf der der Socket läuft
s.listen(1)
print("Server gestartet!"+"\n")

port ="/dev/ttyUSB0"
ser = serial.Serial(port, baudrate=9600, timeout=0)


def sende_sequenz(daten):
    sende_text = daten + "\n"
    print("daten werden übertragen : "+ daten)
    ser.write(sende_text.encode())
    

try: 
    while True: 
        komm, addr = s.accept()
        while True: 
            data = komm.recv(1024) 
            if not data: 
                komm.close()
                break
            daten = data.decode()
            
            print("Daten Empfangen :" + daten)
            
            if daten =="test":
                sende_sequenz("o1")
                empf_buffer = []
                
                while True:
                    for c in ser.read():
                        empf_buffer.append(str(chr(c)))
                        if c == 10:
                            wert = ''.join(empf_buffer)
                            print("Empfangen",wert)
                            ser.flushInput()
                            empf_buffer.clear()  
                            break
                
            print("Befehl ausgeführt !"+"\n")
            komm.send(daten.encode()) 
            
finally: 
    s.close()
Mein Problem ist dass das zweite "while True" durch den "break" nicht beendet wird.
Soweit funktioniert eigentlich alles. Wen ich über einen Test-Button auf dem Steuer-Rechner ein "test" sende dann wird das erkannt.
Die Anfrage an das Filterrad wird über die Serielle gesendet und das Rad meldet die Offset-Position in Form "P1 Offset 2" zurück.
Der Wert "10" wird erkannt und die Offset-Meldung wird angezeigt.
Ich erwarte jetzt das die while Schleife beendet wird und der letzte Befehl "print("Befehl ausgeführt !"+"\n")" ausgeführt wird, aber das ist nicht der Fall.

Ich bin mir sicher das es elegantere Lösungen gibt, aber momentan bin ich noch Anfänger, also habt bitte Nachsicht.

Gruß
Thomas
Benutzeravatar
sparrow
User
Beiträge: 4245
Registriert: Freitag 17. April 2009, 10:28

Vorab: Du hast eines der vielen kaputten Socket-Tutorials aus dem Internet kopiert. Socket-Programmierung auf dem Level ist komplex.
Ich weiß nicht, was der Name "komm" bedeuten soll, aber .recv() garantiert dir nicht, dass da alles gelesen wird, was ankomt. Ebenso garantiert .send() nicht, dass alles gesendet wird. Zumindest dafür gibt es .sendall().
Außerdem fehlt so etwas wie ein Protkoll.
Das wurde hier im Forum bereits oft und viel diskutiert. Du wirst das finden. Aber so ist es verkehrt und funktioniert nur zufällig und wenn die Sterne richtig stehen.

Ich würde dringend davon abraten Sockets auf dieser Ebene selbst zu prorammieren. Nimm etablierte Protokolle wie HTTP. Da gibt es entsprechende Module, die dir die Arbeit sowohl auf der Client- als auch auf der Serverseite abnehmen.

Das Problem mit deiner while-Schleife:
Vielleicht solltest du dir einmal ausgeben lassen was "c" für Werte annimmt. Ich kann mir schwer vorstellen, dass der Wert irgendwann einmal 10 ist. Also ein Integer mit diesem Wert.
Gerne kannst du hier zeigen, was "c" für Werte hat. Aber bitte so, wie Python sie ausgibt - nicht so, wie du denkst, dass das da steht. 10 ist nicht das selbe wie '10' ist nicht das selbe wie b'10'.

Code: Alles auswählen

>>> a = 10
>>> b = "10"
>>> c = b"10"
>>> a == b
False
>>> a == c
False
>>> b == c
False
Edit:
Verwende keine globalen Variablen.
Auf Modulebene (also uneingerückt) gehören nur Importe und die Definition von Konstanten, Funktionen und Klassen.
Alles andere gehört in eine Funkltion main() und am Ende des Codes wird die wie folgt aufgerufen:

Code: Alles auswählen

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@linuxer7: `os` wird importiert, aber nirgends verwendet.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben, und `sende_sequenz()` fehlt die serielle Schnittstelle als Argument. Wenn das Hauptprogramm in einer Funktion verschwunden ist, dann ist die Schnittstelle ja lokal in der Funktion und nicht mehr global auf Modulebene.

Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur daraus bestehen. `s` ist definitiv zu nichtssagend für ein `server_socket`. Und `ser` wäre als `serial_connection` wesentlich verständlicher. Bei `komm` hatte sparrow ja bereits nicht raten können wofür das wohl stehen mag. So allgemein wäre `client_socket` wahrscheinlich richtig.

Das Server-Socket wird geschlossen, die serielle Verbindung aber nicht. Sockets und `Serial`-Objekte sind Kontextmanager, die sollte man wo das möglich ist, einfach mit der ``with``-Anweisung verwenden, damit an den entsprechenden Stellen aufgeräumt wird.

An eine literale Zeichenkette ein Zeilenendezeichen mit ``+ "\n"`` ist unnötig umständlich — das Zeichen hätte man da auch gleich in die literale Zeichenkette davor schreiben können.

Sowohl `baudrate` als auch `timeout` entsprechen den Defaultwerten — die muss man also nicht angeben. Bei `timeout` würde ich auch `None` unmissverständlicher finden als 0.

Das Protokollproblem kann man leicht lösen falls ein zeilenorientiertes Protokoll in Frage kommt, denn dann kann man sich von einem Socket-Objekt ein Datei-Objekt geben lassen. Das sogar die (De)Kodierung von Text erledigt.

Dann braucht man auch nicht `data` und `daten` die im Grunde das gleiche enthalten, einmal als Bytes und einmal als Text, im gleichen Namensraum. Das ist verwirrend.

Die ``while``-Schleife wird in der Tat nicht vom ``break`` abgebrochen, denn bis dort hin kommt der Code nie, weil die schon vorher von einem `TypeError` beendet wird. `Serial.read()` liefert Werte vom Typ `bytes` und `chr()` kann damit nichts anfangen:

Code: Alles auswählen

In [1]: chr(b"a")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [1], in <cell line: 1>()
----> 1 chr(b"a")

TypeError: an integer is required (got type bytes)
Das scheint mir da aber auch irgendwie der komplizierte Versuch die `readline()`-Methode nachzuprogrammieren.

`flushInput()` ist veraltet und wird irgendwann verschwinden. Die Methode heisst jetzt `reset_input_buffer()`.

Überarbeitet (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import socket

from serial import Serial


def sende_sequenz(serial_connection, daten):
    sende_text = daten + "\n"
    print("Daten werden übertragen: " + daten)
    serial_connection.write(sende_text.encode())
    serial_connection.flush()


def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
        server_socket.bind(("192.168.2.15", 50_000))
        server_socket.listen(1)
        print("Server gestartet!\n")

        with Serial("/dev/ttyUSB0") as serial_connection:
            while True:
                client_socket, _ = server_socket.accept()
                with client_socket:
                    client_connection = client_socket.makefile(
                        "rw", encoding="utf-8"
                    )
                    for line in client_connection:
                        print("Daten Empfangen:" + line, end="")

                        if line == "test\n":
                            sende_sequenz(serial_connection, "o1")
                            wert = next(serial_connection).decode("ascii")
                            serial_connection.reset_output_buffer()
                            print("Empfangen", wert)

                        print("Befehl ausgeführt!\n")
                        client_connection.write(line)
                        client_connection.flush()


if __name__ == "__main__":
    main()
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

@sparrow und __blackjack__: `ser.read()` liefert bytes und eine for-Schleife über ein Bytes-Objekt liefert direkt int-Werte.

@linuxer7: Du hast eine for-Schleife innerhalb einer while-Schleife innerhalb einer while-Schleife innerhalb einer while-Schleife. Das sind ziemlich viele Verschachtelungsebenen, da kann man schonmal mit einem break durcheinanderkommen. Alles über zwei Ebenen ist zu viel und sollte dadurch aufgelöst werden, dass man es in mehrere Funktionen unterteilt.

Du brichst also mit break nur die for-Schleife ab, nicht aber die while-Schleife.
Benutze `ser.read_until`, was sowohl die for als auch die while-Schleife überflüssig macht.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1038
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Der Teil, der die Client-Verbindung bedient, sollte einfach in eine Funktion ausgelagert werden. Dann hat man auch gleichzeitig die Vorbereitung mehr als eine Verbindung gleichzeitig zu bedienen (auch wenn es nicht notwendig ist).

Basierend auf dem, was blackjack geschrieben hat:

Code: Alles auswählen

#!/usr/bin/env python3
import socket

from serial import Serial


def sende_sequenz(serial_connection, daten):   
    # f-strings sind schneller bei der Konstruktion
    sende_text = f"{daten}\n"
    print(f"Daten werden übertragen {daten}")
    serial_connection.write(sende_text.encode())
    serial_connection.flush()


def handle_client(client_socket, serial_connection):
    with client_socket:
        client_connection = client_socket.makefile(
            "rw", encoding="utf-8"
        )
        for line in client_connection:
            print(f"Daten Empfangen: {line}", end="")

            if line == "test\n":
                sende_sequenz(serial_connection, "o1")
                wert = next(serial_connection).decode("ascii")
                serial_connection.reset_output_buffer()
                print("Empfangen", wert)

            print("Befehl ausgeführt!\n")
            client_connection.write(line)
            client_connection.flush()


def main():
    # socket.AF_INET, socket.SOCK_STREAM ist der Standard und kann auch entfallen
    # kann sich aber ändern, wenn IPv6 mal endlich überall eingesetzt wird
    with socket.socket() as server_socket:
        server_socket.bind(("192.168.2.15", 50_000))
        server_socket.listen(1)
        print("Server gestartet!\n")

        with Serial("/dev/ttyUSB0") as serial_connection:
            while True:
                client_socket, _ = server_socket.accept()
                # hier wird jetzt die Funktion client_connections aufgerufen
                # und blockiert so lange, bis die client-verbindung beendet ist
                # client_socket und serial_connection müssen an die Funktion übergeben
                # werden, da die Objekte in der Funbktion main() durch die Funktion
                # client_connections nicht sichtbar sind. Sie sind in einem anderen Scope.
                
                handle_client(client_socket, serial_connection)
                # client-verbindung beendet, hier gehts jetzt weiter


if __name__ == "__main__":
    main()

Das könnte man einfacher als Klasse haben:

Code: Alles auswählen

#!/usr/bin/env python3
import socket

from serial import Serial


class Server:
    def __init__(self, ip, port, serial_port):
        self.server_socket = socket.socket()
        self.server_socket.bind((ip, port))
        self.server_socket.listen(1)
        self.serial_connection = Serial(serial_port)
        print("Socket und serielle Verbindung geöffnet")
                    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_obj, exc_tb):
        self.server_socket.close()
        self.serial_connection.close()
        print("Socket und serielle Verbindung geschlossen")
    
    def sende_sequenz(self, daten):   
        # f-strings sind schneller bei der Konstruktion
        sende_text = f"{daten}\n"
        print(f"Daten werden übertragen {daten}")
        self.serial_connection.write(sende_text.encode())
        self.serial_connection.flush()
        
    def handle_client(self, client_socket):
        with client_socket:
            client_connection = client_socket.makefile(
                "rw", encoding="utf-8"
            )
            for line in client_connection:
                print(f"Daten Empfangen {line}", end="")

                if line == "test\n":
                    self.sende_sequenz("o1")
                    wert = next(self.serial_connection).decode("ascii")
                    self.serial_connection.reset_output_buffer()
                    print("Empfangen", wert)
                    
                elif line == "QUIT\n":
                    # Befehl QUIT beendet diese Schleife
                    break

                print("Befehl ausgeführt!\n")
                client_connection.write(line)
                client_connection.flush()
                
        

    def run(self):
        while True:
            client_socket, (ip, _) = self.server_socket.accept()                
            print(f"Neue Client-Verbindung von {ip}, die nun abgearbeitet wird")
            self.handle_client(client_socket)
            print("Client Verbindung beendet")


def main():
    Server("0.0.0.0", 50_000, "/dev/pts/1").run()


if __name__ == "__main__":
    main()
Für den Test habe ich einfach /dev/pts/1 verwendet, was das Pseudo-Terminal ist.
Das sendet natürlich nicht die erwarteten Daten zurück und der Prozess bleibt so lange stehen, bis er eine Antwort bekommt.
D.h. wenn bei dir /dev/ttyUSB0 nicht antwortet, bleibt das Programm einfach stehen.

Abfangen kann man dieses Problem, indem man mit Timeouts arbeitet.
Der Server-Socket bekommt keinen Timeout, da dieser auf die Verbindung des Clients warten muss und beim accept einen TimeoutError auslösenn würde.
Der Client-Socket sollte einen Timeout bekommen, dass falls sich dieser aufhängt, der Server-Prozess den Client wieder trennt.
Die Serielle Verbindung bekommt einen Timeout, damit sich der Server-Prozess nicht aufhängt, falls der serielle Client nicht mehr antwortet.

Basierend auf dem letzten Beispiel mit der Klasse:

Code: Alles auswählen

#!/usr/bin/env python3
import socket

from serial import Serial


class Server:
    def __init__(self, ip, port, serial_port, timeout):
        self.server_socket = socket.socket()
        self.server_socket.bind((ip, port))
        self.server_socket.listen(1)
        self.serial_connection = Serial(serial_port, timeout=timeout)
        self.timeout = timeout
        print("Socket und serielle Verbindung geöffnet")
                    
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_obj, exc_tb):
        self.server_socket.close()
        self.serial_connection.close()
        print("Socket und serielle Verbindung geschlossen")
    
    def sende_sequenz(self, daten):   
        # f-strings sind schneller bei der Konstruktion
        sende_text = f"{daten}\n"
        print(f"Daten werden übertragen {daten}")
        self.serial_connection.write(sende_text.encode())
        self.serial_connection.flush()
        
    def handle_client(self, client_socket):
        with client_socket:
            client_socket.settimeout(self.timeout)
            client_connection = client_socket.makefile(
                "rw", encoding="utf-8"
            )
            for line in client_connection:
                print(f"Daten Empfangen {line}", end="")

                if line == "test\n":
                    self.sende_sequenz("o1")
                    wert = next(self.serial_connection).decode("ascii")
                    self.serial_connection.reset_output_buffer()
                    print("Empfangen", wert)
                    
                elif line == "QUIT\n":
                    # Befehl QUIT beendet diese Schleife
                    break

                print("Befehl ausgeführt!\n")
                client_connection.write(line)
                client_connection.flush()
                
        

    def run(self):
        while True:
            client_socket, (ip, _) = self.server_socket.accept()                
            print(f"Neue Client-Verbindung von {ip}, die nun abgearbeitet wird")

            try:
                self.handle_client(client_socket)
            except TimeoutError:
                print("Client hat nicht schnell genug geantwortet")
            except StopIteration:
                # durch den Aufruf über die funktion next() wird bei
                # Serial() eine StopIteration final ausgelöst wenn die Verbindung
                # geschlossen wird oder ein TimeoutError kommt.
                print("Serielles Endgerät antwortete nicht schnell genug")
            else:
                print("Client Verbindung normal beendet")


def main():
    Server("0.0.0.0", 50_000, "/dev/pts/1", timeout=10).run()


if __name__ == "__main__":
    main()

sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
linuxer7
User
Beiträge: 14
Registriert: Mittwoch 12. Mai 2021, 18:24

Hallo Leute.
vielen Dank für die viele Arbeit die Ihr euch gemacht habt.
Da ich nicht euren Wissensstand habe muss ich mir das alles mal in Ruhe ansehen und versuchen zu verstehen.
Das Socket-Programm ist von mir abgespeckt worden und was unnötig ist wurde entfernt um nur die Relevante Problematik zu beschreiben.
Ich benutze eine ähnlich Socket-Programmierung schon seit über einem Jahr und die hat bis jetzt ohne Probleme funktioniert, allerdings findet dort keine
Serielle Kommunikation statt sondern es werden nur einfache Befehle an den 2ten Rechner gesendet die dann vom System ausgeführt werden.
In dem anderen Fall sind es libcamera Befehle für die Pi-cam deswegen auch der noch enthaltene OS-Import.
Auch in dem neuen Socket benutze ich OS-Befehle, die wurden nur von mir entfernt.

Die Abfrage nach dem Inhalt 10 Funktioniert zuverlässig, nur wird die Schleife halt nicht beendet.

Code: Alles auswählen

Daten Empfangen :test
daten werden übertragen : o1
80
49
32
79
102
102
115
101
116
32
50
13
10
Empfangen P1 Offset 2
Das ist die Ausgabe von print(c).

Wie gesagt ich benutze Python nicht so oft obwohl es Spaß macht ab und zu ein Programm zu schreiben. Allerdings verstehe ich noch zu wenig von Python und bin eigentlich immer noch am Probieren solange bis es funktioniert. :wink:
Beispiele, Erklärungen und Vorschläge finde ich natürlich im Internet allerdings bin ich nicht in der Lage zu erkenne was veraltet ist und was nicht.
Der letzte Weg wenn es auch nach Tagen der Recherche nicht funktioniert führt dann ins Forum.

Ich muss eure Beispiele erst mal durchgehen und versuchen zu Verstehen, deshalb erstmal danke.

Gruß
Thomas
Antworten