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


파이썬을  이용한 태양광 모니터링 마지막
- 사용자 환경(UI) 만들기 -

지난번에 이어 태양광 발전 모니터링 프로그램 개발기 마지막편 입니다.
이런 뻘짓을 하실 분이 또 있을지 모르겠지만 혹시나 참고가 될까 해서 남겨봅니다.
1,2탄에서 만든 시리얼통신으로 인버터 데이터를 얻는 부분과 실시간 그래프표시하는 위젯을
메인윈도우에 탑재하고 데이터 저장 및 각종 설정 부분을 툴바로 만들었습니다.
이번편에서는 기존 대비 PYQTGRAPH 보조축생성하기, 범례만들기등을 추가하였습니다.

기존 내용은 아래 지난번 글을 참고 바랍니다.

[Python]파이썬을 이용한 태양광 모니터링 시스템 자작기 1탄 : fantasy297.tistory.com/640
[Python]파이썬을 이용한 태양광 모니터링 시스템 자작기 2탄 : fantasy297.tistory.com/641

1. 전체 UI 환경

메인윈도우 중앙에 그래프와 데이터 라벨을 배치하고 상단에 간단한 명령툴바를 추가하였습니다.
다른 모니터링 프로그램을 레이아웃을 참고하여 최대한 꾸며 보았습니다.
아이콘은 대부분 파워포인트 도형으로 그렸습니다. 

 

동양ENP 인버터 모니터링 프로그램 최종 레이아웃

 

툴바에는 과거 날짜를 선택하면 과거 데이터를 불러오고, 현재데이터를 저장하는 부분과
모니터링을 시작/정지하고 그래프와 데이터를 삭제하는 부분
그래프 선두께, 선타입, 데이터 표시여부들을 설정할 수 있는 부분
Y축 범위를 설정하는 부분, 시리얼통관 관련 설정 부분
그리고 일별 데이터를 컬러맵과 3D 차트로 보여주는 부분으로 구성하였습니다.

 

 동양ENP 인버터 모니터링 프로그램 툴바 설명

 

툴바에서 명령 실행시 나타나는 다이얼로그창 모습

 

2. 들어가면서

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

3. 메인윈도우에서 위젯 실행하기 

메인윈도우에서 직접적으로 라벨이나 Pyqtgraph등을 배치하는 것은 불가능하므로 별도 Class를 추가하여
실행시켜 줍니다.

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        # 그래프 위젯 호출 후 메인 윈도우에 생성
        GraphWg = RS485Graph()
        self.setCentralWidget(GraphWg)
		
class RS485Graph(QWidget):
    .....
    
if __name__ == '__main__': # 직접 실행 여부 확인후 창 생성
    import sys
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        app = QApplication.instance() 
    ex = MainWindow() # 실행
    QApplication.setQuitOnLastWindowClosed(True)
    app.exec_()
    app.quit()

 

4. 툴바에 명령어 아이콘 생성하기

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        # 툴바 액션 생성
        SelectDateAction = QAction(QIcon('./icon/011_Calendar.png'), 'Select Date', self)
        SelectDateAction.setStatusTip('Select a date to display monitoring data')
        SelectDateAction.triggered.connect(GraphWg.SelectDateDialog)
		...
        # 툴바 생성
        self.toolbar = self.addToolBar('Main_Tollbar')
        self.toolbar.addAction(SelectDateAction)
		...

 

5. 상태표시줄 생성하기 및 다른 Class에서 상태표시줄 호출하기

메인윈도우에서 생성한 상태표시줄에 다른 Class(여기서는 그래프를 그리는 위젯)에서 사용하기 위해
메인윈도우에 별도 함수를 지정하고 다른 메인윈도우에서 호출한 자식 클래스에서 부모(메인윈도우)의 함수를
호출하여 메인윈도우의 상태표시줄에 상태를 표시할 수 있습니다.

class MainWindow(QMainWindow):
	def __init__(self):
		super().__init__()
		self.initUI()
	def initUI(self):
		...
		# 상태창 생성
		self.statusBar().showMessage('Ready')

	def set_status_message(self, message):
		return self.statusBar().showMessage(message) 
		
class RS485Graph(QWidget):
	def __init__(self):
		super().__init__()
		...
		
	def CreatFolderFile(self):
		...
		self.parent().set_status_message('Wrong Data Index')

 

6. PyqtGraph 보조축 사용하기

PyqtGraph의 레이아웃은 아래와 같습니다.
그래프가 그려지는 부분을 ViewBox라하고 이 ViewBox를 여러개 추가해주고 주 그래프와 링크를 맞춰주는 방식으로
보조축을 추가할 수 있습니다. 축아이템(Axisitem)은 여러개 추가할 수 있으며 오른쪽 첫번째는 'right'로 추가하고
그 이상의 축은 레이아웃 행렬 (예를 들어 오른쪽 2번째축은 (2, 3)형태로 추가할 수 있습니다.
메인뷰박스의 윈도우 크기가 변경되었을 경우 뷰박스 링크를 다시 맞춰줘야하므로 함수를 하나 만들고
Connect로 연결해 줍니다. (좀더 자사한 내용은 PyqtGraph 예제파일을 참고하시기 바랍니다.)

PYQTGAPH 레이아웃

	# Main Graph 보조축1(에너지 뷰박스) 설정
	self.MainGraph_Energy = pyqtgraph.ViewBox()
	self.MainGraph.plotItem.showAxis('right')
	self.MainGraph.scene().addItem(self.MainGraph_Energy)
	self.MainGraph.getAxis('right').linkToView(self.MainGraph_Energy)
	self.MainGraph_Energy.setXLink(self.MainGraph)
	self.MainGraph.getAxis('right').setPen(pyqtgraph.mkPen(color=(114, 166, 3), width=1))
	# Main Graph 보조축2(온도 뷰박스) 설정
	self.MainGraph_Temp = pyqtgraph.ViewBox()
	self.tempAxis = pyqtgraph.AxisItem('right')
	self.MainGraph.plotItem.layout.addItem(self.tempAxis, 2, 3)
	self.MainGraph.scene().addItem(self.MainGraph_Temp)
	self.tempAxis.linkToView(self.MainGraph_Temp)
	self.MainGraph_Temp.setXLink(self.MainGraph)
	self.tempAxis.setZValue(-1000)
	self.tempAxis.setPen(pyqtgraph.mkPen(color=(242, 145, 27), width=1))
	# Main Graph 뷰박스 업데이트
	self.UpdateMainVB()
	self.MainGraph.getViewBox().sigResized.connect(self.UpdateMainVB)
	
    def UpdateMainVB(self):
        self.MainGraph_Energy.setGeometry(self.MainGraph.getViewBox().sceneBoundingRect())
        self.MainGraph_Temp.setGeometry(self.MainGraph.getViewBox().sceneBoundingRect())
        self.MainGraph_Energy.linkedViewChanged(self.MainGraph.getViewBox(), self.MainGraph_Energy.XAxis)
        self.MainGraph_Temp.linkedViewChanged(self.MainGraph.getViewBox(), self.MainGraph_Temp.XAxis)

 

7. 하루전, 한달전 날짜 계산하기

datautil의 relativedelta를 사용하여 이전 날짜를 계산할 수 있습니다.

from dateutil.relativedelta import relativedelta
    def getDayList(self,day):
	    NowDay = datetime.datetime.now().strftime('%m/%d')
        Daylist = [NowDay]
        # 이전 24일 리스트 생성
        for i in range(1,day):
            Temp = datetime.datetime.now()-datetime.timedelta(days=i)
            LastDay = Temp.strftime('%Y/%m-%d')
            Daylist.append(LastDay)
        return Daylist
    def getMonthList(self,month):
        # 이전 12개월 리스트 생성
        NowMonth = datetime.datetime.now().strftime('%Y/%b')
        Monthlist = [NowMonth]
        for i in range(1,month):
            Temp = datetime.datetime.now()-relativedelta(months=i)
            LastMonth = Temp.strftime('%Y/%b')
            Monthlist.append(LastMonth)
        return Monthlist

 

8. 그래프 범례추가하기 및 범례 글꼴 설정

기본적으로 PYQTGRAPH의 범례는 메인뷰박스의 그래프만 나옵니다.
그래서 보조축에 들어간 그래프범례를 위해 메인뷰박스에 동일한 Curve를 추가해 주었습니다.

self.Effi_Legend = self.VAGraph.plot(pen=pyqtgraph.mkPen(color=(114, 166, 3), ....) # 범례용 커브
self.Effi_curve = pyqtgraph.PlotCurveItem(pen=pyqtgraph.mkPen(color=(114, 166, ....) # 실제 커브
self.VAGraph_Effi.addItem(self.Effi_curve)
# 범례 추가 및 글꼴 설정
font = QtGui.QFont('Bahnschrift SemiLight', 9)
LegMain = self.MainGraph.addLegend(offset=(10,10))
LegMain.setPen(pyqtgraph.functions.mkPen(180,180,180))
LegMain.setBrush(pyqtgraph.functions.mkBrush(245,245,245))
for item in LegMain.items:
	for single_item in item:
		if isinstance(single_item, pyqtgraph.graphicsItems.LabelItem.LabelItem):
			single_item.item.setFont(font)
		else:
			pass

 

9. 툴바 명령 실행시 별도 창 생성하기

메인 윈도우에서 위젯을 실행하고 위젯 아래 다시 자식 클래스를 생성하여 창을 실행합니다.

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        # 그래프 위젯 호출 후 메인 윈도우에 생성
        GraphWg = RS485Graph()
        self.setCentralWidget(GraphWg)
        # 툴바 액션 생성
        HeatMapAction = QAction(QIcon('./icon/045_Heatmap.png'), 'HeatMap Chart', self)
        HeatMapAction.setStatusTip('Creat Daily Gen. Energy and Run Time Heat Map')
        HeatMapAction.triggered.connect(GraphWg.CreateHeatMap)
        ....
        self.toolbar.addAction(HeatMapAction)
        ....
		
class RS485Graph(QWidget):
    def __init__(self):
    	super().__init__()
        ...
		
    def CreateHeatMap(self):
        Hetmapdlg = HeatMapGraph()
        Hetmapdlg.exec_()
        ...
		
class HeatMapGraph(QDialog):
    def __init__(self):
        super().__init__()
        ...

 

10. 마치면서

이상으로 동양EnP 태양광 인버터에서 데이터를 취득하여 자동 저장하고 저장된 데이터를 그래프로
보여 주는 윈도우 프로그램을 제작해 보았습니다.
최종 프로그램 원본은 아래에서 다운 받으실 수 있습니다.
약 2달간 프로그램을 실행시켜보며 문제 있는 부분은 수정하였으나 오류가 있을 수 있습니다.
알려주시면 가능한한 최대한 수정해 보도록하겠습니다.

 

ESP3K5_Monitoring_Program_v119.zip
0.15MB

동양E&P 인버터 모니터링 프로그램 v1.19 
각 시스템에 맞게 COM Port/ 인버터 ID등은
설정창에서 수정하여 사용하시기 바랍니다.

 

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

이 글을 공유하기

댓글(6)

  • 2020.12.13 12:00

    비밀댓글입니다

    • 2020.12.14 16:08 신고

      다른것 때문에 비슷한 걸 해본적 있는데.
      커브를 만들어주는 함수랑 업데이트하는 함수랑 별도로 만들었습니다.

      커브만드는 함수는 필요할때만 호출하고
      커브만든후 업데이트함수에서 setdata로 그래프를 그렸습니다.

      커브이름을 전역변수로 만들고
      (문의하신 것 처럼 self로 해도 무방할 것 같습니다.)

      class Main(QWidget):
      CrvNam = []

      def SetCrvList(self):
      for i in range(len(Col)):
      Main.CrvNam[0].append('AnalCrvNam_' + str(i))

      def SetPen(self):
      for i in range(len(Main.CrvNam)):
      Main.CrvNam[i] = self.AnalogPlot.plotItem.plot(.....)

      def GraphUpdate(self):
      for i in range(len(Main.CrvNam)):
      Main.CrvNam[i].setData(x = ..., y = ...)

    • 2020.12.17 20:44

      비밀댓글입니다

  • 공돌
    2021.04.07 18:37

    안녕하세요.. 올려주신 코드 참고해서 작성중인데
    혹시 curve line 클릭시 그 line의 이름을 가지고 오는법 문의드려도 될까요?
    self.LineV_curve 이면 클릭했을때 Line Voltage 을 툴에 표기해주고싶은데..
    검색해서 구현해봤는데 sigClicked 를 써도 동작을 안하고 ㅠ sigMouseMoved는 이름을 못가지고 오고 좌표만 가져와서 쉽지가 않네요ㅠ

  • 코린이
    2021.10.07 18:17

    소중한 글 감사합니다
    파이썬으로 모니터링 공부를 맨땅해딩중인데
    원래 코딩을 좀 하셨었나요?
    아니면 완전 초짜도 공부하면 가능한건가요? ㅠ

Designed by JB FACTORY