Page 1 of 2

Gui und Eingänge lesen in Python

Posted: 02 May 2017, 15:33
by Tommy
Hallo Kunbus,

ich habe mit Qt Designer eine Gui Programmiert, in Python. Diese befindet sich auf dem RevPi und kommuniziert mit einem anderen Gerät über eine virtuelle Com Schnittstelle via USB Port. Dementsprechend welche Buttons ich auf dem Bildschirm drücke, fließen Daten zum angeschlossenen Gerät hin und her und werden auf der Gui dargestellt. Nun soll aber in Abhängigkeit von den Eingängen des RevPi, mit dem angeschlossenen Gerät kommuniziert werden. Doch wie löst man so etwas optimal?
Verwendet man dafür einen thread? Oder sollte man in Qt mit QTimer immer nach den Eingängen gucken (aber das erscheint mir nicht so gut zu sein)? Oder sollte man es besser mit logiCad3 realisieren, in ST, da es ja wohl dort auch eine Visualisierungsmöglichkeit gibt? Wie geht man denn bei sowas optimal vor?
Kann mir jemand Infos dazu geben?
Vielen Dank im Voraus.

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 01:38
by volker
Hallo Tommy,
welche Vorgehensweise die optimale ist,m kann man nicht pauschal beantworten, weil man dazu viel mehr Details von der Anwendung wissen müsste. Aber hier mal die Optionen:

1) Das Pythonprogramm so schreiben, dass es wie eine statemachine funktioniert und einen Endlosschleife durchläuft. Bei jedem Durchlauf pollst Du die DI(O) Eingänge im Prozessabbild (wie das in Python geht beschreiben wir in einem Tutorial). Je nach Zustand der Inputs wechselt die Statemachine in einen anderen Zustand, in dem dann die GUI bzw. Deine Kommunikation entsprechend Aufgaben durchführt. Der Nachteil dieser Vorgehensweise ist, dass die serielle Kommunikation dabei sehr wahrscheinlich als separater Thread laufen müsste, wahrscheinlich auch die GUI-Buttons.

2) Im Pythonprogramm eine separate Task schreiben, die zyklisch das Prozessabbild pollt und die Eingänge dort beobachtet. Bei bestimmten Zuständen der Eingänge (welche weißt nur Du und Deine Anwendung) löst diese Task dann eine Aktion aus (z.B. Kommunikation mit anderen Tasks über deren Interface. Das ist die preisgünstigste und eleganteste Methode, erfordert aber einige tiefergehende Programmierkenntnisse.

3) Du verwendest ein komplettes Steuerungspaket: Mit Logi.CAD die Eingänge überwachen und über eine HMI (Spider Control oder das im Juni bei uns erhältliche Procon Web IoT) die Visualisierung realisieren (einschließlich Buttons, die dann im Prozessabbild als Bool-Werte stehen. Vorteil: Wenig Programmierkenntnisse erforderlich und keinen Stress mit Threads etc. Nachteil: Die serielle Kommunikation mit dem externen Gerät könnte kompliziert werden, wenn es nicht zufällig ein gängiges Protokoll spricht, welches RevPi von Hause aus unterstützt. Dann müsstest Du nämlich dieses Protokoll selber implementieren (in Python) und als zusätzliche Task im Hintergrund laufen lassen (oder einen Funktionsbaustein für Logi.CAD erstellen).

Vielleicht kann Dir die Community hier ja mit Details weiterhelfen, wenn Du Dich für eine Vorgehensweise entschieden hast und dann aber auch sehr genau beschreiben musst, was Du da realisieren willst und was das für eine serielle Kommunikation ist.

Viel Erfolg bei dem Projekt!

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 08:49
by Tommy
Hallo Volker,
erstmal vielen Dank für deine Antwort. Nach deinen Vorschlägen zufolge wäre Option 3, die wohl einfachste Variante, fällt aber zum jetzigen Zeitpunkt ja flach. Bleibt also nur Threads und Tasks.
Ich habe die Gui so programmiert, dass ein Startbutton, meiner Gui, den Eingang des RevPi simuliert. Das bedeutet, immer wenn ich den Startbutton mit der Maus anklicke, sollen Daten zwischen dem RevPi und einem Transponder Programmiergerät fliessen.
Das Ergebnis des Transponder Programmierens wird dann auf der Gui dargestellt. Es ist eine virtuelle Com Schnittstelle mit der Einstellung 8-n-1 und 19200 baud. Das Protokoll wird von der Firma des Programmiergerätes vorgegeben. Im Endeffekt ist alles soweit fertig. Jetzt will ich nur noch, dass das Startsignal anstatt meines Mausklicks durch einen Eingang des RevPi ersetzt wird. Dementsprechend ob die Programmierung dann erfolgreich war, wird dann der RevPi einen IO oder NIO Ausgang hoch setzen bzw. high setzen. Hat der RevPi den Ausgang hochgesetzt, wird dadurch automatisch das Startsignal zum Programmieren wieder zurückgenommen.
Dieser Vorgang wiederholt sich dann immer wieder.
Wenn ich jetzt eine Task verwende, könnte ich dann den Großteil meiner Programmierung weiter verwenden? Wenn ich dich richtig verstanden habe, könnte ich ja dann über den Eingang des RevPi die Task laufen lassen und dementsprechend was für eine Bitfolge an den Eingängen ansteht verhaltet sich dann die Software. Im Moment ist es nur ein Startsignal aber vom Prinzip her kann ich ja anhand der Eingänge verschiedene Eingangsvarianten erstellen.
Das müsste doch dann so gehen oder?

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 10:04
by RevPiModIO
Schau dir doch alternativ mal das RevPiModIO-Modul für Python3 an.
https://revpimodio.org

Da kannst du mit auto_refresh alle IOs überwachen lassen und auch Events einbauen, die bei einem Wechsel von IOs ausgelöst werden (auch als Thread)... Oder eben mit deinen Buttons IOs setzen/lesen usw.

Also z.B. eine Funktion als Event an "Input_1", die dann ausgeführt wird und die Daten verarbeitet. https://revpimodio.org/events-mit-dem-mainloop/

Gruß, Sven

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 11:14
by Tommy
Hallo Sven,
danke für die Info. Ich bin mal deinem Link zur mainloop gefolgt und habe mir das Beispiel angeguckt. So wie es beschrieben ist, wird mit start die mainloop gestartet und das Programm wartet dann ja auf die Events. Das ist aber noch kein parallel laufender Prozess, oder? Wäre die Lösung dann die Nutzung der RevPiCallback Funktion? Es muss ja leider ein parallel laufender Prozess sein wegen der Gui. Und jetzt hab ich noch eine Frage. Wenn ich den RevPi nach euren Vorgaben Umbau, kann ich den RevPi so konfigurieren, dass er beim Einschalten sofort mein eigenes Programm startet?. Ich frage, weil meine Gui so konstruiert ist, dass keine Tatstatur erforderlich ist. Ich würde gerne die Maschine einschalten und sie dann am liebsten direkt über die Gui steuern können.
Viele Grüsse
Tommy

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 11:39
by volker
Hallo Tommy,
Wenn sich das bei Deiner Anwendung wirklich nur auf diese wenigen IO-Funktionen beschränkt, dann würde ich dein aktuelles Programm echt so belassen und mit einer zusätzlichen task, die Du beim Init der GUI startest, die entsprechenden Bytes im prozessabbild zyklisch abfragen bzw. setzen. Der Datenaustausch zwischen der Task und der GUI könnte ganz einfach über globale Variablen erfolgen, wenn es wirklich nur um diese einfache Verwendung geht. Das wäre analog zu der Überwachung von Tastatureingaben in einer GUI über eine eigene Task zu sehen. Wenn die Systemkonfiguration bei Dir festzementiert ist und nicht geändert wird (also z.B. immer ein DIO direkt rechts als erstes Gerät am Core montiert), dann könntest Du die sache nochmehr vereinfachen und direkt über einen festen Offset die Eingänge lesen bzw. die Ausgänge schreiben.
Codebeispiel folgt...

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 12:06
by RevPiModIO
Jo, das müsste noch als Thread gestartet werden. Also die Klasse RevPiModIO von Thread ableiten und die "def start()" in "def run()" ändern. Zum Stoppen könnte man dann noch eine "def stop()" einbauen um den Mainloop zu verlassen.

Die Callback Funktion wird verwendet, wenn man Events mit "as_thread=True" setzt - Was für Funktionen mit langer Laufzeit gut ist, aber man muss eben auch aufpassen, dass sie nicht mehrfach gestartet werden. Außer es soll so sein, wie in "Bennis Run" https://revpimodio.org/bennis-run-thrads-im-mainloop/

Von der Sache müsst das dann so aussehen, den Thread startest du dann wann immer du ihn brauchst..

Code: Select all

#!/usr/bin/python3
import revpimodio
import signal
import time
from threading import Thread


class RevPiModIOTest(Thread):

    """Kleine Testklasse fuer mainloop().

    Wenn nach der Instantiierung der Klasse die start() Funktion aufgerufen
    wird, gehen wir in den mainloop(). Das Programm wartet dann auf Events.
    Per Strg+C wird der mainloop() sauber verlassen.

    """

    def __init__(self):
        """Wird bei der Instantiierung aufgerufen."""
        super().__init__()
        daemon = True

        # RevPiModIO Instantieren und Module in den auto_refresh setzen, damit
        # das Prozessabbild automatisch synchronisiert wird.
        self.revpi = revpimodio.RevPiModIO(auto_refresh=True)

        # Auf einen Input eine Funktion anmelden, welche ausgeführt wird,
        # wenn sich der Inputwert ändert.
        self.revpi.devices[30].reg_event("Input", self.eventfunktion)

        # Signal events
        signal.signal(signal.SIGINT, self._sigexit)
        signal.signal(signal.SIGTERM, self._sigexit)

    def _sigexit(self, signum, frame):
        """Signal handler to exit."""

        # mainloop verlassen und Kontrolle zurückgeben
        self.revpi.devices.exit()

    def eventfunktion(self, ioname, iovalue):
        """Wird nur ausgefuehrt, wenn ein Input Pin den Status aendert.
        @param ioname: Wird automatisch uebergeben und enthaelt den IO-Namen
        @param iovlaue: Wert, den der IO zum ausloesezeitpunkt hat"""
        # Eingänge werden auf Ausgänge gespiegelt und eine Bildschirmausgabe
        # erfolg. Natürlich nur, wenn das event ausgelöst wird!
        self.revpi.devices[30]["Output"].value = iovalue
        print(time.time(), ioname, iovalue)

    def run(self):
        """Hier laeuft das eigentliche Programm in der Endlosschleife."""

        # LED A1 am Core grün setzen
        self.revpi.devices.core.A1 = 1

        # In den mainloop gehen und auf Events warten
        print("Gehe in den mainloop()")
        self.revpi.devices.mainloop()

        # LED A1 beim Verlassen des mainloop() ausschalten
        self.revpi.devices.core.A1 = 0
        self.revpi.devices.writeprocimg()

    def stop(self):
        self.revpi.devices.exit()

# Das müsste iwo beim Programmstart gemacht werden...
if __name__ == "__main__":
    root = RevPiModIOTest()
    root.start()
    time.sleep(20)
    root.stop()
Gruß, Sven

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 12:08
by RevPiModIO
Der Autostart von GUIs kann je nach verwendetem Window Manager in autostart Scripten gemacht werden... Bei openbox z.b. ~/.config/openbox/autostart

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 13:05
by Tommy
Hallo Sven,
vielen Dank für deine ausführliche Antwort. Ich werde erstmal versuchen es mit Volkers Idee zu realisieren, da ich denke dass es in meinem Fall am einfachsten zu lösen ist.

Re: Gui und Eingänge lesen in Python

Posted: 03 May 2017, 23:59
by volker
Hier noch wie versprochen der Code mit einem separaten Thread für die IOs:

Code: Select all

import threading
from time import sleep


Inputs = 0 		# this is 2 bytes (INT) or any other type of data which holds the input states of the DIO
Outputs = 0 	# this is 2 bytes (INT) or any other type of data which holds the output states of the DIO

def IO_Thread():
	global Inputs
	global Outputs
    	#init this thread 
	
	# opening piControl interface
	f=open("/dev/piControl0","wb+",0)
	print("driver successfully opened!")

	while 1:
		#poll DIO inputs
		f.seek(InputOffsetDIO1) # do replace the "InputOffsetDIO1" with the offset in your configuration!
		x=f.read(2)
		Inputs = x[0] + 256 * x[1] # works only in python 3. For python 2 you will need to use struct library to get numbers out of the byte array
		
		#set DIO outputs
		f.seek(OutputOffsetDIO1) # do replace the "OutputOffsetDIO1" with the offset in your configuration!
		x=(Outputs).to_bytes(2, 'little') # this works only with python 3. Process image is organized in little Endian!
		f.write(x)

		sleep(0.1) # do not loop with maximum velocity in order to give the system a chance to do other stuff...

thread2 = threading.Thread(target=IO_Thread)    
thread2.start()    

while 1:
	print("..Do your GUI and communication stuff using Inputs and Outputs variable to react to IOs or set IOs..")
	sleep(2)