Threading notwendig?

  • Hallo zusammen,


    Skript steckt noch in der Entwicklung, deswegen sind auch die Kommentare sehr spärlich gesät. Für mich hat sich jetzt gerade dabei folgende Fragen/Problematik ergeben:

    Muss ich hier Threading verwenden, oder gibts hier eine andere Lösung um nicht auf Threading setzen zu müssen (Threading ist noch das unbetretenen Stück Python, oder ist die Zeit gekommen wo ich da jetzt durch muss?)
    Das Skript macht steuert einen 60er LED (WS2812) Kreis an, welches eine Uhr werden soll. Alle 5 Minuten wollte ich bestimmte IP welche in einer Toml Datei hinterlegt sind anpingen, und je nach Ergebnis die LEDs dimmen (Fernseher läuft) oder komplett abschalten (Handy zu Hause).
    Jetzt wenn ich aber pinge, bleibt mir mein Sekundenzeiger stehen. Gibts eine andere Möglichkeit oder am besten Threading einsetzen? (Ok eine Lösung wüsst ich auch noch mit einem zusäztlichen Skript was dann nur die Zustände in einem seperaten toml File hinterlegt)

    Sofern Interesse am Code besteht ;)

    Python
    from neopixel import *


    Das fliegt natürlich auch noch raus, da ich aber anfangs paar Schwiergkeiten hatte das ganze überhaupt importieren zu können, ist das noch ein überbleibsl von einem Tutorial


    :danke_ATDE: schonmal wieder im Voraus

  • Du musst keinen 2. Thread verwenden, wobei Multithreading in Python IMHO echt nicht schwer ist:

    [code=php]
    import threading

    def f(x, y):
    print(x**y)

    thread = threading.Thread(target=f, args=(1234, 5678))
    thread.start()

    # Weiter gehts

    # Am Ende auf den Thread warten, falls noch nicht beendet
    thread.join()
    [/php]

    Nur so als Beispiel...

    Aber das: https://stackoverflow.com/a/16071877/5952681 habe ich bei Google mit "Python subprocess non blocking" gefunden. Schau dir das mal an.

    Ansonsten: ist konstruktive Kritik zum restlichen Code (schon) erwünscht? Ich hätte d nämlich noch ein paar Dinge... :D

    LG

  • Hallo,

    Zitat

    Gibts eine andere Möglichkeit oder am besten Threading einsetzen?


    ja, gibt es: asyncio. Ist seit Python 3.5 vollständig in Python und damit unter Raspbian Stretch verfügbar. Du müsstest das dann IMHO alles in Coroutinen packen, die vom Eventloop gesteuert werden.

    Ob das für dich (oder auch allgemein) "einfacher" ist - kommt drauf an. Diesen asynchronen / event-getriebenen Ansatz zu verstehen ist manchmal nicht ganz einfach, weil man von linearen Programmablauf weg ist. Ich strauchel da auch noch ab und an.

    Grundsätzlich: da die Funktion, die pingt, aber "blocking" ist, und die Antwortzeiten (stark) variieren können, musst du das IMHO auf jeden Fall nebenläufig programmieren.

    Gruß, noisefloor

  • Zitat von "linusg" pid='300339' dateline='1505762518'

    Ansonsten: ist konstruktive Kritik zum restlichen Code (schon) erwünscht? Ich hätte d nämlich noch ein paar Dinge... :D


    Klar doch, nur her damit :D


    Zitat von "noisefloor" pid='300340' dateline='1505762557'


    Hallo,


    ja, gibt es: asyncio. Ist seit Python 3.5 vollständig in Python und damit unter Raspbian Stretch verfügbar. Du müsstest das dann IMHO alles in Coroutinen packen, die vom Eventloop gesteuert werden.

    Ob das für dich (oder auch allgemein) "einfacher" ist - kommt drauf an. Diesen asynchronen / event-getriebenen Ansatz zu verstehen ist manchmal nicht ganz einfach, weil man von linearen Programmablauf weg ist. Ich strauchel da auch noch ab und an.

    Grundsätzlich: da die Funktion, die pingt, aber "blocking" ist, und die Antwortzeiten (stark) variieren können, musst du das IMHO auf jeden Fall nebenläufig programmieren.

    Gruß, noisefloor

    asyncio, das ist mir noch völlig fremd. Den Begriff jetzt erstmals von dir gehört. Werd ich mir auch mal anschauen, aber wenn denn du da auch noch staucheltst, werd ich dann doch mal das Eis mit dem Threading brechen müssen, ist ja gar nicht so schwer sagt linusg :^^: :blush:

  • 1) #!/bin/usr/python3 gibt es bei mir nicht, ergo ist der Shebang nutzlos.
    2) range(0, strip.numPixels()) = range(strip.numPixels())
    3) if zeit.hour > 11:
    index = int(zeit.hour) - 12
    else:
    index = int(zeit.hour)
    Wenn du eh schon mit int vergleichst, musst du doch nicht mehr zu ints umwandeln...
    4) if bool(set(hour) & {minute}) = if set(hour) & {minute}
    Gut, dass du mit set arbeitest, bei größeren Datenmengen ist das gut für die Performance!
    5) return (zeit.second, led_gesetzt) = return zeit.second, led_gesetzt -> Beides ein Tuple!!!
    6) not type(led_gesetzt) == list, besser not isinstance(led_gesetzt, list)
    7) status = dict() -> status = {} ist schneller!
    8) pfad = os.path.abspath(os.path.dirname(__file__))
    configfile = pfad + "/wlan.toml"
    -> IMMER os.path.join! configfile = os.path.join(pfad, "wlan.toml")


    So für's erste. :)

  • Der ping Befehl liefert als Returncode/Exitcode bei "pingable" eine 0 zurück, wenn das Ziel nicht pingbar ist eine 1.

    Das erste Beispiel in dem von linugs genannten Links blockiert aber auch solange bis der "ping" fertig ist. p.poll() ist solange None wie der Befehl beschäftigt ist, erst wenn der Befehl beendet wurde liefert p.poll() den Exitcode.

    Evtl. könnte es auch schon ausreichen dem "ping" einen Timeout zu geben:
    [code=php]
    subprocess.call(["ping", "-c2", "-W1", ip])
    [/php]


    //EDIT: "zeit" ist ein datetime-Objekt. Es reicht völlig "zeit.hour - 12" zu machen dann ist die Ausgabe bereits vom Type "int", auch int(zeit.hour) ist überflüssig.

  • Hallo,

    im folgenden ein Beispiel für "non blocking ping" mit asyncio. Ob das "state of the art" ist weiß ich nicht - aber es funktioniert :)

    [code=php]import asyncio
    from datetime import datetime
    from random import randint

    @asyncio.coroutine
    def heartbeat_1s():
    while True:
    yield from asyncio.sleep(1)
    print('Heartbeat at: {}'.format(datetime.now()))

    @asyncio.coroutine
    def pinger():
    URLS = ['google.de', 'google.foobar']
    result_dict = {}
    for url in URLS:
    ping_call = asyncio.create_subprocess_exec('ping', '-c', '2', url,
    stdout=asyncio.subprocess.PIPE,
    stderr=asyncio.subprocess.PIPE)
    result = yield from ping_call
    yield from result.wait()
    if result.returncode != 0:
    result_dict[url]=False
    else:
    result_dict[url]=True
    return result_dict

    @asyncio.coroutine
    def consumer():
    while True:
    result = yield from pinger()
    print('Got result: {}'.format(result))
    yield from asyncio.sleep(randint(3, 6))

    loop = asyncio.get_event_loop()
    loop.create_task(heartbeat_1s())
    loop.create_task(consumer())

    try:
    loop.run_forever()
    except KeyboardInterrupt:
    loop.close()
    print('done')[/php]

    Gruß, noisefloor

    Einmal editiert, zuletzt von noisefloor (18. September 2017 um 22:15)

  • Hallo,

    auf in den Programmier Feierabend :)

    meigrafd, ein Timeout führt leider nicht zur Lösung

    noisefloor, danke für den Beispielcode, aber asyncio werd ich hinter threading erlernen :angel:

    Die ersten Gehversuche mit threading habe ich gerade unternommen:

    Ist aber noch nicht in die Uhr integriert.

    Zitat von "linusg" pid='300343' dateline='1505764056'


    1) #!/bin/usr/python3 gibt es bei mir nicht, ergo ist der Shebang nutzlos.


    uii, soll /usr/bin/python3 heißen

    Zitat von "linusg" pid='300343' dateline='1505764056'


    2) range(0, strip.numPixels()) = range(strip.numPixels())


    erledigt

    Zitat von "linusg" pid='300343' dateline='1505764056'


    3) if zeit.hour > 11:
    index = int(zeit.hour) - 12
    else:
    index = int(zeit.hour)
    Wenn du eh schon mit int vergleichst, musst du doch nicht mehr zu ints umwandeln...


    erledigt

    Zitat von "linusg" pid='300343' dateline='1505764056'


    4) if bool(set(hour) & {minute}) = if set(hour) & {minute}
    Gut, dass du mit set arbeitest, bei größeren Datenmengen ist das gut für die Performance!


    :)

    Zitat von "linusg" pid='300343' dateline='1505764056'


    5) return (zeit.second, led_gesetzt) = return zeit.second, led_gesetzt -> Beides ein Tuple!!!


    wieder was gelernt, hab das bisher immer in ( ) gesetzt.

    Zitat von "linusg" pid='300343' dateline='1505764056'


    6) not type(led_gesetzt) == list, besser not isinstance(led_gesetzt, list)


    Danke, ich weiß schon warum ich immer wieder Code herzeige :)

    Zitat von "linusg" pid='300343' dateline='1505764056'


    7) status = dict() -> status = {} ist schneller!


    Kannte beides, dass sich da was von der Zeit nimmt war mir nicht bewusst. Hab mir das eigentlich so angewohnt weil ichs persönlich so schöner zum lesen fand.
    Aber wie im Vergleich, muss ich dir das wohl glauben ;)

    Zitat von "linusg" pid='300343' dateline='1505764056'


    8) pfad = os.path.abspath(os.path.dirname(__file__))
    configfile = pfad + "/wlan.toml"
    -> IMMER os.path.join! configfile = os.path.join(pfad, "wlan.toml")


    Das hat jetzt wohl mit der Plattformunabhängigen Programmierung zu tun wenn ich das richtig verstehe?! (na das darf ich in vielen Skripten dann ausbessern, so gut wie in allen die ich habe xD )

    Zurück zum Threading, wie man oben sieht hab ich vor auch den Thread in einer Endlosschleife laufen zu lassen, oder dort drüber die Helligkeit zu regeln. Grund:

    Code
    thread.start()
    
    
    # Weiter gehts
    
    
    # Am Ende auf den Thread warten, falls noch nicht beendet
    thread.join()

    Hierbei, wie du schon geschrieben hast "auf Thread warten", dann würd mir der Sekundenzeiger ja wieder stehen bleiben wenn er am "warten" ist. Deswegen kam ich auf die Idee, Thread nur 1x starten, und nicht mehr warten sondern endlos im Hintergrund arbeiten lassen.

  • 1-6 :thumbs1:
    7 Gibt sich nicht so arg viel, aber man kann ja auch auf die kleinen Dinge achten
    8 Mehr oder weniger ja, aber auch bekommt man eigentlich immer einen korrekten Pfad, auch wenn man jetzt nicht weiß, ob da noch ein Slash am Ende von String 1 ist oder nicht.
    Nur beim 2. String (es gehen beliebig viele als Argumente von os.path.join) muss man etwas aufpassen:

    Python
    $ python3
    Python 3.5.2 (default, Aug 18 2017, 17:48:00)
    [GCC 5.4.0 20160609] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> '/pfad/zu' + '/datei.txt'
    '/pfad/zu/datei.txt'
    >>> import os
    >>> os.path.join('/pfad/zu', '/datei.txt')
    '/datei.txt'
    >>>

    Bzgl. Threading, da hast du mich oder ich dich falsch verstanden. Klar wird der 1x gestartet uns läuft dann im Hintergrund weiter! Das auf den Thread warten war anders gemeint, bei meinem Beispiel hatte ich keine Endlosschleife hineingedacht, sondern nur ein paar Zeilen Code. wenn das Script dann am Ende ankommt, wird der Thread einfach getötet, es sei denn sein daemon Attribut ist True. Mit Thread.join kann man dann noch so lange warten, bis auch der 2. Thread fertig ist. Keine Ahnung, ob man das so verstehen kann :shy:

    Dein Beispiel mit zwei Threads sieht doch ganz gut aus!

    LG

  • Jetzt scheitere ich doch gerade an was, und zwar ist es möglich dem Thread das Objekt "strip" zu übergeben, damit dies gleich in der Funktion verfügbar ist?

    Zitat


    *args* is the argument tuple for the target invocation. Defaults to ().
    *kwargs* is a dictionary of keyword arguments for the target invocation. Defaults to {}.


    Auf diesem Weg nimmt er es leider nicht an

  • Code
    Exception in thread Thread-5:
    Traceback (most recent call last):
     File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
       self.run()
     File "/usr/lib/python3.5/threading.py", line 862, in run
       self._target(*self._args, **self._kwargs)
    TypeError: check_wlan() argument after * must be an iterable, not datetime.datetime

    Datetime wär mir eh egal, soll eigentlich nur Später mein "strip" aus dem oberen Code simulieren:

    Code
    strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT)
  • Einen Timeout solltest du aber trotzdem einbauen, sonst kann es passieren das ein ping niemals endet. Auch sollte für jedes Parameter mit Leerzeichen ein eigener Eintrag in der List für subprocess erstellt werden, " -c 2 " ist also eigentlich falsch und müsste "-c", "2" lauten. Sichergehen kannst du indem du shlex.split() zur Erzeugung verwendest - obgleich es nicht nötig ist ein Leerzeichen zwischen zB "-c" und "2" zu setzen ;) (siehe Beitrag#6)

    Problematisch könnte auch sein dass deine while ungebremst rotiert und somit die CPU stark auslastet - da solltest du zumindest "time.sleep(0.001)" einfügen um das zu verhindern.

    Und zu guter letzt würde ich so gut es geht auf Konsolen Programme/Befehle verzichten, sprich, subprocess rauswerfen und das ganze via "socket" realisieren => https://github.com/emamirazavi/python3-ping

  • Auch wenn es sich nicht mehr wirklich um Threading handelt, so macht mir ein Codeblock immer noch Schwierigkeiten. Die Software hat jetzt bisschen warten müssen da die Hardware gebaut wurde. Stelle das ganze die Tage mal im Projektforum vor.

    Hier mein Problem:

    Warum ist an dieser Stelle, das Keyword "dimmen" noch nicht bekannt?

    Da wlanliste aus einer Datei generiert wird, hier wie die Variable aussieht:

    Python
    wlanliste = {'anwesend': {'bt': ['xxx', 'yyy'], 'wlan': ['xxx']},
                'dimmen': {'bt': [], 'wlan': ['xxx']}}

    meigrafd Timeout ist eingebaut ;) aber auf das Pingmodul werd ich erstmal verzichten :saint:

  • Weil die Abfrage früher erfolgt als die check_anwesenheit() Funktion davon überhaupt Wind bekommt?

    Die Fehlermeldung zeigt if status[key_status] == True: und dort möchte er vom status Dictionary den Schlüssel dimmen abrufen, was es aber nicht gibt...

    Prüf mal die Zeilen die vorher verarbeitet werden, da ist AFAIK break Schuld an deinem Problem ;)

    Ohne break null problemo:

    ..so wie ich das sehe steckt da aber noch ein kleines Problem verborgen, mal gucken ob du dahinter kommst ;)

  • Also ein break benötige ich schon (Zeile 157) sonst gewinnt meist das False

    Aber jetzt funktionierts :) danke

    Welches Problem hast du denn noch entdeckt meigrafd ?

  • Wenn es um gewinnen oder verlieren geht läuft was schief

    Schreib doch einfach direkt den Wert von status_return in status[key_status] dann kannst dir dieses doppelte Gewusel schenken ;)

    Und wenn in deiner wlanliste mehrere Geräte für "anwesend" oder "dimmen" hinterlegt sind werden die aktuell nicht beachtet, nur jeweils das letzte. Siehe Beitrag#16 => es gibt ein bt und ein wlan Eintrag für 'anwesend' aber status enthält nur noch ein False - wo ist das zweite? 8|

  • Code
    if not status_return:
      status[key_status] = True
      break
    else:
      status[key_status] = False

    Das habe ich deshalb drin, weil hierbei ja die Logik gedreht wird. 0 wird zurückgegeben wenn ein Gerät erreichbar ist. Was ich dann als Status True hinterlege (Für mich im Kopf logischer nachzuvollziehen).

    Im status unterscheide ich nicht ob es sich um bt oder wlan handelt. Hierbei geht es nur darum ob ein Gerät aus der Liste erreichbar ist.

    bt wurde eh nur wegen den Iphone eingebaut, denn über wlan lassen sich diese nicht anpingen wenn die Geräte gesperrt sind. Mittels bt schon. Deshalb im status keine Unterscheidung zwischen bt und wlan.

    Aktuell funktioniert s wie gedacht:

    Ist kein Gerät von dimmen oder anwesend erreichbar -> LED Licht aus

    Ist ein Handy erreichbar -> Licht auf 100%

    Sobald der Fernseher an ist -> Licht dimmen auf 50%

    Fernseher an aber kein Handy erreichbar -> Trotzdem dimmen auf 50%

    Vielleicht taucht aber auch noch der ein oder andere Fehler in Laufe des Betriebes auf :saint:

    Jetzt muss ich noch die Funktionen für die Taster programmieren, dann neigt sich das Projekt langsam den Ende zu -> V1.0

    V1.1 wird dann mal noch verschiedene Modien zu programmieren, für verschiedene Darstellungen der Stunden, Minuten und Sekunden Anzeige.

  • Code
    if not status_return:
              status[key_status] = True
              break
            else:
              status[key_status] = False

    Das habe ich deshalb drin, weil hierbei ja die Logik gedreht wird. 0 wird zurückgegeben wenn ein Gerät erreichbar ist.

    Sofern ich das richtig verstehe, schreibst du oben etwas von "gewinnen". Das kann aber doch eigentlich nicht sein, das Ergebnis ist eigentlich eindeutig, kein Zufall :fies:

    Wenn das Ziel nicht pingable/erreichbar ist liefert ping eine 1 zurück...

    Das schöne ist, man kann solche Werte umdrehen ;) Am besten direkt in deinen ping Funktionen, die ich mit Verlaub lieber ausserhalb check_anwesenheit() definieren würde

    Python
    def ping_wlan(ip):
        return not subprocess.call(shlex.split("ping -c2 -W1 {}".format(ip)))
        
    def ping_bt(bt):
        return not subprocess.call(shlex.split("sudo l2ping -c2 -t1 {}".format(bt)))

    Und in Folge dessen könntest du status_return weg lassen:

    Hm Ok. Dann hab ich dich wohl falsch verstanden :blush: Ich dachte dass in "anwesend" mehrere Geräte eingetragen wären, nicht nur eins :X

Jetzt mitmachen!

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