[Python]파이썬을 이용한 태양광 모니터링 시스템 자작기 2탄


파이썬을  이용한 태양광 모니터링 프로그램 2탄
- RS485통신으로 실시간 데이터  그래프로 표시하기 -

 

지난번에 이어 태양광 발전 모니터링 프로그램 개발기 2탄 입니다.
이런 뻘짓을 하실 분이 또 있을지 모르겠지만 혹시나 참고가 될까 해서 남겨봅니다.
1탄에서 파이썬을 이용해 시리얼 통신으로 태양광 인버터에서 실시간 데이터를 받아
저장하는 프로그램을 만들었습니다.

2탄에서는 이 데이터를 이용하여 실시간 그래프를 그리는데 까지 제작해보았습니다.
시리얼 통신을 통해 데이터 취득하는 것은 이전 코드를 그대로 사용하였습니다.

1탄이 궁금하신 분은 아래 링크를 참고하시기 바랍니다.
[Python]파이썬을 이용한 태양광 모니터링 시스템 자작기 1탄 : fantasy297.tistory.com/640

1. 들어가면서

인터넷의 샘플 소스들을 참고하여 작성한 프로그램으로 제가 알아보기 쉽게 작성하는라 미흡한 점도 많고
코드가 매우 지저분 할 수 있습니다. 전체 코드를 설명하자니 복잡해질 것 같아
프로그램하면서 어려웠던 것 위주로 설명해 보겠습니다.

 

2. 윈도우 창 및 레이아웃 배치

PYQT5의 QWidget을 이용하여 창을 생성했고 QHBoxLayout과 QVBoxLayout을 이용하여 그래프, 라벨
그리고 그룹박스를 순서대로 배치 하였습니다.
GRID를 이용하여 배치하는 게 좀더 직관적이던데 시작을 이녀석으로 하다보니 조금 지저분해졌네요.
QHBoxLayout과 QVBoxLayout은 위젯들을 수평 혹은 수직으로 배치한는데 사용합니다.

 PYQT5의 QHboxlayout과 QVboxlayout

본 프로그램에서는 Hbox를 이용하여 그래프 2개가 수평배치된 Layout을 2개 생성하고
Vbox를 이용하여 그룹박스와 라벨을 수직 배치한 Layout을 10개 생성하고 이 10개를 수평으로 배치한
Hbox를 생성하여 총 3개의 Hbox를 Vbox에 넣어 최종 3행의 Layout을 갖는 배치를 하였습니다.

대략적인 코드는 아래와 같습니다.

from PyQt5.QtWidgets import QWidget, QApplication, QHBoxLayout, QVBoxLayout, QGroupBox, QLabel

class Main(QWidget):
    def __init__(self):
        super().__init__()
        # 레이아웃 생성
        hbox1 = QHBoxLayout()
        hbox2 = QHBoxLayout()
        hbox3 = QHBoxLayout()
        vbox1 = QVBoxLayout()
        Gbox0 = QVBoxLayout()
        Gbox1 = QVBoxLayout()
        Gbox2 = QVBoxLayout()
        Gbox3 = QVBoxLayout()
        Gbox4 = QVBoxLayout()
        Gbox5 = QVBoxLayout()
        Gbox6 = QVBoxLayout()
        Gbox7 = QVBoxLayout()
        Gbox8 = QVBoxLayout()
        Gbox9 = QVBoxLayout()
        # 그래프 객체 4개 생성
        self.Power = pyqtgraph.PlotWidget()
        self.Energy = pyqtgraph.PlotWidget()
        self.Voltage = pyqtgraph.PlotWidget()
        self.Temperature = pyqtgraph.PlotWidget()
        # Data Indicator 그룹 박스 생성
        self.groupbox_SV = QGroupBox('Solar Voltage')
        self.groupbox_SC = QGroupBox('Solar Current')
        self.groupbox_SP = QGroupBox('Solar Power')
        self.groupbox_LV = QGroupBox('Line Voltage')
        self.groupbox_LC = QGroupBox('Line Current')
        self.groupbox_LP = QGroupBox('Line Power')
        self.groupbox_T = QGroupBox('Temperature')
        self.groupbox_TTL = QGroupBox('Energy Today')
        self.groupbox_LIFE = QGroupBox('LifeTime Energy')
        self.groupbox_Status = QGroupBox('Inverter Status')
        # Data Indicator 라벨 생성
        self.label_SV = QLabel('0', self)
        self.label_SC = QLabel('0', self)
        self.label_SP = QLabel('0', self)
        self.label_LV = QLabel('0', self)
        self.label_LC = QLabel('0', self)
        self.label_LP = QLabel('0', self)
        self.label_T = QLabel('0', self)
        self.label_TTL = QLabel('0', self)
        self.label_LIFE = QLabel('0', self)
        self.label_Status = QLabel('Ready', self)
        # 그룹박스와 Data Indicator 수직 배치
        Gbox0.addWidget(self.label_SV)
        Gbox1.addWidget(self.label_SC)
        Gbox2.addWidget(self.label_SP)
        Gbox3.addWidget(self.label_LV)
        Gbox4.addWidget(self.label_LC)
        Gbox5.addWidget(self.label_LP)
        Gbox6.addWidget(self.label_T)
        Gbox7.addWidget(self.label_TTL)
        Gbox8.addWidget(self.label_LIFE)
        Gbox9.addWidget(self.label_Status)
        self.groupbox_SV.setLayout(Gbox0)
        self.groupbox_SC.setLayout(Gbox1)
        self.groupbox_SP.setLayout(Gbox2)
        self.groupbox_LV.setLayout(Gbox3)
        self.groupbox_LC.setLayout(Gbox4)
        self.groupbox_LP.setLayout(Gbox5)
        self.groupbox_T.setLayout(Gbox6)
        self.groupbox_TTL.setLayout(Gbox7)
        self.groupbox_LIFE.setLayout(Gbox8)
        self.groupbox_Status.setLayout(Gbox9)
        # 수평방향으로 창 그룹화 (1행 그래프 2개), (2행 그래프 2개), (3행 라벨 10개)
        hbox1.addWidget(self.Power)
        hbox1.addWidget(self.Energy)
        hbox2.addWidget(self.Voltage)
        hbox2.addWidget(self.Temperature)
        hbox3.addWidget(self.groupbox_SV)
        hbox3.addWidget(self.groupbox_SC)
        hbox3.addWidget(self.groupbox_SP)
        hbox3.addWidget(self.groupbox_LV)
        hbox3.addWidget(self.groupbox_LC)
        hbox3.addWidget(self.groupbox_LP)
        hbox3.addWidget(self.groupbox_T)
        hbox3.addWidget(self.groupbox_TTL)
        hbox3.addWidget(self.groupbox_LIFE)
        hbox3.addWidget(self.groupbox_Status)        
        # 그룹화된 창 수직방향으로 그룹화
        vbox1.addLayout(hbox1)
        vbox1.addLayout(hbox2)
        vbox1.addLayout(hbox3)
        # 윈도우창생성 및 레이아웃 배치
        self.setLayout(vbox1)
        self.setGeometry(100, 100, 2600, 1000)

 

최종 생성된 윈도우 창의 Layout

 

3. 폴더(디렉토리) 생성하기

데이터를 프로그램이 있는 폴더에 저장하다보니 파일 정리가 안되서
하위에 Data 폴더를 만들어 저장하도록 하였습니다.

import os

try:
    if not(os.path.isdir('./Data')):
        os.makedirs(os.path.join('./Data'))
except OSError as e:
    if e.errno != errno.EEXIST:
        print("Failed to create directory!!!!!")
        raise

파이썬에서 폴더 생성하기

 

4. 그래프 생성 및 실시간 업데이트 하기

pyqtgraph를 이용하였습니다.
pyqtgraph.PlotWidget을 이용하여 그래프 위젯을 생성하고
pyqtgraph.mkpen을 이용하여 그래프의 pen을 설정하였습니다.

Qtimer를 이용하여 일정시간(단위 ms)마다 update 함수를 호출합니다.
update 함수에는 setData를 이용하여 최신데이터로 업데이트 하여 그래프를 다시 그려줍니다.

import pyqtgraph
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtCore import pyqtSlot, QTimer

class Main(QWidget):
    def __init__(self):
        super().__init__()
        # 그래프 객체 생성 및 X축을 STRING축으로 설정
        self.Power = pyqtgraph.PlotWidget()
        # 그래프 펜 설정
        self.SolP_curve = self.Power.plot(pen=pyqtgraph.mkPen(color=(203, 26, 126),
                                           width=3, style=QtCore.Qt.SolidLine ))
        self.LineP_curve = self.Power.plot(pen=pyqtgraph.mkPen(color=(44,106, 180),
                                           width=3, style=QtCore.Qt.DotLine ))
        # 일정 시간 마다 그래프 및 라벨값 갱신
        self.Data_Get_Timer = QTimer()
        self.Data_Get_Timer.setInterval(Main.Cycle_Time*1000)
        self.Data_Get_Timer.timeout.connect(self.update)
        self.Data_Get_Timer.start()
        self.show()

    @pyqtSlot()
    def update(self):
            # 그래프 갱신
            self.SolP_curve.setData(Main.DF_Result.Solar_Power, name="Solar Power")
            self.LineP_curve.setData(Main.DF_Result.Line_Power, name="Line Power")

if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    ex = Main()
    sys.exit(app.exec_())

 파이썬 pyqtgraph를 이용하여 실시간 그래프 그리기

 

5. 그래프 x축을 문자(시간 H:M:S)로 설정하기

숫자가 아닌 데이터를 x축으로 설정할 경우 그래프를 그릴 때 오류가 발생합니다.
y축만 설정할 경우 x축이 1,2,3과 같이 숫자로 표시되어 원하는 결과가 나오지 않습니다.
이에 stringaxis를 이용하여 x축이 문자임을 선언해 주어야 합니다.

# 문자열 x축 생성
self.stringaxisP = pyqtgraph.AxisItem(orientation='bottom')
# 그래프 위젯 생성 및 X축을 STRING축으로 설정
self.Power = pyqtgraph.PlotWidget(axisItems={'bottom': self.stringaxisP})

 파이썬 pyqtgraph에서 문자열 x축 생성하기

 

6. 그래프 x축 눈금값 개수 제한 하기

x축을 문자열로 설정하면 모든 x축의 문자열이 표시되어 시간이 지나면 x축 눈금값들이 겹쳐서
알아볼수 없게 표시 됩니다. 이에 아래와 같이 눈금값 개수를 제한 하였습니다.
무언가 다른 방법이 있을 것 같은데, 구글에서 도저히 검색이 안되어 제 나름 대로 사용한 방법인데
좋은 방법 있으면, 알려주시면 감사하겠습니다.

# X축 최대 눈금 제한 (Tick간 겹치지않게 숫자 설정)
xMAXTickN = 16
# X축 눈금값 문자 읽기
xdict = dict(enumerate(Main.DF_Result.index))
xlist = list(xdict.items())
xticklist = []
if len(xlist) > xMAXTickN:
    tick_interval = len(xlist)//xMAXTickN
    tick_key = 0
    for i in range(xMAXTickN+1):
        xticklist.append(xlist[tick_key])
        tick_key += tick_interval
        # Tick키가 리스트 범위를 넘지 않도록 조건문 추가
        if tick_key >= len(xlist):
            tick_key = len(xlist)-1
    self.stringaxisP.setTicks([xticklist])
else:
    self.stringaxisP.setTicks([xdict.items()])

 파이썬 pyqtgraph에서 문자열 x축 눈금값(tick) 개수 제한 하기

7. 그래프, 라벨등 색상 및 글꼴 설정하기

이왕이면 이쁜게 좋아서 쓸데 없이 이런 꾸미기에 상당시간을 공들였습니다.
일단은 중요한 부분은 아니니까 직접다루진 않고
원본 소스코드 다운받아보시면 글꼴 및 색상 변경하는 부분들이 있으니
참고하시면 될 것 같습니다.

 

8. 마치면서

이상으로 태양광 인버터에서 일정시간 마다 데이터를 취득하여 csv형태로 저장하고
각종 오류를 및 현재 상태를 윈도우 창에 표시하고
데이터를 실시간으로 그래프로 그리는 것까지 구현된 프로그램을 제작해보았습니다.
최종 프로그램 원본은 아래에서 다운 받으실 수 있습니다.

 파이썬을 이용한 태양광 모니터링 프로그램 결과물

 

다음편에는 좀더 공부하여 포트설정 및 모니터링 시작 종료 데이터 저장, 불러오기등을
윈도우창에서 UI로 가능하는 것 까지 구현 해보도록 하겠습니다.

ESP3K5_Monitoring_v061_Pygraph.zip
0.01MB

동양E&P 인버터 모니터링 프로그램 v0.61 
각 시스템에 맞게 COM Port등 일부 변수들은
수정하여 사용하시기 바랍니다.

 

 

※ 글과 파이썬 코드의 저작권은 본인에게 있으며, 상업적용도의 사용을 금지합니다.
코딩에 대한 조언 이나, 개선할 점 등을 댓글로 남겨 주시면 감사하겠습니다.

이 글을 공유하기

댓글(0)

Designed by JB FACTORY