Wahlweises Senden von UDP- und TCP-Befehlen

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
chq
User
Beiträge: 13
Registriert: Mittwoch 27. Februar 2019, 19:44

Hallo,

ich befasse mich mit Python aufgrund einer konkreten Aufgabenstellung. Ich möchte via Raspberry, Touchscreen und einer tkinter-GUI UDP- und TCP-Befehle versenden.

Mittlerweile bin ich an dem Punkt angekommen, an dem ich mich folgendem Teilbereich der o.a. Aufgabenstellung widmen möchte:

Ich möchte erreichen, dass eingangs im Quellcode möglichst einfach definert werden kann, welche Geräte (Anzahl Definitionssache) wie (siehe weiter unten unter "Parameter") angesprochen werden sollen.

Ein Beispiel:

Es sollen zwei Geräte angesprochen werden können.

1. Gerät: TCP, IP: 192.168.0.111, Port 5555, Carriage return als Abschlusszeichen
2. Gerät: UDP, IP: 192.168.0.117, Port 3050, kein Abschlusszeichen

In den einzelnen Buttons (bereits definiert), soll dann nur noch definiert werden müssen, welches Gerät welchen Befehl bekommen soll. Über die Wahl des Geräts innerhalb des Buttons soll sich dann der Einfachheit halber auch das entsprechend zu sendende Abschlusszeichen definieren.

Ich möchte an dieser Stelle die aus meiner Sicht relevanten Parameter angeben:

1. UDP oder TCP
2. IP-Adresse des Empfangsgeräts
3. Port des Empfangsgeräts
4. Abschlusszeichen (CR, LF, CR+LF, LF+CR, <userDefined>)

Ich habe zwar ein sehr dickes Buch vor mir liegen, weiss jedoch nicht nach was ich suchen muss, um die o.a. Aufgabenstellung mögl. effektiv zu lösen.

Ich würde mich an dieser Stelle somit bereits über Stichworte , nach denen ich suchen kann, freuen.



Gute Grüße- Chris
Benutzeravatar
Perlchamp
User
Beiträge: 178
Registriert: Samstag 15. April 2017, 17:58

@ chq:
ahoi, ich bin auch Anfänger ...
meine Frage(n) diesbezüglich:
1. warum über Abschlußzeichen und nicht über beispielsweise (vorangestellte) Zahlen/Zeichen ?

Stichwörter:
Tkinter (get, cget, config; ...)
Python (Klasse, Tupel, Modul re ...)

alles, wie immer, ohne Gewähr ...
Irgendwas ist immer !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 18266
Registriert: Sonntag 21. Oktober 2012, 17:20

Solche Parameter konfiguriert man üblicherweise mit einer Konfigurationsdatei. YAML ist glaube ich gerade sehr beliebt dafür. Mit dem passenden YAML-Parser wird das dann in Wörterbücher überführt, was dann von Dir in Instanzen einer TCP- bzw. UDP-Sender-Klasse umgewandelt werden könnte.
chq
User
Beiträge: 13
Registriert: Mittwoch 27. Februar 2019, 19:44

Was wäre denn das Mittel der Wahl, wenn ich die anzusteuernden Geräte nicht in einer zusätzlichen Konfigurationsdatei auslagern wollen würde?

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

Musst du am Ende doch sowieso. Oder willst du die Daten nicht persistent speichern sondern jedes Mal neu eingeben?

Ansonsten brauchst du entweder ein CLI oder ein GUI um die Daten zu erfassen.
chq
User
Beiträge: 13
Registriert: Mittwoch 27. Februar 2019, 19:44

Genau. Ich möchte die Daten für die einzelnen zu steuernden Geräte, deren Anzahl zudem schwankt, jedesmal erneut eingeben, bevor ich den Code ausführe.

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

Dann entweder ein CLI bauen, indem man die Daten nacheinander abfragt.
Oder du musst dir eine entsprechende GUI mit TK oder einem anderen Toolkit bauen. Worauf es wohl hinausläuft, wenn du einen Touchscreen hast. Da du ja schon Buttons hast, hast du auch eine GUI. Dann brauchst du wohl entsprechende Textfelder um entsprechende Daten einzugeben und dafür dann wahrscheinlich auch eine (hoffentlich vom System gelieferte) Bildschirmtastatur.
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@chq: Dann bin ich jetzt etwas verwirrt. Im Ausgangsbeitrag stand das noch anders.

Was hast Du gegen Konfigurationsdateien? Es muss ja nicht nur eine sein, man kann das Programm ja so schreiben das am Anfang eine angegeben/ausgewählt werden kann.

Ich denke Du musst noch mal etwas genauer beschreiben wie Du Dir das vorstellst, denn im Ausgangsbeitrag hört sich das so an als sollte die Konfiguration im Quelltext stehen. Soll sie anscheinend nicht, sondern es soll Code sein der den Anwender nach der Konfiguration fragt? Da es ein GUI-Programm ist: soll das in der GUI passieren? Muss er da wirklich die Details eingeben, oder würde es auch reichen wenn er dort eine Konfigurationsdatei mit den Daten über einen Dateidialog auswählen kann?

Edit: Und selbst wenn man alles über die GUI eingibt, sehe ich da trotzdem noch eine Konfigurationsdatei, denn der Benutzer möchte ja sicher die Eingaben am Ende speichern, damit er sie nicht tatsächlich bei jedem Neustart erneut eingeben muss.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
chq
User
Beiträge: 13
Registriert: Mittwoch 27. Februar 2019, 19:44

Ich stelle es mir so vor, dass ich innerhalb des Pythoncodes die entspr. Geräte angebe, um diesen dann nach ausführen des Codes über die GUI entsprechende Befehle senden zu können.

Die zu sendenden Befehle (exkl. der evtl. zu sendenden Abschlusszeichen) würde ich dann in den einzelnen Buttons hinterlegen.

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

@chq: Wo ist denn da nun das konkrete Problem? Gib halt die Daten in einer geeigneten Datenstruktur als Konstante am Anfang des Quelltextes an und verarbeite die im Programm entsprechend. Wobei ich da wenigstens optional Konfigurationsdateien vorsehen würde. Man will normalerweise nicht dauernd den Quelltext ändern wenn sich eigentlich bloss *Daten* ändern, der Code aber gleich bleibt.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Ich hab heut meinen Tkinter Tag...
Ich hab mal etwas gespielt. Du kannst die Geräte sowohl in der Konstante als auch via File eintragen.
fund(do_tcp) ist nur ein Beispiel
Laden und Speichern geht. Wobei das Speichern nur für die das erste Tuple aus dem Dict funktioniert. Da hapert das Dict-> Str. Aber ich komm nicht drauf wie man das lösen kann. Als StringVar setzen?

Ist ja auch schon spät..
@all könnt ihr mir da auf die Sprünge helfen? Ich war wirklich bemüht aber der Kopf qualmt gerade...
Welche Anmerkungen habt ihr dazu?

Code: Alles auswählen

import tkinter as tk
from tkinter import filedialog, messagebox

SETTINGS = {1: {"name": "RK3399",
                "IP": "192.168.0.111",
                "Port": 5555,
                "pastfix": "CR"
                },
            2: {"name": "Pi3B",
                "IP": "192.168.0.117",
                "Port": 3050,
                "pastfix": "CR+LF"
                }
            }


class Tochbar(object):
    def __init__(self):
        self.datei = ""
        self.engines = SETTINGS
        self.fenster = tk.Tk()
        self.fenster.title("Tcp - UDP")
        tk.Frame(self.fenster, width=500, height=500).pack()
        self.label = tk.Label(self.fenster,
                              font=("Arial", 10))
        self.label.pack(expand=1)
        self.__add_menueleiste()
        self.__add_dateimenue()
        self.__add_formatmenue()
        self.fenster.mainloop()

    def __add_menueleiste(self):
        self.menueleiste = tk.Menu(self.fenster)
        self.fenster.configure(menu=self.menueleiste)

    def __add_formatmenue(self):
        self.formatmenue = tk.Menu(master=self.menueleiste)
        self.menueleiste.add_cascade(label="Geräte", menu=self.formatmenue)
        self.rechnertyp = tk.StringVar()
        for engine in self.engines:
            self.formatmenue.add_radiobutton(label="{}".format(self.engines.get(engine)["name"]),
                                             variable=self.rechnertyp,
                                             value=engine, command=self.do_tcp)

    def __add_dateimenue(self):
        self.dateimenue = tk.Menu(self.menueleiste)
        self.menueleiste.add_cascade(label="Datei", menu=self.dateimenue)
        self.dateimenue.add_command(label="Laden", command=self.laden)
        self.dateimenue.add_command(label="Speichern unter", command=self.speichern)
        self.dateimenue.add_separator()
        self.dateimenue.add_command(label="Ende", command=self.beenden)

    def do_tcp(self):
        rechner = self.rechnertyp.get()
        self.label.config(
            text=self.engines.get(int(rechner))["name"])  # Hier kann man dann weitere Funktionen jeweils aufbauen

    def laden(self):
        self.datei = tk.filedialog.askopenfile()
        self.engines.delete(1.0, tk.END)
        if self.datei:
            self.engines.insert(1.0, self.datei.read())
            self.datei.close()

    def speichern(self):
        self.datei = tk.filedialog.asksaveasfile()
        print(self.engines.items())
        if self.datei:
            self.datei.write(
                str(self.engines.get(1.0, tk.END)))  # speichert nur das erste Tupel ab, Fehler bei Dict->str
            self.datei.close()

    def beenden(self):
        if messagebox.askyesno("Beenden", "Wollen sie wirklich beenden?"):
            self.fenster.destroy()


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

@Tholo: Laden und speichern geht? In beiden Methoden versuchst Du `self.engines` wie ein `Entry` zu verwenden, das ist aber ein Wörterbuch!

Wenn Du tatsächlich das Wörterbuch meinst, greifst Du immer mit ``a_dict.get(…)`` zu statt mit ``a_dict[…]``. Das macht Sinn wenn man danach dann auch prüft ob man einen Wert oder `None` zurückbekommen hat, Du machst aber immer einfach weiter in der Annahme es ist nicht `None`. Das vermeidet einen `KeyError` direkt beim Zugriff auf Kosten eines `TypeError` der dann halt später mit den `None` passiert. Das macht so keinen Sinn.

Wobei ein Wörterbuch mit fortlaufend nummerierten Einträgen ja doch eher eine Liste sein sollte. Die Schlüssel werden ja nur intern verwendet, die müssen also auch nicht bei 1 anfangen.

`filedialog` wird importiert, dann aber nicht direkt sondern über den Umweg über `tk` angesprochen.

Was soll der Name `Tochbar` bedeuten?

In Python 3 braucht man nicht mehr von `object` erben, das passiert implizit.

Die doppelten führenden Unterstriche bei den `__add_*`-Methoden sollten nur *ein* führender Unterstrich sein. Wobei ich diese Methoden wahrscheinlich sowieso nicht aus der `__init__()` heraus gezogen hätte.

Eine `__init__()` sollte dem Aufrufer ein benutzbares Objekt hinterlassen und nicht in einer GUI-Hauptschleife hängen bis alles vorbei ist.

Wenn man über Schlüssel *und* Wert eines Wörterbuchs iterieren möchte, gibt es die `items()`-Methode.

Wenn der Wert aus einer `StringVar` immer in eine ganze Zahl umgewandelt werden muss, würde es Sinn machen gleich eine `IntVar` stattdessen zu verwenden. Den Wert mal `engine` und mal `rechner` zu nennen und das `*Var`-Objekt Rechnertyp ist verwirrend. Zumal es gar kein Typ sein muss. Es können ja auch mehrere Geräte vom gleichen Typ aber unterschiedlichen IPs in `SETTINGS` stehen.

Das `datei`-Attribut ist unsinnig. Das wird in der `__init__()` als Zeichenkette gesetzt, in `laden()` und `speichern()` dann aber an ein Dateiobjekt gebunden. Das ausserhalb dieser beiden Methoden gar nicht gebraucht/verwendet wird, also ist das dort eigentlich nur eine lokale Variable. Schliessen sollte man dort dann auch mit der ``with``-Anweisung sicherstellen.

Beim Laden/Speichern ist mir auch nicht so ganz klar was Du da *eigentlich* machen willst. Ich hoffe doch mal stark dass das kein eigenes Dateiformat werden sollte. Die Daten lassen sich ganz wunderbar als JSON (de)serialisieren. Wobei Speichern nur Sinn machen würde, wenn man in der GUI auch etwas daran ändern könnte.

Grundsätzlich würde ich die GUI und die Programmlogik besser trennen. Du hast da ja nur GUI und Einstellungen, und gar nicht die tatsächliche Logik dazwischen. Es wäre ja aber eher so das man aus den Einstellungen die entsprehende Programmlogik aufbaut und *darauf* dann die GUI.

Bei den `SETTINGS` würde ich bei Schlüsselnamen bei Python-Namenskonventionen bleiben. Dann kann man beispielsweise `addict.Dict` verwenden um einige Schlüsselzugriffe etwas hübscher als Atttributzugriffe zu schreiben, oder leichter mit Bibliotheken wie Marshmallow zwischen Objekten und ”JSON” zu konvertieren.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Nach etwas Schlaf und deinen Erläuterungen klickt es in meinem Kopf jetzt auch.

Das komplett mit einem Json durchzuführen... Daran hatte ich gestern überhaupt nicht gedacht. Aber natürlich völlig logisch!

Die Klasse hab ich Touchbar benannt wegen Touchscreen und durchführbar.

Ich hatte zuerst Probleme "filedialog" normal aufzurufen. Dann aber nicht mehr betrachtet. Vielleicht hat sich die IDE nur verschluckt. Mein Fehler.
Eine `__init__()` sollte dem Aufrufer ein benutzbares Objekt hinterlassen und nicht in einer GUI-Hauptschleife hängen bis alles vorbei ist.
Hier verstehe ich nicht was du meinst. Lieber eine Main Funktion erststellen und dann aufrufen?

Die Gui auslagern ist klar. Aber dafür wären mehr Infos zur Logik nötig gewesen.

Ich wollte ja nur Möglichkeiten aufzeigen. Aber mit einem Json hätte ich mich natürlich viel viel leichter getan als dieses Konstrukt.
Benutzeravatar
__blackjack__
User
Beiträge: 14032
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tholo: Ahh, also Fipptehler und „Denglisch“. Schönes Beispiel warum das nicht gut ist, denn es ist hier doppeldeutig. Das Wort „bar“ hat ja auch im Englischen eine Bedeutung. Wobei diese Eigenschaft bzw. dass das später mal mit einem Touchscreen verwendet wird, wenig mit der Klasse an sich zu tun hat. Die kann nichts was speziell auf eine Bedienung per Touchscreen ausgerichtet ist. Ich würde das einfach `App` oder `Application` nennen.

Du weist das erstellte Objekt ja dem Namen `start` zu. Allerdings ist das gesamte Programm bereits gelaufen und beendet *bevor* diese Zuweisung tatsächlich statt findet. An der Stelle wo `Tochbar` (sic!) aufgerufen wird, bekommt der Aufrufer nie ein benutzbares Objekt in die Finger. Das ist ein „code smell“. Man würde hier den Aufruf der `mainloop()` in eine eigene Methode stecken, beispielsweise `run()` oder auch `mainloop()`. Und das ganze auch noch in eine `main()`-Funtkion stecken, damit `start` nicht auf Modulebene definiert wird. Wobei der Name auch komisch ist. Das Objekt repräsentiert ja keinen Start sondern die GUI-Anwendung.

Also beispielsweise:

Code: Alles auswählen

def main():
    app = App()
    app.run()
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Tholo
User
Beiträge: 177
Registriert: Sonntag 7. Januar 2018, 20:36

Natürlich... An Denglisch Doppeldeutigkeit hab ich nicht gedacht und fand das gestern eher belustigend.Sollte "Touchbar" heißen...
Ich denke auch wenn man Pep8 gelesen und verstanden hat, sorgt das nicht Automatisch dafür das die Benennungen richtig oder zutreffend sind. Ich denke das kommt mit der Erfahrung.
Ich werd mich diesen Punkten aber annehmen! (Ich war schon glücklich if __name__ == "__main__" eingebaut zu haben ;)

Berechtigterweise legt ihr aber Beispielcode gleich auf die Goldwaage ;)
Aber das ist nur Richtig! Falsch abschreiben bringt halt leider auch nix!
Antworten