Anfängerfrage: GPIO-Eingänge, HDMI-CEC und http-get

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

Hallo Gemeinde,

als Pi-Neuling und Newbie im Forum erstmal
ein hallo welt in die Runde. Ich habe seit 20 Jahren nicht mehr programmiert und es damals im Studium nicht gerade gemocht...

Anyway. Ich habe einen Pi und eine Idee, die ich möglichst "schön" umsetzen möchte:

1. Ein externes Gerät mit potenzialfreiem Ausgang kommt an einen GPIO. Ob ein kurzes, 10-sekündiges oder Dauersignal ausgegeben wird, weiß ich nicht. Wenn es kein Dauersignal ist, kann es auch sein, dass das Gerät den Kontakt auch wieder öffnet und danach irgendwann nochmal schließt.
2. Beim ersten Auslösen (mögliche Folgeauslösungen sollen ignoriert werden) des GPIO soll folgendes machen:
A) Philips-TV am HDMI-Port des Pi aus dem Standby aufwecken und für 60 Minuten an lassen, danach wieder auf Standby schalten.
B) Einen http-get absetzen um eine externe API anzusteuern.
C) Eine Website im lokalen Chromium anzeigen.
3. Ein Taster soll dem Pi sagen, ab wann er wieder auf einen Kontakt am Eingang reagieren soll.

Das Ganze soll als Autostart im Hintergrund laufen.
Bis auf Pkt. 3 habe ich das brachial über eine endlose while-Schleife, mit der auf den GPIO gehorcht wird, geschafft. Leider mit hoher CPU-Last und nicht immer wurde der Fernseher angeschaltet.
Geht das galanter mit Interrupts?

Punkt 3, also das "Wiedereinschalten" des Lauschens auf den GPIO-Eingang bekomme ich nicht hin - ich verheddere mich irgendwie in den Schleifen :-(.

Ich freue mich auf Ideen. :)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Statt zu Pollen, benutz besser die Ereignis-Rückrufaktionen. Und ich vermute du benutzt RPI.GPIO. Das ist Mist, bessere sind gpiozero oder pigpio. Und was deine Logik angeht: bitte Code zeigen, dabei die Code-Boxen nicht vergessen!
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

Richtig vermutet, ich habe es erstmal mit RPi.GPIO versucht.

Mein bisheriger Code sieht so aus:

Code: Alles auswählen

import RPi.GPIO as GPIO
import time
import requests
import os
GPIO.setmode(GPIO.BCM)
GPIO.setup(21, GPIO.OUT)
GPIO.setup(18, GPIO.IN)
GPIO.setup(6, GPIO.IN)
while True:
    if GPIO.input(18) == 1:
        # LED Ausschalten
        GPIO.output(21, GPIO.LOW)    
    else:
        # LED Einschalten
        GPIO.output(21, GPIO.HIGH)    
        # Absetzen http-Request
        r = requests.get('https://www.xyz.de')
        #TV aus Standby aufwecken
        os.system('echo "on 0" | sudo cec-client -s -d 1')
        #nach 300 Sekunden TV in Standby
        time.sleep(300)
        os.system('echo "standby 0" | sudo cec-client -s -d 1')
Die Probleme:
  • Beim Lauschen auf den Eingang geht die CPU-Last auf 30+ % hoch
  • Der Timer bringt nicht nur den Fernseher in Standby, sondern startet auch das Lauschen neu, was ich erst über einen Taster am GPIO 6 neu veranlassen wollte.
  • Chromium rufe ich nicht auf, weil der Aufruf als Kiosk-Vollbild erfolgen sollte und ich das Chromium nicht beibringen konnte. Stattdessen Habe ich die Page als Autostart im Kiosk-Modus angelegt und lasse das Programm im Hintergrund laufen.
Mit gpiozero habe ich noch nichts probiert.

Any idea?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@jomahoso: benutze »wait_for_edge« statt busy-waiting und subprocess mit communicate statt os.system. Du solltest zum Schluß aufräumen.

Code: Alles auswählen

import RPi.GPIO as gpio
import time
import requests
import subprocess

def cec_client(cmd):
    p = subprocess.Popen(["sudo", "cec-client", "-s", "-d", "1"], stdin=subprocess.PIPE)
    p.communicate(cmd)

def initialize():
    gpio.setmode(gpio.BCM)
    gpio.setup(21, gpio.OUT)
    gpio.setup(18, gpio.IN)
    gpio.setup(6, gpio.IN)

def main():
    try:
        initialize()
        while True:
            gpio.wait_for_edge(18, gpio.BOTH)
            if gpio.input(18) == 1:
                # LED Ausschalten
                gpio.output(21, gpio.LOW)    
            else:
                # LED Einschalten
                gpio.output(21, gpio.HIGH)    
                # Absetzen http-Request
                r = requests.get('https://www.xyz.de')
                #TV aus Standby aufwecken
                cec_client("on 0")
                #nach 300 Sekunden TV in Standby
                time.sleep(300)
                cec_client("standby 0")
    finally:
        gpio.cleanup()

if __name__ == '__main__':
    main()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wie gesagt, beschäftige dich mal mit den callbacks. Dann kannst du in der hauptschleife gemütlich auf Ereignisse warten.

http://abyz.me.uk/rpi/pigpio/python.html#callback

Code: Alles auswählen

from functools import partial
import Queue

def callback(queue, gpio, level, tick):
       queue.put((gpio, level, tick))


def main(): 
     ... # uA pigpio pi anlegen wie dokumentiert 
     q = Queue.Queue()
     pi.callback(22, pigpio.EITHER_EDGE, partial(callback, q))
     ...
     while True: 
         pin, level, tick = q.get() # wartet ohne CPU Last!
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

@Sirius3: Danke für den Code, den ich... nicht wirklich verstehe. Es ist doch "nur" mein Code in anderer Optik, oder? Meine Probleme löst er doch nicht (Ignorieren von weiteren Eingängen nach mehr als 300 Sek. - dafür wollte ich den zweiten Taster).

Ich habe deinen Code mal 1:1 kopiert und er wirft folgende Meldungen nach Drücken des Tasters an GPIO 18:

Traceback (most recent call last):
File "/home/pi/Programme/a.py", line 38, in <module>
main()
File "/home/pi/Programme/a.py", line 30, in main
cec_client("on 0")
File "/home/pi/Programme/a.py", line 8, in cec_client
p.communicate(cmd)
File "/usr/lib/python3.4/subprocess.py", line 941, in communicate
self.stdin.write(input)
TypeError: 'str' does not support the buffer interface

Die LED blinkte nur kurz und der Fernseher wachte nicht auf. Danach brach das Programm ab. :?:

Ich verstehe es nicht...
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

@ _deets_
__deets__ hat geschrieben:Statt zu Pollen, benutz besser die Ereignis-Rückrufaktionen. Und ich vermute du benutzt RPI.GPIO. Das ist Mist, bessere sind gpiozero oder pigpio. Und was deine Logik angeht: bitte Code zeigen, dabei die Code-Boxen nicht vergessen!
Okay, das mit den Callbacks - nach meinem Verständnis sind das Funktionen, die zum späteren Aufruf verwendet werden - macht den Code übersichtlicher und lässt Mehrfachverwendungen im Hauptcode zu.

Ich habe bzgl. GPIOzero nachgelesen. Das bringt mir doch "nur" etwas Vereinfachung hinsichtlich der Notation, oder?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein. Sondern es ist zuverlässiger. RPI.GPIO ist ziemlicher Murks. Ja, ich weiß, das sieht man überall. Weil halt im PI-Lager abschreiben und rumfrickeln an der Tagesordnung ist. Pigpio ist mein Favorit. Mehr Performanz und Funktionalität inbegriffen. Auch wenn du die jetzt ggf nicht brauchst.

Vor allem aber erlaubt dir der callback auch noch anderer Sachen zu machen, statt nur auf die gpios zu warten.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@jomahoso: Du solltest aber anfangen, ihn zu verstehen. Ob man das jetzt über wait_for_edge oder über Queues macht, wie __deets__ vorschlägt, ist ja erstmal egal. Wichtig für den Anfang ist es, das Programm sinnvoll mit Funktionen zu strukturieren.

Wenn Du Python3 benutzt, mußt Du die Kommandos richtig Encodieren:

Code: Alles auswählen

p.communicate(cmd.encode())
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

Mmmmmh, ich versuche den Code nachzuvollziehen... Dazu habe ich das Programm in Python2 gestartet. Es funktioniert, aber die LED am Ausgang bleibt dauerhaft an, auch wenn das Programm nach einer Zeit wieder auf den Eingang reagiert.
Warum ist das so?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wo wird die denn eingeschaltet, und was fehlt da?
jomahoso
User
Beiträge: 6
Registriert: Samstag 3. Februar 2018, 15:55

Danke erstmal für die Tipps mit den Prozeduren. :D
Nach dem Verstehen (ja, lesen hilft) und dem Ausprobieren funktioniert mein Programm wirklich zufriedenstellend - die Abfrage, dass nur beim ersten Kontakt etwas passiert und nachfolgende Kontakte am IN ignoriert werden, habe ich über einen Zähler realisiert.

Was ich aber hardwaretechnisch als Problem bemerkt habe, ist eine extrem hohe Anfälligkeit des Eingangs gegenüber Störstrahlung. Je nach Position des Pi, des Breadboards oder des Kabels zum potenzialfreien Kontaktes des abzufragenden Gerätes wird ein (nicht vorhandenes) Signal am Eingang erkannt und das Programm rattert los. Das ist sehr doof. :cry:
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dazu führt man üblicherweise pull-ups oder pull downs ein. Wenn möglich erstere. Der PI hat auch schon welche eingebaut. Die kannst du aktivieren. Steht in der Doku zu deiner GPIO Bibliothek
Antworten