RasPi iobroker SimpleApi Python

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

Moinsens!

Siehe Stichworte im Betreff: Ich bastel mir gerade ein Dashboard, auf dem ich einige Werte aus dem iobroker auslesen und anzeigen möchte. Mittels SimpleApi habe ich erste Erfolge, bei denen ich mir z.B. die aktuellen und Ziel-Temperaturen von 2 3D-Druckern (octoprint.0 und .1) und von 3 Heizkörper-Thermostaten (fritzdect.0.DECT_xxx) anzeigen lasse.

Nun ist es aber sehr "ruckelig", wenn ich per http-Aufruf alle Werte einzeln abrufe, wenn sie angezeigt werden sollen. (Das Dashboard hat mehrere Seiten, durch die geblättert werden kann.) Also dachte ich mir, ich bastel mir eine Daten-Struktur, die "im Hintergrund" per Timer auf aktuellem Stand gehalten wird. Das Intervall kann dabei recht unterschiedlich sein. Heizkörper sind alle 60 Sekunden eher zu viel, Jog-Positionen des Druckers in 1 Sekunde schon fast zu langsam aktualisiert. Aber ok, das wäre Finetuning später.

Ich hab mir jetzt recht aufwändig Klassen gebastelt, die die Struktur im iobroker abbilden. Also z.B. sowas wie:

Code: Alles auswählen

# octoprint.0
class cls_octoprint():
# -----------------------------------------------------------------------------
    # octoprint.0.command
    class cls_command():
# -----------------------------------------------------------------------------
        # octoprint.0.command.jog
        class cls_jog():
            x = None
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def readall( self ):
            getList = [ self.channel + ".x", self.channel + ".y", self.channel + ".z" ]
            [ ... ]
            self.x = valList[0]
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def get( self, node ): # e.g. "x"
            self.channel = parent().channel + ".jog" # e.g. "octoprint.0.command.jog"
            print( "node: " + self.channel + "." + node ) # e.g. "octoprint.0.command.jog.x"
            [ ... ]
            if ( node == "x" ):
                retval = self.x
            [ ... ]
            return retval
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
            def __init__( self ):
                pass
        # end cls_jog
# -----------------------------------------------------------------------------
        jog = cls_jog()
        [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def readall( self ):
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def get( self, node ): # e.g. "jog.x"
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def __init__( self ):
            pass
    # end cls_command
Dazu dann noch:

Code: Alles auswählen

class cls_iobroker():
    octoprint0 = None
    octoprint1 = None
    fritzdect0 = None
    def __init__():
        octoprint0 = cls_octoprint();
        octoprint0.setinstance( 0 )
        octoprint1 = cls_octoprint();
        octoprint1.setinstance( 1 )
        fritzdect0 = cls_fritzdect();
Der Gedanke dahinter:
Das Hauprogramm erzeugt mittels

Code: Alles auswählen

iob = cls_iobroker
ein Abbild der benötigten Strukturen. Ich kann dann mittels

Code: Alles auswählen

bed0tmp = iob.get( "octoprint", 0, "tools.bed.actualTemperature" )
den benötigten Wert auslesen. (Die get() Funktion leitet ggf. an die get() vom passenden Child, wenn ein "." im Request steht.) Dieser wird dann aus Instanz-Variablen gespeist, statt per get aus dem iobroker gelesen zu werden. Parallel dazu läuft ein Thread (oder auch mehrere), wo die Instanz-Variablen mit Werten aus dem iobroker per urlopen() gefüttert werden, alle 60 Sekunden oder so.

Nun frage ich mich, ob ich so auf dem richtigen Weg bin oder ob das alles eher totaler Murks ist. Tatsache ist: Spätere Anpassungen, wenn sich in der iobroker Struktur der Daten was ändert, sind schon eher aufwändig. Und mich stört auch der Pflegeaufwand der Funktionen, z.B. get(), in den einzelnen Klassen. Die machen prinzipiell ja das gleiche, aber eben auch individuell. Da bin ich auch am Überlegen, ob man das nicht generischer bauen könnte.

Was meint ihr dazu?

Michael
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die verschachtelten Klassen, Klassenattribute statt Instanzattributen, leeren __init__s und ueberfluessigen Klammern um if-Bedingungen sehen schon mal alle nicht so gut aus. Du scheinst hier Klassen als Datenstruktur selbst zu missbrauchen, das macht man so nicht.

Da das Ganze zu entkernt ist, um die eigentliche Funktionalitaet erkennen zu koennen, kann ich mangels iobroker-Kenntnissen zumindest da nicht mehr sagen.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Klassen erkennt man daran, dass sie mit großem Anfangsbuchstaben geschrieben werden, nicht daran, dass man ihnen ein cls_-Präfix verpasst. Oder hast Du schon irgendwo anders diese Namensgebung gelesen?
Diese nichtssagenden Kommentare, die nur aus Minuszeichen oder Punkten bestehen, können auch gleich gelöscht werden, die behindern das Lesen, anstatt dass sie den Code strukturieren.
Klassen verschachtelt man nicht ineinander, und auch sonst scheinen die Einrückungen nicht zu passen. Aus den Codefragmenten kann man nicht viel herauslesen.
Einem __init__ fehlt ein `self` und Klassenattribute sind kein Ersatz für Instanzattribute.

Nachdem eine Instant initialisiert wurde, sollte sie vollständig nutzbar sein; dass Du nachträglich mit setinstance etwas setzen willst, ist nicht gut. ; braucht man in Python nicht.

Wo ist das Hauptprogramm? Wo wird hier etwas angezeigt oder abgefragt? Bevor Du hier etwas im Hintergrund abfragen willst, sollte die einfache Variante mit dem sequenzellen Abfragen solide programmiert sein.
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

__deets__ hat geschrieben: Sonntag 13. März 2022, 15:21 Die verschachtelten Klassen, Klassenattribute statt Instanzattributen, leeren __init__s und ueberfluessigen Klammern um if-Bedingungen sehen schon mal alle nicht so gut aus.
Einiges, speziell die leeren inits, sind als Platzhalter für weitere Bearbeitung. Ich bin da ja noch lange nicht fertig. Klammern um if habe ich mir seit Jahren, eher Jahrzehnten, angewöhnt. Das stammt wohl noch aus der Turbo-Pascal 5 Zeit. Aber was ist an Klassen-Attributen schädlich bzw. warum sollte ich Instanz-Attribute benutzen?
Du scheinst hier Klassen als Datenstruktur selbst zu missbrauchen, das macht man so nicht.
Das habe ich als eine der Möglichkeiten gelesen, eine "struct" Datenstruktur nachzubilden, wie es sie in anderen Sprachen gibt. Unter anderem https://stackoverflow.com/questions/359 ... -in-python beschreibt auch die "dataclass" ab Python 3.7.
mangels iobroker-Kenntnissen zumindest da nicht mehr sagen.
Kein Ding, die Anbindung an iobroker ist auch gerade kein Thema. Die Hilfsklasse, die den Transfer von und zu iobroker betreibt, funktioniert.

Michael
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

Sirius3 hat geschrieben: Sonntag 13. März 2022, 15:37 Klassen erkennt man daran, dass sie mit großem Anfangsbuchstaben geschrieben werden, nicht daran, dass man ihnen ein cls_-Präfix verpasst. Oder hast Du schon irgendwo anders diese Namensgebung gelesen?
Lass mir mal bitte, gerade in einem nicht veröffentlichten oder zur Zusammenarbeit gedachten, meine sicherlich z.T. ungewöhnliche und aus anderen Sprachen abgeleitete oder einfach nur über Jahrzehnte angewohnte Schreibweise(n). Und ich weiss auch, dass ich da nicht immer konsequent bin und im Verlauf der Entwicklung auch einiges später umbenenne. Man mag mich bitte darüber benachrichtigen, wenn es auf die Funktionalität Einfluss hat...
Diese nichtssagenden Kommentare, die nur aus Minuszeichen oder Punkten bestehen, können auch gleich gelöscht werden, die behindern das Lesen, anstatt dass sie den Code strukturieren.
Nope, die behindern schon mal gleich gar nicht und helfen mir, in diesem Beispiel der # ... Kommentare zu sehen, welche Ebene gerade mit welchem Parameter zu tun hat.
Klassen verschachtelt man nicht ineinander,
Warum nicht? Was ist daran problematisch?
und auch sonst scheinen die Einrückungen nicht zu passen.
Ups, wo denn?
Aus den Codefragmenten kann man nicht viel herauslesen.
Doch, es ist alles Essentielle vorhanden. Der Rest sind überwiegend Wiederholungen der Abfragen mit anderen Strings. Ok, kommt noch die Weiterleitung der Abfrage an das Child dazu, wenn der String nicht zur lokalen Variable passt.
Einem __init__ fehlt ein `self` und Klassenattribute sind kein Ersatz für Instanzattribute.
Stimmt, das self hatte ich vergessen, ist gefixt. Aber auch an Dich die Frage: Was ist an Klassen- statt Instanz-Attributen problematisch?
Nachdem eine Instant initialisiert wurde, sollte sie vollständig nutzbar sein; dass Du nachträglich mit setinstance etwas setzen willst, ist nicht gut. ; braucht man in Python nicht.
Doch, brauch ich. Die Instanz wird per default mit 0 belegt (diesen Aufruf kann ich also ggf. weglassen) und ergibt dann als channel z.B. "octoprint.0", aber octoprint brauch ich als 0 und 1.
Wo ist das Hauptprogramm? Wo wird hier etwas angezeigt oder abgefragt? Bevor Du hier etwas im Hintergrund abfragen willst, sollte die einfache Variante mit dem sequenzellen Abfragen solide programmiert sein.
Schrieb ich doch: Funktioniert. Z.B. mit:

Code: Alles auswählen

displayAt(  4, 1, "Printer 2:            '{:s}' - {:s}".format( getIobroker( iob+"name" ), getIobroker( iob+"printer_status" )))
Michael
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Klassenattribute sind letztlich globale Variablen. Und wenn du seit Jahrzehnten programmierst, dann ist das hoffentlich eine der verinnerlichten Lektionen: globale Variablen sind schlecht.

Man verschachtelt Klassen nicht ineinander, weil das eine Beziehung suggeriert, die nicht da ist. Es gibt sprachen, die kennen das Konzept der inner classes, Java zb. Aber Python gehört nicht dazu, und nur weil der Interpreter flexibel ist, muss man nicht alles machen, was geht. Praktisch als Anwendung des Prinzips mit viel Freiheit kommt eben auch Verantwortung.

Letztlich ist es aber natürlich dein Ding. Wenn du darauf bestehst, Sachen so zu machen, wie du es willst, kann dich davon keiner abhalten. Mag sich dann aber auch keiner mit auseinander setzen.
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

__deets__ hat geschrieben: Sonntag 13. März 2022, 16:57 Klassenattribute sind letztlich globale Variablen. Und wenn du seit Jahrzehnten programmierst, dann ist das hoffentlich eine der verinnerlichten Lektionen: globale Variablen sind schlecht.
Hmmm... Punkt. Aber ich sehe diese Variablen auch als Speicher für get/set-Funktionen, also als "private". Ich weiss, Python hat diese private Geschichte eher nicht, nur mit viel Gemurksel über _ so halbwegs. Aber ich bin es (auch: "gerne") gewohnt, die Werte über definierte Funktionen setzen und lesen zu lassen, um dort ggf. noch zusätzliche Logik wie z.B. Wertebereiche o.ä. zuzulassen, also "property" Technik. Aber wäre es dann sinnvoller so:

Code: Alles auswählen

    class cls_jog():
        #x = None
        #y = None
        #z = None
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def readall( self ):
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def get( self, node ): # e.g. "x"
            [ ... ]
# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
        def __init__( self ):
            x = None
            y = None
            z = None
    # end cls_jog
Ich hatte sie nur als Deklaration im Header, weil es für mich übersichtlicher ist. Ich bin Freund von "option explicit" = Alle verwendeten Variablen sind deklariert und das auch im Header. Auch eine Sache aus Pascal Zeiten... ;-) Ich mag nicht mal die "on the fly" Deklaration von z.B. C# mit "int i = 1;" irgendwo mittendrin im Source.
Man verschachtelt Klassen nicht ineinander, weil das eine Beziehung suggeriert, die nicht da ist.
Ist sie nicht? In der Tat habe ich das genau so gedacht. Die Klasse cls_jog z.B. sollte nur von cls_octoprint aus verfügbar sein, nicht aber von cls_fritzdect. Deswegen habe ich sie innerhalb der "äusseren" Klasse platziert. Wenn Python das aber ignoriert, kann ich sie in der Tat auch auf oberster Ebene untereinander setzen.

Michael
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Als erfahrener Python(!) Programmierer schaue ich nicht auf den Klassenkörper. Sondern in __init__. Also ja, da gehören die rein. Natürlich mit self davor.

Und was du da tust, funktioniert so lange, wie deine “explizit” deklarierten Klassenattribute unveränderlich sind. Was strings und zahlen sind. Wehe du erklärst da ein veränderliches Objekt, wie eine Liste. Dann holst du dir sofort Probleme ins Haus, dann plötzlich wird das, wie globaler Zustand nun mal so ist, aus unerklärlichen Gründen modifiziert. Und produziert die entsprechenden Fehler.

Wenn du properties schon kennst, und weißt, dass man die nutzt, wenn man Logik beim setzen, oder lesen von Attributen laufen lassen will, und auch welche Konventionen es gibt in Python, dann frage ich mich, warum wir hier diskutieren. Wen du weißt, wie es “richtig” im Sinne von “so macht man es in Python” geht, und es bewusst anders machst, fein. Aber dann frag auch nicht, was die Leute da in halten. Denn natürlich wird das alles angemerkt. Du wirst keine Argumente hervorbringen, die jemandem hier diese Konventionen abgewöhnen. Das läuft also auf ein wortreiches agree to disagree hinaus.

Was die Klassen angeht: jeder kann problemlos aussen.innen zugreifen & instantiieren. Das ist also nur ein Namensraum, mehr nicht.
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

__deets__ hat geschrieben: Sonntag 13. März 2022, 18:21Sondern in __init__. Also ja, da gehören die rein. Natürlich mit self davor.
Ok, daran kann ich mich gewöhnen. Der "header" hat nur (für mich) halt den Vorteil, eben naturgemäß "ganz oben" zu sein. Eine init Routine muss ich ggf. erst mühsam suchen. Ich gewöhn mir i.a. an, die ganz unten zu haben, nach den "private" und "public" Methoden. Weiss aber nicht, ob das immer so ganz sinnvoll ist. Wahrscheinlich ist ganz oben sinnvoller. Da ich aber (auch wieder aus der Pascal Zeit) "top down" im Source habe = "oben" kann ich nichts referenzieren, was erst "weiter unten" kommt, ergibt sich für mich immer diese "private", "public", "init" Reihenfolge.
Und was du da tust, funktioniert so lange, wie deine “explizit” deklarierten Klassenattribute unveränderlich sind.

Ok, das sind sie "hier" in der Tat noch, vorgegeben von der Struktur im iobroker. Aber wenn das ein grundsätzliches Problem ist, lass ich das lieber.
Wen du weißt, wie es “richtig” im Sinne von “so macht man es in Python” geht, und es bewusst anders machst, fein.
Aber nur "wenn" und "wenn". Wegen Ersterem frage ich, Zweiteres ergibt sich aus Ersterem. ;-)
Was die Klassen angeht: jeder kann problemlos aussen.innen zugreifen & instantiieren. Das ist also nur ein Namensraum, mehr nicht.
Also geht:

Code: Alles auswählen

class a():
    class b():
class c():
    d = b()
Weird, aber dann gut zu wissen...

Michael
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, das geht nicht. Du musst schon darauf zugreifen via a. Also d = a.b()

Und __init__ ist - Trommelwirbel - per Konvention aus genau dem Grund die erste Methode, die man anlegt. Weil sie ja gleichzeitig auch darstellt, welche Argumente zur Konstruktion eines Objektes erwartet werden, und das ist ja zwangsweise das allererste, dass man machen muss, bevor man es benutzen kann. Ich sortiere danach die öffentlichen Methoden, und dann am Ende die privaten (mit _ davor), damit der Betrachter möglichst schnell einen Eindruck hat, was er benutzen kann.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Aus Gründen: viewtopic.php?t=54303
miriki66
User
Beiträge: 8
Registriert: Freitag 4. März 2022, 17:35

__deets__ hat geschrieben: Sonntag 13. März 2022, 18:40Nein, das geht nicht. Du musst schon darauf zugreifen via a. Also d = a.b()
Hm, ok, dann lass ich es aber wohl lieber so "verschachtelt", um dadurch die Zugehörigkeit deutlich zu machen. Ja, ok, ich kann ein Instanziieren nicht verhindern, weil's eben nicht "private" ist. Aber Es sieht in jedem Fall schon (dann) auf den ersten Blick komisch aus, wenn ich im fritzdect-Object ein octoprint.jog Object einbinden will.
Und __init__ ist - Trommelwirbel - per Konvention aus genau dem Grund die erste Methode, die man anlegt.
Sir, yes Sir, happy to be at your service, Sir! ;-)
Ich sortiere danach die öffentlichen Methoden, und dann am Ende die privaten (mit _ davor), damit der Betrachter möglichst schnell einen Eindruck hat, was er benutzen kann.
Ist halt eben genau anderes herum, als ich es bisher von so ziemlich allen anderen Sprachen gewohnt bin, teilweise zwangsweise, teilweise aus Konvention. Aber ok, krieg ich auch hin.

Michael
Antworten