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


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

 

주택으로 이사한 뒤 적응을 위해 몇 년 동안 블로그를 방치해 두었다가 간만에 포스팅해봅니다.
제목과 같이 파이썬을 이용한 태양광 발전 모니터링 프로그램 개발기입니다.
이런 뻘짓을 하실 분이 또 있을지 모르겠지만 혹시나 참고가 될까 해서 남겨봅니다.

1. 태양광 발전 환경

지붕에 가정용 3kW 태양광이 설치 되어 있습니다.
태양광 모듈은 300W 신성 E&G SS-BM300PB 10개로 구성되어 있고,
인버터는 정격출력 3.5kW의 동양 E&P의 ESP3K5가 설치되어 있습니다.

문제는 인버터 위치가 지붕의 태양광 패널 하단에  있다는 것 입니다.
발전을 하고있는지 발전량은 어느정도 인지 인버터 창을 확인하려면 지붕을 올라가야만 합니다.

실제로 설치 후 1년정도 지났을 때 인버터가 고장이 났는데 전기료가 나오기 전까지 몰랐답니다.

검색을 통해 인버터가 RS485 시리얼통신을 지원한다는 사실을 알게 되었고,
PC에서 실시간 발전 현황을 확인할 수 있도록 원격 모니터링 시스템 구축에 바로 돌입 하였습니다.

3kW급 태양광 발전 환경
여담이지만 태양광 밑의 새집에는 참새가 육아중 입니다.

 

2. 인버터와 시리어 통신을 위한 하드웨어 구성

첫째로 인버터가 RS485 통신을 지원하기 때문에 컴퓨터와 통신하기 위해 시리얼 to USB 모듈이 필요합니다.
동양 E&P에서는 리얼시스 제품을 추천했으나 가격대가 상당해서 알리발 저렴이로 구매하였습니다.

알리에서 판매중인 RS485 to USB 모듈
찾아보면 더 작고 저렴한 제품들이 많이 있습니다.

 

다음으로 통신 케이블이 필요합니다.
동양E&P 인버터에 맞는 방수 커넥터 및 케이블이 필요한데 온라인에서 구매 할 수 없더군요
동양E&P에 직접 문의하니, 엔플러스코리아를 소개 시켜줍니다.
각종 커넥터류 전문 업체이고, 동양E&P의 협력업체 인 듯합니다.
전화하여 5,000원에 구매 했습니다. 연락처는 검색하시면 바로 나오니 이곳에서는 언급하지 않겠습니다.

 

동양 E&P ESP3K5인버터의 RS485 통신 단자와 케이블

 

다음으로 옥상에서 컴퓨터가 있는 방까지 통신선을 연결해야합니다.
이 과정이 가장 힘들었습니다. 옥상에 매립된 CD관에 인출선을 사용하여
전기 계량기가 있는 단자함까지 통신선을 1차 인입하고,
다시 단자함에서 실내 분배 단자함까지 2차 인입하였습니다.
이곳까지 왔더니 통신선이 부족하여 추가 구매후 단자함에서 연결한뒤
방까지 3차 인입하여 통신선을 컴퓨터방에 있는 인터넷 단자까지 끌고 왔습니다.
전선은 약 15m가량 들었습니다.
만약 집에 남는 UTP케이블이 매립되어있다면 UTP 케이블을 사용하여도 무방합니다.

 

RS4855 통신 케이블

 

최종 구축된 통신 환경

 

3. 인버터의 통신 규약(Protocol)

동양E&P의 태양광 자료실 홈페이지가 있는데 얼마전부터 자료가 보이지 않더군요.
자료실에서 자체 모니터링 프로그램도 배포했습니다.
저도 배포된 프로그램을 사용했지만, 그래프 및 자료 저장기능등이 부족하더군요.
이 프로그램 자작이라는 뻘짓의 가장큰 이유 입니다.

다행히 초창기에 받아놓은 Protocol 자료가 있어 이 있어서 사용하였습니다.
아래는 동양 E&P ESP3K5 인버터의 통신 규약입니다.

동양 E&P ESP3K5 송신 규약
byte2는 인버터의 일련번호로 기기 시리얼번호의 뒤 2자리입니다.
byte6은 데이터 검증용으로 byte2~4를 단순 더한 값 입니다.

 

 동양 E&P ESP3K5 수신 규약
헤더부는 고정이고입니다. 데이터 검증용 CheckSUM은 0~30번 바이트 값을 XOR SUM한 값입니다.

 

 동양 E&P ESP3K5 에러 코드
수신 데이터중 에러 코드 상세 설명입니다.

 

4. 아나콘다 및 필수 패키지 설치

일단, 파이썬을 사용하기 위해 관련 프로그램들을 설치하여야 합니다.
이 부분은 유튜브나 다른 블로그에 자세하게 소개 되어있으니 여기서는 패스하기로 하겠습니다.

저는 복잡한게 싫어 통합환경인 아나콘다만 설치하였습니다.
파이썬 개발 환경은 주요 패키지를 미리 포함한 '배포판 파이선'을 많이 사용하는데,
이 배포판 파이선 가운데 가장 널리 쓰는 게 바로 '아나콘다'입니다.

아나콘다 설치후 시리얼 통신과 데이터처리를 위한 Pyserial과 Pandas를 설치합니다.
설치방법은 'anaconda prompt'에서
conda install xxxxx 혹은 pip install xxxxx을 입력하면 됩니다. xxxxx는 패키지 이름입니다.
conda와  pip는 동일한데 아나콘다에서는 conda로 설치하는 것이 좀더 안정적인 것 같더군요.

추후 UI 및 그래프를 그리기 위해 pyqt5와 pyqtgraph도 설치해줍니다.

pip install pyqt5
conda install pyqtgraph
conda install pyserial
pip install keyboard
conda install pandas

아나콘다에서 태양광 모니터링을 위한
필수 패키지 설치


5. 태양광 모니터링 파이썬 프로그램

위키독스의 '점프투 파이썬' (https://wikidocs.net/book/1) 으로 독학한지 1주일만에
인터넷의 샘플 소스들을 참고하여 작성한 프로그램으로 고수님들이 보기에는 미흡한 점도 많고
코드가 매우 지저분 할 수 있습니다.
개선할 부분이나 좀더 세련된 방법을 알려주시면 감사하겠습니다.

프로그램에서 구현된 주요 내용은 아래와 같습니다.

1) 시리얼 통신으로 데이터 읽고 쓰기
통신 포트, 통신속도등을 설정하여 open 명령으로 통신 포트를 열어
read와 write 명령어로 데이터를 송수신 합니다.
송수신은 별도 함수로 작성하여 향후 사용 가능하게 하였습니다.
수신문자열 길이는 데이터 규약을 알기 때문에 미리 지정했습니다.
아스키 형태로 변환하여 문자열 리스트로 저장합니다.

 

import serial
import binascii
 
def ESP3K5_RX(RS485Port, RS485BaudRate, RS485bytesize, StationID, RS485RXANumber):
    
    ser = serial.Serial()
    ser.port = RS485Port
    ser.baudrate = RS485BaudRate
    ser.stopbits = serial.STOPBITS_ONE
    ser.bytesize =  RS485bytesize
    ser.parity = serial.PARITY_NONE
    ser.rtscts = 0
    ser.timeout = 0.6
    ser.open()
    # 수신 문자열 정의
    RS485RXArray =[]
    # 데이터 송신
    ser.write(bytes(bytearray([0x0A,0x96,StationID,0x54,0x18,0x05,108+StationID])))
    # 데이터 문자열 수신    
    for i in  range(RS485RXANumber):
        mHex = ser.read()
        RS485RXArray.append(binascii.hexlify(bytearray(mHex)))
 

 

2) 수신 데이터 검증
수신 데이터의 길이, 헤더, CheckSUM등을 확인하여 규약과 다를 경우
오류값을 반환 합니다. XOR CheckSUM 부분은 '^' 연산으로 처리하였습니다.

# 수신데이터 길이 검증
if len(RS485RXArray) != RS485RXANumber:
    ser.close()
    return 'Received data Error : Wrong Length'
# 수신데이터 헤더 검증
if RS485RXArray[0] != b'b1' or RS485RXArray[1] != b'b7':
    ser.close()
    return 'Received data Error : Wrong Header'
# 수신데이터 Station ID 검증
if int(RS485RXArray[2],16) != StationID :
    ser.close()
    return 'Received data Error : Wrong Station ID'
# 수신데이터 CheckSum 검증
Check = 0
RX_Check = int(RS485RXArray[31],16)
for i in range(RS485RXANumber-1):
    Check = Check ^ int(RS485RXArray[i],16)
if Check != RX_Check :
    ser.close()
    return 'Received data Error : Wrong CheckSUM'

 

3) 수신 메시지 변환 및 에러코드 변환
동양 E&P 인버터의 경우 Little-Endian통신 규약을 따르기 때문에 하위 바이트부터 데이터를 읽어야합니다.
데이터를 읽을 때  "Little" 명령어를 사용하면 되지만 사용법도 이해가 안되고, 머리가 나빠서
제가 보기 쉽게 더럽게 코딩 하였습니다.

# FaultCode 변환
RX_FaultCode = RS485RXArray[23] +RS485RXArray[22] + RS485RXArray[21] + RS485RXArray[20]
if RX_FaultCode  == bytes(b'00000000'):
    CX_Fault  = "No Fault"
elif RX_FaultCode  == bytes(b'00000001'):
    CX_Fault  = "Solar Over Current"
elif RX_FaultCode  == bytes(b'00000002'):
    CX_Fault  = "Solar Over Voltage"
              ...

# FaultCode 확인
if CX_Fault  != "No Fault":
    ser.close()
    return 'Inverter Fault : %d' % CX_Fault

#  수신 Data 변환
RX_SolarV1 = int(RS485RXArray[4] + RS485RXArray[3],16)/10
RX_SolarV2 = int(RS485RXArray[8] + RS485RXArray[7],16)/10
RX_SolarCurrent = int(RS485RXArray[6] + RS485RXArray[5],16)/10
RX_LineVoltage = int(RS485RXArray[10] + RS485RXArray[9],16)/10
RX_LineCurrent = int(RS485RXArray[12] + RS485RXArray[11],16)/10
RX_Temperature = int(RS485RXArray[14] + RS485RXArray[13],16)/10
              ...

# 전력값 및 효율 계산
CX_SolarVoltage = round((RX_SolarV1 + RX_SolarV2)/2,2)
CX_SolPower = round((CX_SolarVoltage * RX_SolarCurrent)/1000,3)
CX_LinePower = round((RX_LineVoltage * RX_LineCurrent)/1000,3)
CX_InverterEfficiency = round((CX_LinePower/CX_SolPower)*100,2)
 

 

4) 최종 수신값 반환
정상 수신시 딕셔너리로 각 항목별 값을 반환합니다.

#  출력값 반환
CX_Result = {'Solar_Voltage': CX_SolarVoltage,
             'Solar_Current': RX_SolarCurrent,
             'Solar_Power': CX_SolPower,
             'Line_Voltage': RX_LineVoltage,
             'Line_Current': RX_LineCurrent,
             'Line_Power': CX_LinePower,
             'Inverter_Efficiency': CX_InverterEfficiency,
             'Temperature': RX_Temperature,
             'Today_GEN_Power': RX_TodayTTL,
             'TTL_GEN_Power': RX_EnergyTTL,
             'AC_Frequency' : RX_Frequency,
             'RUN_Time' : RX_OperationTime,
             'PF' : RX_PowerFactor,
             'DSP_ver' : RX_DSPVersion,
             'RUN' : RX_RUN}
ser.close()    
return CX_Result
 

 

5) 데이터를 데이터프레임으로 변환하여 csv로 저장하기
정상 수신된 데이터를 행렬형식으로 보기 쉽게 데이터프레임으로 변환하고
csv형태로 저장합니다.
이 때 OS의 path 함수를 이용하여 기존파일이 존재하면 연속해서 새 값을 추가하고,
아울러, 첫줄에만 INDEX가 표시되도록 기존파일이 존재하면 header=False로 하여
첫줄에만 Index가 저장되도록 하였습니다.

# 출력 파일 이름 설정
CX_Date = time.strftime('%Y%m%d', time.localtime(time.time()))
OutFileName = "ESP3K5_Monitoring_Data_%s" % CX_Date
# 기존 파일 존재시 데이터 읽기
if not os.path.exists('./%s.csv'% OutFileName):
    CX_Result = DataFrame(columns = ['Solar_Voltage', 'Solar_Current', 'Solar_Power',
                              'Line_Voltage', 'Line_Current', 'Line_Power',
                              'Inverter_Efficiency', 'Temperature',
                              'Today_GEN_Power', 'TTL_GEN_Power', 'AC_Frequency',
                              'RUN_Time', 'PF', 'DSP_ver', 'RUN'])
else:
    CX_Result = pandas.read_csv('./%s.csv'% OutFileName, index_col=0)
# INDEX용 시간 계산
CX_Time = time.strftime('%H:%M:%S', time.localtime(time.time()))
# 인버터 데이터 수신
RS485 = ESP3K5_RX(Port, BaudRate, bytesize, InverterID, ArrayNumber)
# 데이터프레임 변환 및 csv파일로 누적 저장
Result_Temp = DataFrame(RS485, index = [CX_Time])
CX_Result = CX_Result.append(Result_Temp)
if not os.path.exists('./%s.csv'% OutFileName):
    Result_Temp.to_csv('./%s.csv'% OutFileName, mode='w')
else:
    Result_Temp.to_csv('./%s.csv'% OutFileName, mode='a', header=False)
 

 

6) 데이터 송/수신 무한루프 수행 및 'esc'등 특정키 누를시 무한루프 탈출
데이터 수신을 위해 무한 루프를 수행하여야하지만
특정 키를 누를시 빠져나올 수 있게 하였습니다.

import keyboard
 
# Keybord Interrupt시 무한 루프 종료
try:
    while True:
        if keyboard.is_pressed("esc"):
            print("Ending Monitoring...")
            break
        #수행할 프로그램
        time.sleep(Cycle_Time)
except KeyboardInterrupt:
    print("Ending Monitoring...")
    pass

 

6. 실행 결과물

프로그램 실행시 정상적으로 데이터프레임이 수신되는 것과 csv파일로 누적 저장 되는 것 을 확인 하였습니다.
출력된 csv파일을 엑셀등으로 그래프를 그리면 시각적으로 발전량을 확인 할 수 있습니다.

프로그램 실행시 출력 데이터 프레임

 

출력된 csv 파일을 엑셀로 불러온 결과물

 

엑셀로 발전량과 인버터 온도를 그래프로 그린 결과물

 

7. 마치면서

이상 파이썬을 이용하여 시리얼 통신으로 실시간 데이터를 취득하여
csv형태로 저장하고 각종 오류를 표시하는 것 까지 구현한 프로그램을 제작해보았습니다.
최종 프로그램 원본은 아래에서 다운 받으실 수 있습니다.

다음편에는 좀더 공부하여 Pyqt와 Pyqtgraph를 이용하여
윈도우 창에 실시간으로 그래프를 표시하는 것 까지 구현 해보도록 하겠습니다.

ESP3K5_Monitoring_v050.zip
0.00MB

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

 

 


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

이 글을 공유하기

댓글

Designed by JB FACTORY