[PyQt] pyqtSignal, pyqtSlot을 이용한 클래스 사이의 값 전달 구현

이정찬·2022년 5월 18일
1

PyQt

목록 보기
1/6
post-thumbnail

2021년 8월부터 2021년 11월까지 진행된 PyQt를 이용한 스마트팩토리 외주에서 배운 사항들을 정리하기 위해 작성한 글입니다.
Raspberry Pi 3 환경에 Raspbian OS를 설치하여 진행하였습니다.

1. 왜 클래스 사이의 통신이 필요한가요?

Arduino nano 33 BLE 센서를 이용해 값을 받아오는 클래스와, PyQt를 이용하여 GUI를 구성하는 클래스를 따로 구성하여, 받아온 값을 GUI class에 전달하는 방식으로 구조를 짰습니다.
따라서, Arduino 센서값을 받아오는 클래스에서 GUI class로 값을 전달하는 방법이 필요했습니다.

2. pyqtSignal 선언

pyqtSlot과 pyqtSignal은 모두 PyQt5 프레임워크의 QtCore 모듈 내에 정의되어 있습니다.

from PyQt5 import QtCore 

위 코드를 사용하면 QtCore의 모든 module을 사용할 수 있습니다.

from PyQt5.QtCore import pyqtSignal, pyqtSlot

QtCore의 다른 기능은 제외하고, pyqtSignal, pyqtSlot 만을 사용한다면, 위와 같은 사용법을 추천드립니다. 최적화 측면에서도 사용할 모듈만 import하는 것이 더 유리합니다.

쓰레드를 통해 1초에 한번씩 값을 보낼 것이기 때문에, QtCore.QThread를 상속받은 센서 클래스(값을 보낼 클래스)에서 전역변수로 pyqtSignal을 선언해줍니다.

# noiseValue.py
class NoiseValue(QtCore.QThread):
	datasent = QtCore.pyqtSignal(float, int, int, int)
    ...

순서대로 float, int, int, int로 이루어진 총 4개의 값을 보낼거기 때문에, signal을 위 모양대로 선언해줍니다. 보내는 자료형, 형태에 따라 괄호 안의 값들을 바꿔서 이용합니다.

# noiseValue.py
def __init__(self):
	...
    self.sendable = True
    self.runnable = True
    self.maxTime = config.maxTime # default 1
    self.startTime = time.time()
    self.ser = serial.Serial('/dev/microphone', config.baudrate, timeout=1) # serial communication
    ...

클래스의 생성자를 정의합니다. 1초에 한번씩, sendable 값을 확인하여 미리 이름을 지정해둔 Arduino 센서에서 시리얼 통신을 통해 받아오는 값을 다른 클래스에 보내게 됩니다.

# noiseValue.py
def run(self):
	while self.runnable and self.ser.isOpen():
    	...
    	serialValue = self.ser.readline().decode()
        ...
        if (time.time() - self.startTime) >= self.maxTime and self.sendable:
                self.dataSent.emit(vibeValue, round(float(temperature)), round(float(humidity)), noiseValue)
        		...
                self.startTime = time.time()

thread가 실행되는 run 함수입니다. 실행 가능하고, 센서가 정상연결 되어있다면 무한히 돌면서 Arduino 센서에서 값을 가져와서, 해독합니다. 미리 원하는 값을 가져올 수 있도록 센서에 코드를 업로드 해둔 상태이며, 이에 대해서는 추후 포스팅 하겠습니다.
while문 안에서 마지막으로 값 전달이 진행된 때와, 현재의 시간을 빼서 1초보다 크다면, 위에서 선언했던 pyqtSignal의 emit 함수를 실행합니다. 보내는 값의 자료형은 선언시 명시했던 자료형과 동일합니다. 이후, 마지막으로 값 전달이 실행된 시간을 갱신합니다.

정확히 1초에 한번 실행되는 코드는 아니지만, sleep() 함수등에 영향을 받지 않으면서 대략적으로 1초에 한번씩 실행되는 코드를 만들기 위해 이렇게 작성하였습니다.

3. pyqtSlot 선언

pyqtSignal의 선언이 끝났다면, 데이터를 받을 클래스에 pyqtSlot으로 지정할 함수를 선언해줍니다.

# machineStateUI.py
@QtCore.pyqtSlot(float, int, int, int)
def setValues(self, _vibe, _temperature, _humidity, _noise):
	self.setGraphValue(_noise, _vibe)
	self.setTemperatureValue(_temperature)
	self.setHumidityValue(_humidity)
        
    URL = config.URL + '/data'
    payload = { 
        'serial' : self.currentBarcode,
        'process': self.processNum,
        'data' : {
            'noise' : _noise,
            'accel' : _vibe,
            'temp' : _temperature,
            'humi' : _humidity,
            'water_temp': 9999,
        }
    }
    res = requests.post(URL, json=payload)
 	...

pyqtSignal로 보내는 매개변수와 동일한 형식으로 값을 전달받는 함수를 선언합니다.

@QtCore.pyqtSlot(float, int, int, int)

이 선언은 사용하는 PyQt의 버전에 따라 명시할 때, 명시하지 않을 때가 다르며, 이 슬롯이 무슨 타입으로 매개변수를 받는지에 대한 선언입니다.

이렇게 pyqtSlot을 선언하면, 상기했던 pyqtSignal 이 pyqtSlot 함수를 연결해줘야 합니다. 보통은 클래스의 생성자에서 진행되며, 생성자에 이 두 줄을 삽입하면 noiseValue의 emit(pyqtSignal)에서 보낸 값이 MachineStateUI.py의 setValues로 들어가게 됩니다.

# machineStateUI.py
import noiseValue
...

class MachineStateUI(QtWidgets.QMainWindow):
    def __init__(self, num, s):
        ...
        self.noise = noiseValue.NoiseValue()
        self.noise.dataSent.connect(self.setValues)
        ...

4. 마치며

외주를 할 당시, 센서에서 들어온 값을 1초마다 한번씩 다른 클래스로 보내는 방식이 익숙치 않아서 꽤 힘들었던 기억이 있습니다. 그래도 pyqtSlot과 pyqtSignal을 한번 알고 사용하니 그 이후에 PyQt를 사용하는 프로젝트에서 굉장히 유용하게 사용했었습니다.

Serial Communication과 Http Communication에 대해서도 추후 서술할 예정입니다.

profile
개발자를 꿈꾸는 사람

0개의 댓글