PyQt4 GUI mit zusätzlichem Python Code verknüpfen?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
julian
User
Beiträge: 5
Registriert: Donnerstag 5. Juni 2014, 09:16

Hallo zusammen! :)

Stehe vor einem kleinen Problem (und vermute das die Lösung für einen erfahreneren Programmierer ganz einfach ist. ;) ).
Ich möchte mit einem Raspberry Pi und Python bzw PyQt eine kleine Temperaturregelung realisieren. Abgesehen von der weiteren Baustelle, dem Regler und dem dafür notwendigen Threading, möchte ich nun zunächst lediglich die gemessene Temperatur im lcdNumber Display des GUI anzeigen.

Das ganze wird auf dem Raspberry über das start.py Skript direkt im XServer gestartet und ergibt auf dem Adafruit Display ein wunderschönes Fenster mit LCD Anzeige ;)


Kann mir bitte jemand helfen wie ich die Programme verknüpfen kann um die vom Thermoelement ermittelte Temperatur "tc" am Ende des temp.py Codes an das lcdNumber Display der GUI zu übergeben?
Separat in der Konsole gestartet funktioniert das per SPI bit banging an den GPIO Pins 22, 23 und 27 angeschlossene Thermoelement mit dem letzten Codeblock temp.py einwandfrei.

Vielen Dank schon mal im voraus!! :)



Mit PyQt habe ich folgende GUI erstellt:

Code: Alles auswählen

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'main.ui'
#
# Created: Mon May 26 14:27:45 2014
#      by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(320, 240)
        MainWindow.setCursor(QtGui.QCursor(QtCore.Qt.BlankCursor))
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.lcdNumber = QtGui.QLCDNumber(self.centralwidget)
        self.lcdNumber.setGeometry(QtCore.QRect(200, 30, 101, 41))
        font = QtGui.QFont()
        font.setPointSize(16)
        font.setBold(True)
        font.setWeight(75)
        self.lcdNumber.setFont(font)
        self.lcdNumber.setCursor(QtGui.QCursor(QtCore.Qt.BlankCursor))
        self.lcdNumber.setLayoutDirection(QtCore.Qt.LeftToRight)
        self.lcdNumber.setAutoFillBackground(False)
        self.lcdNumber.setFrameShape(QtGui.QFrame.NoFrame)
        self.lcdNumber.setLineWidth(1)
        self.lcdNumber.setMidLineWidth(0)
        self.lcdNumber.setSmallDecimalPoint(False)
        self.lcdNumber.setMode(QtGui.QLCDNumber.Dec)
        self.lcdNumber.setObjectName(_fromUtf8("lcdNumber"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 320, 21))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))

Code: Alles auswählen

'''
Created on 26.05.2014

@author: julian
'''
#!/usr/bin/python
import sys
from PyQt4 import QtGui
from main import Ui_MainWindow


class Main(QtGui.QMainWindow):
    
    
    def __init__(self):

        QtGui.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
 
if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    window = Main()
    window.show()    
    sys.exit(app.exec_())       

Code: Alles auswählen

'''
Created on 03.06.2014

@author: julian
'''
#!/usr/bin/python
import RPi.GPIO as GPIO

class MAX31855(object):
    def __init__(self, cs_pin, clock_pin, data_pin, units = "c", board = GPIO.BCM):

        self.cs_pin = cs_pin
        self.clock_pin = clock_pin
        self.data_pin = data_pin
        self.units = units
        self.data = None
        self.board = board

        # Initialize needed GPIO
        GPIO.setmode(self.board)
        GPIO.setup(self.cs_pin, GPIO.OUT)
        GPIO.setup(self.clock_pin, GPIO.OUT)
        GPIO.setup(self.data_pin, GPIO.IN)

        # Pull chip select high to make chip inactive
        GPIO.output(self.cs_pin, GPIO.HIGH)

    def get(self):
        '''Reads SPI bus and returns current value of thermocouple.'''
        self.read()
        self.checkErrors()
        return getattr(self, "to_" + self.units)(self.data_to_tc_temperature())

    def get_rj(self):
        '''Reads SPI bus and returns current value of reference junction.'''
        self.read()
        return getattr(self, "to_" + self.units)(self.data_to_rj_temperature())

    def read(self):
        '''Reads 32 bits of the SPI bus & stores as an integer in self.data.'''
        bytesin = 0
        # Select the chip
        GPIO.output(self.cs_pin, GPIO.LOW)
        # Read in 32 bits
        for i in range(32):
            GPIO.output(self.clock_pin, GPIO.LOW)
            bytesin = bytesin << 1
            if (GPIO.input(self.data_pin)):
                bytesin = bytesin | 1
            GPIO.output(self.clock_pin, GPIO.HIGH)
        # Unselect the chip
        GPIO.output(self.cs_pin, GPIO.HIGH)
        # Save data
        self.data = bytesin

    def checkErrors(self, data_32 = None):
        '''Checks error bits to see if there are any SCV, SCG, or OC faults'''
        if data_32 is None:
            data_32 = self.data
        anyErrors = (data_32 & 0x10000) != 0 # Fault bit, D16
        noConnection = (data_32 & 1) != 0 # OC bit, D0
        shortToGround = (data_32 & 2) != 0 # SCG bit, D1
        shortToVCC = (data_32 & 4) != 0 # SCV bit, D2
        if anyErrors:
            if noConnection:
                raise MAX31855Error("No Connection")
            elif shortToGround:
                raise MAX31855Error("Thermocouple short to ground")
            elif shortToVCC:
                raise MAX31855Error("Thermocouple short to VCC")
            else:
                # Perhaps another SPI device is trying to send data?
                # Did you remember to initialize all other SPI devices?
                raise MAX31855Error("Unknown Error")

    def data_to_tc_temperature(self, data_32 = None):
        '''Takes an integer and returns a thermocouple temperature in celsius.'''
        if data_32 is None:
            data_32 = self.data
        tc_data = ((data_32 >> 18) & 0x3FFF)
        return self.convert_tc_data(tc_data)

    def data_to_rj_temperature(self, data_32 = None):
        '''Takes an integer and returns a reference junction temperature in celsius.'''
        if data_32 is None:
            data_32 = self.data
        rj_data = ((data_32 >> 4) & 0xFFF)
        return self.convert_rj_data(rj_data)

    def convert_tc_data(self, tc_data):
        '''Convert thermocouple data to a useful number (celsius).'''
        if tc_data & 0x2000:
            # two's compliment
            without_resolution = ~tc_data & 0x1FFF
            without_resolution += 1
            without_resolution *= -1
        else:
            without_resolution = tc_data & 0x1FFF
        return without_resolution * 0.25

    def convert_rj_data(self, rj_data):
        '''Convert reference junction data to a useful number (celsius).'''
        if rj_data & 0x800:
           without_resolution = ~rj_data & 0x7FF
           without_resolution += 1
           without_resolution *= -1
        else:
             without_resolution = rj_data & 0x7FF
        return without_resolution * 0.0625

    def to_c(self, celsius):
        '''Celsius passthrough for generic to_* method.'''
        return celsius

    def to_k(self, celsius):
        '''Convert celsius to kelvin.'''
        return celsius + 273.15

    def to_f(self, celsius):
        '''Convert celsius to fahrenheit.'''
        return celsius * 9.0/5.0 + 32

    def cleanup(self):
        '''Selective GPIO cleanup'''
        GPIO.setup(self.cs_pin, GPIO.IN)
        GPIO.setup(self.clock_pin, GPIO.IN)

class MAX31855Error(Exception):
     def __init__(self, value):
         self.value = value
     def __str__(self):
         return repr(self.value)

if __name__ == "__main__":

    # Multi-chip example
    import time
    import os
    cs_pins = [27]
    clock_pin = 23
    data_pin = 22
    units = "c"
    thermocouples = []
    for cs_pin in cs_pins:
        thermocouples.append(MAX31855(cs_pin, clock_pin, data_pin, units))
    running = True
    while(running):
        try:
            for thermocouple in thermocouples:
                rj = thermocouple.get_rj()
                try:
                    tc = thermocouple.get()
                except MAX31855Error as e:
                    tc = "Error: "+ e.value
                    running = True
                os.system('clear')
                print("tc: {} and rj: {}".format(tc, rj))
            time.sleep(1)
        except KeyboardInterrupt:
            running = False
    for thermocouple in thermocouples:
        thermocouple.cleanup()
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Du hast doch im Prinzip alles vorhanden, was genau ist das Problem? Wenn der Code von dir ist, solltest du ohne den Hauch eines Problems fähig sein, die gewünschten Daten ans GUI zu senden.

Letztes Endes sollte der Code, der aktuell in der while Schleife läuft, in einem separaten Thread laufen. Aus eben jenem Thread kannst du dann bequem ein Signal (mit der Temperatur als Parameter) an den GUI-Thread senden.

pyqtSignal
Emitting Signals

Vielleicht hilft dir das bereits weiter.
Wenn ich dich missverstanden habe, dann versuch bitte das Problem oder die Frage näher zu erläutern.

EDIT:
Du musst das GUI nicht erst in eine Klasse umwandeln, du kannst *.ui Dateien auch explizit via uic zur Laufzeit laden und auf dem MainWindow platzieren. Ganz ohne den Umweg über pyuic.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Madmartigan hat geschrieben:Du musst das GUI nicht erst in eine Klasse umwandeln, du kannst *.ui Dateien auch explizit via uic zur Laufzeit laden und auf dem MainWindow platzieren. Ganz ohne den Umweg über pyuic.
Ich würde das noch etwas schärfer formulieren: Du solltest auf keinen Fall Code generieren lassen, sondern diesen immer mittels uic zur Laufzeit laden. Das erspart dir ständiges Neugenerieren der GUI und schafft eine saubere Trennung vom Code.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

EyDu hat geschrieben:
Madmartigan hat geschrieben:Du musst das GUI nicht erst in eine Klasse umwandeln, du kannst *.ui Dateien auch explizit via uic zur Laufzeit laden und auf dem MainWindow platzieren. Ganz ohne den Umweg über pyuic.
Ich würde das noch etwas schärfer formulieren: Du solltest auf keinen Fall Code generieren lassen, sondern diesen immer mittels uic zur Laufzeit laden. Das erspart dir ständiges Neugenerieren der GUI und schafft eine saubere Trennung vom Code.
Dass die Trennung vom "Code" sauberer ist, sehe ich nicht ganz. Ich zumindest schreibe GUIs ohne Designer respektive von Hand und der Code ist einwandfrei von der Logik getrennt. Ich wollte lediglich auf den Umstand hinweisen, dass geneigte Nutzer des GUI-Designers durch die Verwendung von pyuic an Bequemlichkeit verlieren, nicht dass der Code eventuell unsauber ist.
julian
User
Beiträge: 5
Registriert: Donnerstag 5. Juni 2014, 09:16

Hallo zusammen!

Vielen Dank für die Antworten und entschuldigt bitte meine späte Reaktion. Ich bringe mir den ganzen Spaß gerade Anhand von Tutorials bei. Code nachvollziehen und für mich anpassen geht schon sehr gut, von Grund auf selber schreiben hapert leider noch ein wenig.

Daher auch meine vermutlich ziemlich "leichte" Frage mit der Übergabe eines Wertes an den Gui Thread. ;)

Zum Verständnis abstrahiere ich das ganze ein wenig und bitte euch um Hilfe eine Zufallszahl als Variable an das LCD Display der GUI zu senden. Der Code unten ist mein Stand der Dinge.

gui.ui enthält dabei lediglich ein QWidget mit einem QLCDNumber darauf.

Code: Alles auswählen

import sys,  threading,  random,  time

from PyQt4.QtGui import QApplication, QMainWindow, QLabel, QFont
from PyQt4 import QtCore, QtGui
from PyQt4.uic import loadUi

class newWindow(QMainWindow,  threading.Thread):
	def __init__(self):
		QMainWindow.__init__(self)
		self.ui = loadUi('gui.ui')
		self.ui.show()

class RandomNumber(QObject,  threading.Thread):
    signal = Signal()

    def __init__(self):
        QObject.__init__(self)

    def run(self):
        running = True
        while(running):
            i = random.randint(0,  1500)
            self.signal.emit()
            time.sleep(1)

if __name__ == '__main__':

	app = QApplication(sys.argv)
	fenster = newWindow()
	sys.exit(app.exec_())
Viele Grüße und vielen Dank schon mal! :)
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

So z.B.:

Code: Alles auswählen

In [1]: from PyQt4 import QtGui, QtCore

In [2]: from random import random

In [3]: from time import sleep

In [4]: class RandNum(QtCore.QThread):
   ...:     randnum = QtCore.pyqtSignal(str)
   ...:     def run(self):
   ...:         while True:
   ...:             sleep(1)
   ...:             self.randnum.emit(str(random()))
   ...:             

In [5]: class Output(QtGui.QLabel):
   ...:     def __init__(self, parent=None):
   ...:         QtGui.QLabel.__init__(self, parent)
   ...:         self.thread = RandNum(self)
   ...:         self.thread.randnum.connect(self.setText)
   ...:         self.thread.start()
   ...:         

In [6]: app = QtGui.QApplication([])

In [7]: w = Output()

In [8]: w.show()

In [9]: app.exec_()
Benutzeravatar
Madmartigan
User
Beiträge: 200
Registriert: Donnerstag 18. Juli 2013, 07:59
Wohnort: Berlin

Du deklarierst das Signal einfach entsprechend.

Code: Alles auswählen

signal = pyqtSignal(int)
Als Übergabeparameter wird nun eine Ganzzahl erwartet.

Wenn du dann das Signal emittierst, übergibst du den gewünschten Wert:

Code: Alles auswählen

signal.emit(my_int)
In der aufrufenden Klasse musst du lediglich das Signal an etwas binden. (connect())

Code: Alles auswählen

RandomNumber.signal.connect(my_function)
my_function() muss die gleichen Parameter deklarieren wie jene, die das Signal mitbringt, also in dem Falle wäre es:

Code: Alles auswählen

def my_function(value):
    print value
    # dein code hier ...
EDIT: Da war jerch schneller... :)
julian
User
Beiträge: 5
Registriert: Donnerstag 5. Juni 2014, 09:16

Es läuft!! :D
Vielen Dank!
Antworten