GPIO-STATUS in MySQL-Datenbank schreiben

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo zusammen,

    habe diese kleine Python-Script, mit dem der Zustand eines GPIO´s erfasst werden soll.

    In Zukunft soll das Programm über einen crontab zyklisch gestartet werden. Der STATUS soll als 0(off) oder 1(on) in eine MySQL-Datenbank geschrieben werden. Die Datenbank-Struktur ist über das Projekt VOLKSZÄHLER vorgegeben:

    Leider habe ich trotz Suche und probieren nichts gefunden, was für mich erklärlich anzupassen b.z.w. umzusetzen ist. Das einzige was funktioniert ist das kleine Script und der Crontab.

    Würde mich über Hilfestellungen sehr freuen - mfG - towi

  • Hi meigrafd,

    leider weiß ich jetzt nicht, wie ich mit dem Script den STATUS des GPIO in die Datanbank bekommen. Hatte es mal mit einem Teile aus 1wire.py Deines Highcharts-Projektes versucht. Hier mal der Auszug aus Deinem Original:

    Dort hattest Du ja die Temperaturwerte von mehreren DS18.. in eine Datenbank geschrieben. Könnte man das so prinzipiell verwenden?
    Irgendwie hat das schon mit der def... nicht geklappt. Habe da einfach keine ausreichende Kennung :daumendreh2:

    Trotzdem schon mal Danke - towi

  • Hallo,

    Zitat

    Könnte man das so prinzipiell verwenden?


    Prinzipiell schon.

    Dein Problem ist ja scheinbar eher, dass du (noch) keine Ahnung von SQL und der Datenbank-Anbindung unter Python hast. Was dann wohl das größere "Problem" ist - zumindest, wenn man C&P "ohne Sinn und Verstand" vermeiden will.

    Tipp: Such' dir mal ein Tutorial, wie man Python und dessen DB API 2.0 mit relationalen Datenbanken verwendet. Das vorgehen ist immer gleich, du musst nur nach DB die Verbindungsparameter und die Feldtypen anpassen.

    Außerdem wäre es hilfreich, wenn du _deinen_ Code postest - auch, wenn der nicht funktioniert. Dann hat man wenigsten einen Ausgangspunkt. Ein "hat nicht geklappt" oder "funktioniert nicht" nützt nicht wirklich was...

    Noch eine Anmerkung zum Code: `input`ist kein guter Name für eine Variable, weil du damit die Build-in Funktion `input`überschreibst. Was "unerwartete" Effekte haben wird, wenn du die `input` Funktion in deinem Code benutzen willst.

    Gruß, noisefloor

  • Hallo meigrafd,

    Zitat

    Was genau möchtest du denn erreichen?

    Möchtest du möglichst in Echtzeit den jeweiligen Digitalen Status in der Datenbank haben - und was nutzt dir das dann?

    Ja - genau das ist beabsichtigt! In der DB sollen in value nur 0-en oder 1-en(je nach Status des GPIO) geschrieben werden. Das frondend des Volkszählers stellt dann auf der Zeitachse den Staus dar. Damit soll schlussendlich sichtbar gemacht werden, wann ein Verbrauche AN oder AUS war. Wenn ich die Daten von Hand in die Datenbank eingebe, funktioniert das schonmal.

    Wollte nun per Python-Script den Status erfassen und dann mit Zeitstempel in die DB übertragen.

    Habe die vergangenen Tage dazu gelesen und gelesen und... Habe jetzt mal diesen Scripot geschrieben(als Basis) :

    Leider reagiert´s so:

    Code
    pi@raspberrypi:~ $ sudo /test01.py
     File "/test01.py", line 26
       time.sleep(30.0)
          ^
    SyntaxError: invalid syntax

    Würde mich über Unterstützung freuen - mfG towi

  • Dann wäre denk ich das sinnvollste Interrupt zu verwenden sodass sofort ein Flankenwechsel erkannt wird. Was du da machst ist sog. Polling und nicht derart effektiv zumal aufwändiger.

    Beispiel:

    [code=php]
    #!/usr/bin/python

    from __future__ import print_function
    import RPi.GPIO as GPIO
    import Queue # https://pymotw.com/2/Queue/
    import MySQLdb
    import time
    import sys

    mysqlHost = '127.0.0.1'
    mysqlPort = 3306
    mysqlLogin = 'root'
    mysqlPass = 'raspberry'
    mysqlDatabase = "volkszaehler"

    gpio_list = [2, 3, 4, 17, 27, 22, 10, 9, 11, 18, 23, 24, 25, 8, 7]

    GPIO.setmode(GPIO.BCM)


    # Pro Status (GPIO HIGH oder GPIO LOW) ein Queue erstellen
    queue_high = Queue.Queue()
    queue_low = Queue.Queue()

    # Permanente MySQL Verbindung
    try:
    db = MySQLdb.connect(host=mysqlHost, port=mysqlPort, user=mysqlLogin, passwd=mysqlPass)
    cursor = db.cursor()
    except:
    print("Fehler beim verbinden zum MySQL Server")
    sys.exit()

    # ISR
    def interrupt_Event(channel):
    if GPIO.input(channel) == GPIO.HIGH:
    queue_high.put(channel)
    else:
    queue_low.put(channel)

    try:
    # Interrupt Event fuer jeden gpio hinzufuegen. Auf steigende und fallende Flanke reagieren und ISR deklarieren sowie Pin entprellen
    for gpio in gpio_list:
    GPIO.setup(gpio, GPIO.IN)
    GPIO.add_event_detect(gpio, GPIO.BOTH, callback=interrupt_Event, bouncetime=200)

    # Queues abarbeiten
    while True:
    time.sleep(0.1)
    if not queue_high.empty():
    pin = queue_high.get()
    print("GPIO HIGH: %s" % pin)
    cursor.execute("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);", (pin, time.time(), '1'))
    cursor.commit()
    if not queue_low.empty():
    pin = queue_low.get()
    print("GPIO LOW: %s" % pin)
    cursor.execute("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);", (pin, time.time(), '0'))
    cursor.commit()

    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")

    [/php]ungetestet!

    Sobald ein Flankenwechsel stattfindet wird das sofort ins jeweilige Queue eingefügt. Die while am Ende prüft dann obs im jeweiligen Queue einen Eintrag gibt und führt ein entsprechendes Insert aus.
    Das einfügen in die Datenbank auch in der ISR Funktion abzuhandeln ist keine gute Idee da diese dann derweil blockiert wird. time.sleep in der while sollte man haben damit nicht 100% CPU Last verursacht wird, wobei 0.1 völlig reicht.

  • Hallo meigrafd,

    vielen Dank erstmal für Deine Script-Beispiel :danke_ATDE:

    Wenn ich das Beispiel starte läuft es auch einige Zeit , bis dann nach etwa 2min dieses kommt:

    Code
    pi@raspberrypi:~ $ sudo /test01.py
    
    
    Fehler beim verbinden zum MySQL Server

    Daten sind auch noch nicht in der DB angekommen.

    Bin mir nicht 100%-ig sicher ob die Zugangsdaten stimmen. Wenn ich sie allerdings mal testweise ändere, kommt die Meldung sofort.

    Gibt es eine Möglichkeit, den Verbindungsstatus b.z.w. den Verbindungsverlauf zu diesem Script zu checken?

    Besten Dank schonmal - kann mich erst am kommenden Wochenende dazu wieder melden - Gruß towi

  • Hm so wie es aussieht scheint python die Verbindung nicht permanent offen zu halten (persistent connections)... Dann musst du wohl doch jedes mal eine erneute Verbindung herstellen, was ich zwar blöd finde aber nunja...

    [code=php]
    #!/usr/bin/python

    from __future__ import print_function
    from RPi import GPIO
    import Queue # https://pymotw.com/2/Queue/
    import MySQLdb
    import time
    import sys

    mysqlHost = '127.0.0.1'
    mysqlPort = 3306
    mysqlLogin = 'root'
    mysqlPass = 'raspberry'
    mysqlDatabase = 'volkszaehler'

    gpio_list = [2, 3, 4, 17, 27, 22, 10, 9, 11, 18, 23, 24, 25, 8, 7]

    GPIO.setmode(GPIO.BCM)


    # Pro Status (GPIO HIGH oder GPIO LOW) ein Queue erstellen
    queue_high = Queue.Queue()
    queue_low = Queue.Queue()

    # ISR
    def interrupt_Event(channel):
    if GPIO.input(channel) == GPIO.HIGH:
    queue_high.put(channel)
    else:
    queue_low.put(channel)

    def exec_sql(sql):
    try:
    con = MySQLdb.connect(host=mysqlHost, port=mysqlPort, user=mysqlLogin, passwd=mysqlPass, db=mysqlDatabase)
    cursor = con.cursor()
    cursor.execute(sql)
    con.commit()
    cursor.close()
    con.close()
    except, e:
    print("Error: "+str(e))

    try:
    # Interrupt Event fuer jeden gpio hinzufuegen. Auf steigende und fallende Flanke reagieren und ISR deklarieren sowie Pin entprellen
    for gpio in gpio_list:
    GPIO.setup(gpio, GPIO.IN)
    GPIO.add_event_detect(gpio, GPIO.BOTH, callback=interrupt_Event, bouncetime=200)

    # Queues abarbeiten
    while True:
    time.sleep(0.1)
    if not queue_high.empty():
    pin = queue_high.get()
    print("GPIO HIGH: %s" % pin)
    exec_sql("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);" % (pin, time.time(), '1'))
    if not queue_low.empty():
    pin = queue_low.get()
    print("GPIO LOW: %s" % pin)
    exec_sql("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);" % (pin, time.time(), '0'))

    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")
    [/php]

    Ansonsten musste halt selber mal bisschen rumprobieren - das Ziel sollte denk ich mittlerweile klar sein :huh:

  • Hallo,

    Zitat

    Hm so wie es aussieht scheint python die Verbindung nicht permanent offen zu halten (persistent connections)...


    IMHO "lebt" das connection-Objekt so lange, wie der Kontext, in dem es erstellt wurde oder bis MySQL aufgrund von Inaktivität die Verbindung schließt. Wobei der timeout-Wert AFAIK bei etlichen Stunden liegt (zumindest in der Default-Konfiguration).

    Was sagt denn der Error-Log von MySQL? Oder blockt evtl. irgendein Paketfilter den Verkehr zwischen Python und MySQL?

    Alternativ kann man auch ein ORM wie SQLAlchemy nehmen - das kümmert sich dann auch um den Connection Pool.

    Gruß, noisefloor

  • Naja das Python Module 'MySQLdb' ist schon recht alt, soweit ich das sehe kümmert es sich nicht um ein reconnect. Von "Connection Pool" lese ich auch nur dass das nicht immer von Vorteil sein soll..

    Ich persönlich bevorzuge das Module CyMySQL da es einige speedups gegenüber 'MySQLdb' besitzt. Django zum Beispiel nutzt mysqlclient und dann gibts auch noch pymysql ... Von all denen weiß ich aber nicht ob 'persistent connections' oder ein automatischer Reconnect unterstützt wird
    => http://stackoverflow.com/questions/4960…724855#25724855

    Zumindest aus der PHP Welt weiß ich dass es viel schneller ist permanent eine MySQL Verbindung aufrecht zu erhalten, als jedes mal eine neue Verbindung herzustellen. Wär halt in diesem Fall denk ich auch von Vorteil.

  • Hallo,

    das MySQLdb Modul ist wohl inzwischen in der Tat das schlechteste der verfügbaren. Nur (leider) basieren immer noch vielen Howtos und Tutorials darauf.

    Zitat

    Zumindest aus der PHP Welt weiß ich dass es viel schneller ist permanent eine MySQL Verbindung aufrecht zu erhalten, als jedes mal eine neue Verbindung herzustellen. Wär halt in diesem Fall denk ich auch von Vorteil.


    Richtig. Verbindung auf- und abbauen kostet Zeit. Wobei das je nach Anwendungsfall ("zu Hause Anwendung") ja fast egal ist.

    Abgesehen davon denke ich immer noch, dass hier erst gar keine Verbindung zustande kommt...

    Gruß, noisefloor


  • Abgesehen davon denke ich immer noch, dass hier erst gar keine Verbindung zustande kommt...

    Ach, stimmt, du hast Recht... das hab ich bis eben völlig übersehen :blush:

    Code
    pi@raspberrypi:~ $ sudo /test01.py
    
    
    Fehler beim verbinden zum MySQL Server

    Und nun bin ich auch auf die Idee gekommen wie man das fixen kann... Falls die Meldung "MySQL server has gone away" kommt kann man das einfach wie folgt bewerkstelligen:

    [code=php]
    db.ping(True)
    cursor.execute("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);", (pin, time.time(), '1'))
    [/php]..usw..

    Oder so: http://stackoverflow.com/a/982873

    [code=php]
    #!/usr/bin/python

    from __future__ import print_function
    from RPi import GPIO
    import Queue # https://pymotw.com/2/Queue/
    import MySQLdb
    import time
    import sys

    mysqlHost = '127.0.0.1'
    mysqlPort = 3306
    mysqlLogin = 'root'
    mysqlPass = 'raspberry'
    mysqlDatabase = 'volkszaehler'

    gpio_list = [2, 3, 4, 17, 27, 22, 10, 9, 11, 18, 23, 24, 25, 8, 7]

    GPIO.setmode(GPIO.BCM)


    # Pro Status (GPIO HIGH oder GPIO LOW) ein Queue erstellen
    queue_high = Queue.Queue()
    queue_low = Queue.Queue()


    class DB(object):
    def __init__(self, host, port, user, passwd, db):
    self.conn = None
    self.host = host
    self.port = port
    self.user = user
    self.passwd = passwd
    self.db = db

    def connect(self):
    self.conn = MySQLdb.connect(host=self.host, port=self.port, user=self.user, passwd=self.passwd, db=self.db)

    def query(self, sql):
    try:
    cursor = self.conn.cursor()
    cursor.execute(sql)
    except (AttributeError, MySQLdb.OperationalError):
    self.connect()
    cursor = self.conn.cursor()
    cursor.execute(sql)
    return cursor


    # ISR
    def interrupt_Event(channel):
    if GPIO.input(channel) == GPIO.HIGH:
    queue_high.put(channel)
    else:
    queue_low.put(channel)


    try:
    db = DB(host=mysqlHost, port=mysqlPort, user=mysqlLogin, passwd=mysqlPass, db=mysqlDatabase)
    # Interrupt Event fuer jeden gpio hinzufuegen. Auf steigende und fallende Flanke reagieren und ISR deklarieren sowie Pin entprellen
    for gpio in gpio_list:
    GPIO.setup(gpio, GPIO.IN)
    GPIO.add_event_detect(gpio, GPIO.BOTH, callback=interrupt_Event, bouncetime=200)

    # Queues abarbeiten
    while True:
    time.sleep(0.1)
    if not queue_high.empty():
    pin = queue_high.get()
    print("GPIO HIGH: %s" % pin)
    cur = db.query("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);" % (pin, time.time(), '1'))
    if not queue_low.empty():
    pin = queue_low.get()
    print("GPIO LOW: %s" % pin)
    cur = db.query("INSERT INTO data (channel_id(%s)) (timestamp, value) VALUES (%s, %s);" % (pin, time.time(), '0'))

    except (KeyboardInterrupt, SystemExit):
    GPIO.cleanup()
    print("\nQuit\n")
    [/php]

  • Hallo meigrafd,

    sorry - hat etwas gedauert, bis ich mir hier wieder melden konnte.

    Das Interrup-Script funktioniert schon mal - allerdings nutze ich jetzt eine API-Schnittstelle, welche die Daten zur middlware des Volkszählers überträgt, da das per Mysql nicht zuverlässig funktioniert hat.

    Das Script wurde folgendermaßen angepasst und überträgt dann die Werte 0 oder 100 in eine prozentual skalierte Darstellung:

    Leider gibt es noch ein Problem mit der Erkennung der positiven oder negativen Flanke. Den GPIO habe ich auf internen Pull-Up eingestellt:

    Code
    GPIO.setup(gpio, GPIO.IN, pull_up_down = GPIO.PUD_UP)    #GPIO -> 3V3

    Es wird allerdings nicht zuverlässig die richtige Flanke erkannt(müsste immer zwischen LOW und HIGH wechseln):

    Ich habe bereits diverse Einstellungen der bouncetime und der sleep.time probiert - keine Besserung. Weiterhin habe ich es mit externem Pull-Up-Widerstand probiert - auch keine Besserung.
    Desweiteren habe ich mit der GPIO tool for Raspberry Pi-App die Erkennung des Einganges kontrolliert - erkennt sauber HIGH und LOW.
    Habe auch mal einen anderen GPIO-Eingang getestet - die selben Probleme.

    Hasst Du oder Ihr eventuell noch eine Idee, woran das liegen könnte.

    Besten Dank und Gruß - towi

  • Spoiler anzeigen


    Das Script wurde folgendermaßen angepasst und überträgt dann die Werte 0 oder 100 in eine prozentual skalierte Darstellung:

    Leider gibt es noch ein Problem mit der Erkennung der positiven oder negativen Flanke. Den GPIO habe ich auf internen Pull-Up eingestellt:

    Code
    GPIO.setup(gpio, GPIO.IN, pull_up_down = GPIO.PUD_UP)    #GPIO -> 3V3

    Es wird allerdings nicht zuverlässig die richtige Flanke erkannt(müsste immer zwischen LOW und HIGH wechseln):

    Ich habe bereits diverse Einstellungen der bouncetime und der sleep.time probiert - keine Besserung. Weiterhin habe ich es mit externem Pull-Up-Widerstand probiert - auch keine Besserung.
    Desweiteren habe ich mit der GPIO tool for Raspberry Pi-App die Erkennung des Einganges kontrolliert - erkennt sauber HIGH und LOW.
    Habe auch mal einen anderen GPIO-Eingang getestet - die selben Probleme.

    Hasst Du oder Ihr eventuell noch eine Idee, woran das liegen könnte.

    Hm vielleicht liegt das am zu niedrigen Bouncetime Wert... Hast du dir zu der Ausgabe mal eine Zeitangabe anzeigen lassen sodass man sehen kann wie schnell nacheinander ein "GPIO LOW" ausgegeben wird?

    Software Seitig wird mit "bouncetime=150" einfach ein weiterer Flankenwechsel innerhalb der letzten 150ms ignoriert.

    Hardware Seitig könntest du einen Kondensator parallel schalten direkt am Taster oder was auch immer du nutzt ;) Der Kondensator dient zum entstören und glätten, quasi ein Filter. Siehe dazu auch hier bzw https://www.mikrocontroller.net/articles/Entprellung

    Eine weitere Möglichkeit wäre dass das Queue Schuld ist bzw der letzte Eintrag nicht gelöscht wurde - allerdings wäre das komisch da beim normalen "Queue" Module ein get() ausreicht damit der Eintrag gelöscht wird: https://docs.python.org/2/library/queue.html#Queue.Queue.get

    Es könnte aber auch sein das deine Modifikation die while Schleife zu lange blockiert, also 'requests.post' zu lange benötigt da solange gewartet wird bis dieser Befehl beendet wurde, die ISR Funktion läuft aber in einem separaten Thread und wird davon nicht beeinflusst - die Abarbeitung könnte dann also evtl. hinterher hinken.

  • Ich sage nur, was ich schon oft gesagt habe: RPI.GPIO ist Mist und sollte nie mehr verwandt werden, nutze lieber PIGPIO (ggf auch gpiozero, das kenne ich aber nicht persoenlich).

    Auch mir gehen mit RPI.GPIO gelegentlich mal Flanken verloren - das liegt einfach in der hundsmiserablen Linux-Implementierung fuer GPIOs, welche bei aktiviertem triggering leider kein Sample sofort in das Device packt, sondern die Zeit, die zwischen Trigger & lesen vergeht dazu "genutzt" werden kann, wieder den Zustand zurueck zu aendern.

    PIGPIO hingegen sampelt mit hoher Praezision dank DMA, und da macht es dann auch nix, wenn man mal etwas zu spaet dran ist mit dem abbarbeiten. Zumindest nicht fuer einen Fall wie den deinen.

  • Hallo meigrafd,

    werde die Modifikation mal entfernen und testen.

    Wenn's dann funktioniert, wäre dann eine sleep.time vor exept eine Möglichkeit, das ganze etwas zu entschleunigen?

    Gruß towi
    Automatisch zusammengefügt:
    Hallo deets,

    OK - sollte es generell nicht funktionieren werde ich wohl umswitchen und PIGPIO nutzen.

    Gruß - towi

    Einmal editiert, zuletzt von towi (22. Oktober 2016 um 20:16)

  • Hallo zusammen,

    habe mal das probiert:


    Code
    resp = requests.post("http://192.168.178.103/middleware.php/data/"+$
           time.sleep(2.0)
    except (KeyboardInterrupt, SystemExit):
       GPIO.cleanup()
       print("\nQuit\n")

    Und sie da - funktioniert schonmal wesentlich sicherer.

    Jetzt kommt aber noch die letzte Herausforderung. Ich möchte mehreren GPIO´s entsprechende Zuordnungen zuweisen, damit dies an ihren zugewiesenen UUID-Channel die Logdaten übertragen.

    Dazu habe ich jetzt mal testweise zwei UUID-Kanäle entsprechend der GPIO_BOARD-Nummer erstellt:

    Code
    # UUID-Zuweisung
    uuid29 =  "c8ef0690-993e-11e6-93bf-05928009d3e3"
    uuid31 =  "6c63f740-94b6-11e6-8b55-15bb6ce24a5e"
    
    
    gpio_list = [29, 31]
    
    
    GPIO.setmode(GPIO.BOARD)

    Die UUID´ kommen direkt von der Kanal-Verwaltung des VZ-Frontend´s.

    Und dann habe ich die request.post´s eingefügt, um in Abhängigkeit des GPÍO´s auch in den richtigen Kanal den richtigen Wert zu übertragen.

    Das Script läuft auch ohne Fehler - nur hapert es gewaltig an der Erkennung der beiden Eingänge. Wenn sie denn erkannt werden, landen die Daten auch im Frontend des VZ - aber eben nur wenn.

    Gibt es vielleicht eine eleganteren Weg als der von mir gewählte oder umgestzte? :s

    Und zum Thema RPI.GPIO eventuell gegen PIGPIO auszutauschen - würde ich einen neuen Thread aufmachen - OK?

    Schonmal besten Dank - Gruß towi

  • Hallo zusammen,

    sorry - sieht natürlich so aus:

    Problem beseht allerdings weiterhin!

    Besten Dank schonmal - Gruß towi

Jetzt mitmachen!

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