Meine Python Bibliothek für das 7" Touch LCD auf GitHub und PyPI

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Update: Tutorial gibt's hier: [Veraltet] Steuerung des offiziellen 7" Touch LCD mit rpi-backlight.

    Hallo,
    ich hoffe, das ist der richtige Ort, um so etwas zu posten, wenn nicht, lasst es mich wissen.

    Ich habe mir vor ein paar Wochen das offizielle 7" Touch LDC für den Raspi gekauft und fand es besonders Abends recht nervig, dass es so hell eingestellt war. Also etwas gegoogelt, und eine Lösung gefunden, das wurde ja bereits vielfach diskutiert.

    Allerdings hatte sich keiner die Mühe gemacht, ein paar Zeilen stabilen Code mit Fehlerbehandlung usw. zu veröffentlichen, daher habe ich das ganze nochmal sauber (Nachtrag: sauber ist anders, aber man lernt nie aus ;) ) implementiert und in eine kleine Python Bibliothek gepackt, die ich unter der MIT Lizenz auf GitHub und PyPI veröffentlicht habe:


    Features:

    • Displayhelligkeit weich oder abrupt einstellen
    • Display an und ausschalten
    • Aktuelle Helligkeit zurückgeben
    • Maximale Helligkeit zurückgeben
      ... und alles mit Python :thumbs1:


    Ich hoffe, irgendwer findet das nützlich und kann etwas Feedback geben :danke_ATDE:

    Gerne nehme ich konstruktive/destruktive Kritik, Meinungen aller Art, Lob, Verbesserungsvorschläge und Ankündigungen zur Mithilfe entgegen :)

  • Meine Python Bibliothek für das 7" Touch LCD auf GitHub und PyPI? Schau mal ob du hier fündig wirst!

  • Ein paar Anmerkungen zum Code:

    - Konstanten (wie "path") werden in Python gross geschrieben. Auch wenn sie das genauso viel oder wenig konstant macht, erkennt man dadurch ihre Bestimmung.
    - das global-Schluesselwort brauchst du nicht, um auf path zuzugreifen. Python schlaegt Namen automatisch im umgebenden Scope nach - sonst wuerde zB auch der Aufruf einer anderen Funktion ein "global Funktionsname" erfordern :)
    - Funktionen hast du ja schon, das ist gut - kannst du aber noch weiter treiben: statt jedes mal " with open(os.path.join(path, "actual_brightness"), "r") as f:... " und so weiter zu schreiben, einfach eine Funktion "get_value":

    Code
    def get_value(name):
        with open(os.path.join(path, value), "r") as f:
            return f.read()

    definieren, und alle anderen Funktionen benutzen die - mit ggf. Typwandlung nach int, wenn notwendig.

    - gleiches gilt natuerlich auch fuer das setzen von Werten.


    Sternchenaufgabe waere die etwas holprige set_brightness etwas mehr zu generalisieren. Momentan kannst du nur mit einer konstanten Rate aendern. Schoener waere ja, einen Zeitraum fuer das Dimmen anzugeben.

    Dazu musst du nur die zu ueberbrueckende Differenz von n Schritten aufteilen auf m viele Zeitscheibchen, die sich aus deiner Wartezeit und der Dimmzeit ergeben. Das kann man als einfache Geradengleichung betrachten, und dann entweder mit floats & Konvertierung nach int loesen, oder mit Bresenham.

    Aber trotz der Kritik - schon ein sehr gelungener Anfang.

  • @__deets__

    Vielen dank für den ersten Input!

    Ich bin schon seit ein paar Jahren mit Python unterwegs, allerdings habe ich seit einiger zeit nix anderes als Klassen gemacht - da fiel mir der einstieg in das programmieren nur mit funktionen schon ein bisschen schwer :D

    Ich werde deine Anregungen aber auf jeden Fall gleich mal einfließen lassen!
    Automatisch zusammengefügt:

    Danke nochmal, ich habe die Änderungen gemacht und auf GitHub gestellt. Bei PyPI landen sie dann demnächst als neues release.

    Die Sache mit der Dimmzeit hatte ich mir auch schon mal überlegt, habe dann aber doch den quick&dirty Weg gewählt ;) - kommt aber auf die TODO liste für das nächste release.

  • Was mir da auch noch auffällt:

    Du beschreibst das dein Script mit sowohl python2 als auch python3 genutzt werden kann, deine print's sind aber von python3 - eine Funktionsaufruf, kein Statement wie in python2.
    Abhilfe => als aller erstes folgende Zeile:

    Python
    from __future__ import print_function

    das funktioniert in sowohl python3 als auch python2.

    Beschleunigt wird das Script indem man nur das imporiert was man benötigt, also zum Beispiel nicht nur "import sys" sondern "import sys.exit"....

    Wofür "os.path.join" ? Du gibst doch sowieso den absoluten/vollständigen Pfad an :s

    Und etwas bessere Variable Namensgebungen wären nicht schlecht, sowas wie "v" sind nicht allzu viel sagend ;)


    PS: Bitte nicht Beiträge vollständig quoten/zitieren, vor allem wenn diese genau da drüber stehen. Danke.

  • meigrafd

    Auch an dich erstmal ein großes Dankeschön!

    Zitat


    Du beschreibst das dein Script mit sowohl python2 als auch python3 genutzt werden kann, deine print's sind aber von python3 - eine Funktionsaufruf, kein Statement wie in python2.
    Abhilfe

    Was die print Funktionen betrifft - ich hoffe niemand benutzt mehr Python 2.5 auf dem Raspi, und seit 2.6 gibt es print als Funktion und Statement parallel. Ich werd's aber trotzdem mal reinnehmen.


    Zitat

    Beschleunigt wird das Script indem man nur das imporiert was man benötigt, also zum Beispiel nicht nur "import sys" sondern "import sys.exit"....

    Die Annahme, ist schlich falsch, siehe http://stackoverflow.com/a/3592137/5952681 und http://programmers.stackexchange.com/a/187471/225530 (diese Leute wissen was sie sagen ;) ). Außerdem hat man dann den Nachteil, dass der lokale Namensraum (local namespace) irgendwie "zugemüllt wird".

    Zitat

    Wofür "os.path.join" ? Du gibst doch sowieso den absoluten/vollständigen Pfad an

    Weil das in meinen Augen der sauberste Weg ist, wobei man in diesem Fall tatsächlich

    Code
    FILE + "bl_power"

    machen könnte.

    Zitat


    Und etwas bessere Variable Namensgebungen wären nicht schlecht, sowas wie "v" sind nicht allzu viel sagend

    Da geb ich dir recht, das wird geändert :D

  • Hallo,

    noch zwei kleine Verbesserungsvorschläge:
    * im Quelltext auch die Lizenz erwähnen
    * in der Doku erwähnen, dass `smooth=True` bei `set_brightness()` der Default-Wert ist und dann nicht angegeben werden muss. Im Moment sieht es so aus, als müsste `smooth=...` angegeben werden.

    Persönlich finde ich ja auch noch ausführliche Doc-Strings gut, die noch mehr Erklärung und Beispiele liefern, so dass man bei `help(name_der_funktion)` weiß, worum es geht und wie es funktioniert. Im Moment sind bei dir die Doc-Strings ja eher kurz.

    Gruß, noisefloor

  • noisefloor - Danke auch an dich!

    Zitat

    Im Quelltext auch die Lizenz erwähnen

    Ist natürlich immer gut, wird gemacht :D

    Zitat

    in der Doku erwähnen, dass `smooth=True` bei `set_brightness()` der Default-Wert ist und dann nicht angegeben werden muss. Im Moment sieht es so aus, als müsste `smooth=...` angegeben werden.

    Ja, ich habe die Beispiele jetzt ein wenig klarer gemacht.

    Zitat

    Persönlich finde ich ja auch noch ausführliche Doc-Strings gut, die noch mehr Erklärung und Beispiele liefern, so dass man bei `help(name_der_funktion)` weiß, worum es geht und wie es funktioniert. Im Moment sind bei dir die Doc-Strings ja eher kurz.

    Auch das ist natürlich eine gute Idee, da werde ich mich mal dransetzen :danke_ATDE: . Allerdings weiß ich nicht so genau, wie man außer mit Beispielen

    Zitat

    """Return the maximum display brightness.

    """

    noch klarer machen kann...

  • Man sollte schon immer os.path.join benutzen, weil es nie schadet sich gute Angewohnheiten zuzulegen bei denen man bleibt, auch wenn es mal streng genommen nicht notwendig waere. Ausserdem macht es den Code robuster, da os.path.join zB ueberfluessige Slashes wegnormalisiert - os.path.join("/foo/", "bar") == os.path.join("/foo", "bar")

    Und Microoptimierungen im sub-uS-Bereich ueber Name-Lookups sind ein Mittel welches bestenfalls in den innersten der inneren heiss laufenden Loops einen merklichen Vorteil bringen. So etwas tut man nach Profiler laeufen. Unter Umstaenden. Bisher in meinen ~18 Jahren Python vielleicht zwei mal.... So etwas zu optimieren fuer den *einmaligen* Fall eines Programmabbruchs ist nutzlos.

    Zu deinen Aenderungen: sieht gut aus, ich wuerde aber noch die Verallgemeinerung von _set_value weiter treiben. So wie es aussieht kannst du von aussen immer mit Integern arbeiten. Damit solltest du die str-Wandlung *in* die Funktion ziehen, und von aussen konsistent ints verwenden. Damit reduziert sich die kognitive Last bei der Verwendung - man muss weniger wissen.

    Ein Konstrukt wie

    Code
    if on:
                _set_value("bl_power", "0")
            else:
                _set_value("bl_power", "1")

    wuerde ich dann auch auch als

    Code
    _set_value("bl_power", int(on))

    schreiben.

    Dann koennte die Logik in der __main__-Loop noch vereinfacht werden - statt success zu verwenden, einfach ein break wenn du *keinen* ValueError bekommst. Und das set_brightness dahinter. Denn dieser Code hat nix im Loop zu suchen, der kommt *nach* der Bestimmung einer sinnvollen Eingabe zum tragen, und sollte darum auch nicht innerhalb des while stehen.

    Und last but not least solltest du dir mal die Funktionalitaet von 'entry_points' in setuptools anschauen. Ich verwende die zB hier:

    https://github.com/deets/brombeer…opencv/setup.py

    Damit bietest du gleich ein fertiges Skript an, ohne mit Python/shebang oder der Plazierung im Filesystem den User belaestigen zu muessen. Landet dann einfach in /usr/local/bin, und sollte im Pfad sein.

    Alles in allem - weiter so!

  • @__deets__

    Ich werde _get und _set_value also noch weiter optimieren, danke für die Tipps. Dir ist da allerdings ein kleiner Fehler unterlaufen:

    Code
    _set_value("bl_power", int(on))

    schaltet das Display AUS, wenn on True ist! Keine Ahnung, wer das so gewollt hat, aber eine 0 in bl_power heißt an und eine 1 aus.

    Und so ein entrypoint ist ja mal eine coole Sache, das werde ich mir auf jeden Fall nochmal anschauen!


  • Was die print Funktionen betrifft - ich hoffe niemand benutzt mehr Python 2.5 auf dem Raspi, und seit 2.6 gibt es print als Funktion und Statement parallel. Ich werd's aber trotzdem mal reinnehmen.

    Das wurde mir hier von einem Python Guru anders eingetrichtert, wo ich mich ebenfalls lange vor gesträubt hatte :blush:

    Wenn man print("bla", "blub") in Python < 3 verwendet wird eine Tuple erzeugt und ausgegeben - es ist deshalb also längst keine Funktion nur weil das auf den ersten Blick zu funktionieren scheint:

    Python
    Python 2.7.9 (default, Sep 17 2016, 20:26:04) 
    [GCC 4.9.2] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print("bla", "blub")
    ('bla', 'blub')
    >>> 
    >>> from __future__ import print_function
    >>> print("bla", "blub")
    bla blub
    >>>

    In Python > 3 also zum Beispiel 3.5 ist der Aufruf von

    Code
    print "bla"

    nicht möglich, da wird dir eine Fehlermeldung ausgeworfen:

    Code
    Python 3.5.2+ (default, Aug 30 2016, 19:08:42) 
    [GCC 6.1.1 20160802] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print "bla"
      File "<stdin>", line 1
        print "bla"
                  ^
    SyntaxError: Missing parentheses in call to 'print'
    >>>

    Da dein Shebang besagt dass das Script mit "python" ausgeführt werden soll, bin ich davon ausgegangen das es python2 ist - was auch die eine Zeile mit print aussagt. In deiner Beschreibung steht aber das es egal wäre ob Python 2 oder 3.


    Besagter Python Guru sagte mir dazu: In Python2 ist print ne Anweisung, in Python3 ne Funktion. Wer den Unterschied zwischen Anweisung und Funktion nicht kennt bzw. nicht verstehen will, der sollte besser die Sprache wechseln.
    https://www.python.org/dev/peps/pep-0214/
    https://www.python.org/dev/peps/pep-3105/


    Die Annahme, ist schlich falsch, siehe http://stackoverflow.com/a/3592137/5952681 und http://programmers.stackexchange.com/a/187471/225530 (diese Leute wissen was sie sagen ;) ). Außerdem hat man dann den Nachteil, dass der lokale Namensraum (local namespace) irgendwie "zugemüllt wird".

    Dann irren sich die Herren dort leider auch mal ...
    Importiere ich größere Module vollständig dauert das starten des Scripts länger als wenn ich nur die Methoden/Funktionen importieren die mein Script wirklich nutzt.
    Auch wenn Module vorher noch nie genutzt wurde dauert das starten länger, weil dann erst der Bytecode erzeugt wird.

    Warum der "local namespace" zugemüllt wird wenn man nur die Methoden/Funktionen importiert die man auch nutzt, versteh ich auch nicht - ist das nicht eher andersherum?

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!