YOGYUI

힐스테이트 광교산::환기(전열교환기) 제어 RS-485 패킷 분석 본문

홈네트워크(IoT)/힐스테이트 광교산

힐스테이트 광교산::환기(전열교환기) 제어 RS-485 패킷 분석

요겨 2022. 6. 17. 11:10
반응형

도시가스, 난방 관련 제어를 하면서, RS-485 통신선 여러개가 묶여있는 포트로 오가는 패킷 중 네번째 바이트 값에 따라 어떤 기기가 관련있는지 조사해나가고 있다

총 7개 값 (0x18, 0x1B, 0x1C, 0x2A, 0x2B, 0x34, 0x48)이 네번째 바이트 값으로 설정되어 있으며, 그 중

  • 0x1B: 도기가스밸브
  • 0x18: 난방

인 것을 알게 되었다

 

이번엔 환기(전열교환기)를 제어하면서 5번째 바이트가 0x02인 명령 패킷을 캡쳐하고 분석해보자

1. 환기 가동 및 풍량 변경 패킷 분석

가스, 난방을 제외하고 패킷을 캡쳐해보자

class ParserVarious(SerialParser):    
    def interpretPacket(self, packet: bytearray):
        if packet[2:4] == bytearray([0x01, 0x1B]):  # 가스차단기
            if packet[4] == 0x04:  # 상태 응답
                state = 0 if packet[8] == 0x03 else 1
                result = {
                    'device': 'gasvalve',
                    'state': state
                }
                self.sig_parse_result.emit(result)
        elif packet[2:4] == bytearray([0x01, 0x18]):  # 난방
            room_idx = packet[6] & 0x0F
            if packet[4] == 0x04:  # 상태 응답
                if room_idx == 0:  # 일반 쿼리 (존재하는 모든 디바이스)
                    thermostat_count = (len(packet) - 10) // 3
                    for idx in range(thermostat_count):
                        dev_packet = packet[8 + idx * 3: 8 + (idx + 1) * 3]
                        if dev_packet[0] != 0x00:  # 0이면 존재하지 않는 디바이스
                            state = 0 if dev_packet[0] == 0x04 else 1                            
                            temp_current = dev_packet[1]  # 현재 온도
                            temp_config = dev_packet[2]  # 설정 온도
                            result = {
                                'device': 'thermostat',
                                'room_index': idx + 1,
                                'state': state,
                                'temp_current': temp_current,
                                'temp_config': temp_config
                            }
                            self.sig_parse_result.emit(result)
                else:  # 상태 변경 명령 직후 응답
                    if packet[5] in [0x45, 0x46]:  # 0x46: On/Off 설정 변경에 대한 응답, 0x45: 온도 설정 변경에 대한 응답
                        state = 0 if packet[8] == 0x04 else 1
                        temp_current = packet[9]  # 현재 온도
                        temp_config = packet[10]  # 설정 온도
                        result = {
                            'device': 'thermostat',
                            'room_index': room_idx,
                            'state': state,
                            'temp_current': temp_current,
                            'temp_config': temp_config
                        }
                        self.sig_parse_result.emit(result)
        else:
            if packet[4] == 0x02:
                print(self.prettifyPacket(packet))

 Hi-oT 앱으로 환기 가동/중지 명령을 내리면서 캡쳐된 패킷은 다음과 같다


F7 0B 01 2B 02 40 11 01 00 84 EE : 환기 시작
F7 0B 01 2B 02 40 11 02 00 87 EE : 환기 종료


네번째 바이트가 0x2B인 패킷이 환기와 연동되어 있는 것을 알 수 있다

7번째 바이트는 다른 기기들의 경우 하위4비트가 공간 인덱스를 나타냈는데, 환기의 경우 공간 인덱스가 1로 거실인 것을 알 수 있다 (실제 Hi-oT 앱에도 거실에 '환기'가 배치되어 있다)

 

조명, 아울렛과 마찬가지로 8번째 바이트가 0x01이면 ON, 0x02이면 OFF 상태를 의미하고 있다

앱을 통해 세단계(약,중,강)로 풍량을 조절할 수 있기에, 각 단계별로 조정하면서 발생하는 패킷을 캡쳐한 결과는 다음과 같다


F7 0B 01 2B 02 42 11 01 00 86 EE : 풍량  설정
F7 0B 01 2B 02 42 11 03 00 84 EE : 중량  설정
F7 0B 01 2B 02 42 11 07 00 80 EE : 중량  설정


풍량 설정 시 6번째 바이트는 0x42가 되며, 8번째 바이트가 0x01, 0x03, 0x07이면 각각 풍량이 약, 중, 강이 된다

각 값을 이진법으로 표시해보면

  • 0000 0001 : 약
  • 0000 0011 : 중
  • 0000 0111 : 강

풍량이 LSB 3개 비트를 사용해서 상당히 직관적으로 인코딩되어있다 ㅋㅋ

난방 때도 온도가 정수형으로 1바이트만 사용했고, 환기 풍량은 3비트만 사용하고..

개발자 입장에서 분석하고 구현하기 상당히 간편하다!

2. 쿼리 및 응답 패킷 분석

4번째 바이트가 0x2B인 패킷을 전부 캡쳐해서 분석해보자


F7 0B 01 2B 01 40 11 00 00 86 EE : 평소 쿼리
F7 0C 01 2B 04 40 11 00 02 00 86 EE : 평소 응답 (환기가 꺼져있을 경우)
F7 0B 01 2B 02 40 11 01 00 84 EE : 가동 명령
F7 0C 01 2B 04 40 11 01 01 01 85 EE : 가동 명령에 대한 응답 (풍량 = '약')

F7 0B 01 2B 01 40 11 00 00 86 EE
F7 0C 01 2B 04 40 11 00 01 01 84 EE : 평소 응답 (환기 가동중, 풍량 = '약')

F7 0B 01 2B 02 42 11 03 00 84 EE : 풍량 '중' 변경 명령
F7 0C 01 2B 04 42 11 03 01 03 87 EE : 풍량 변경에 대한 응답 (풍량 = '중')

F7 0B 01 2B 02 42 11 07 00 80 EE : 풍량 '강' 변경 명령
F7 0C 01 2B 04 42 11 07 01 07 87 EE : 풍량 변경에 대한 응답 (풍량 = '강')

F7 0B 01 2B 01 40 11 00 00 86 EE : 평소 쿼리
F7 0C 01 2B 04 40 11 00 01 07 82 EE : 평소 응답 (환기 가동중, 풍량 = '강')


패킷 명세를 정리해보자

  • 4번째 바이트가 0x2B이면 환기(전열교환기) 관련 패킷
  • 5번째 바이트가 0x01이면 쿼리, 0x02이면 명령, 0x04이면 응답 패킷
  • 6번째 바이트가 0x40이면 평소 쿼리/응답 혹은 가동 시작/종료 명령, 0x42이면 풍량 변경 명령 및 응답 패킷
  • 풍량 변경 명령 시, 8번째 바이트의 하위 3비트에 따라 풍량이 3단계로 조절됨
    0x01 = '약', 0x03 = '중', 0x07 = '강'
  • 평소 응답 시 9번째 바이트가 0x02이면 가동 중지, 0x01이면 가동 중 상태
    10번째 바이트는 풍량을 나타내며, 가동 중지 상태일 경우 값은 0이고 가동 중일 경우 풍량은 0x01, 0x03, 0x07 세 값 중 하나
  • 가동, 풍량 변경 명령에 대한 응답 패킷은 평소 응답 패킷과 8번째 바이트값을 제외하고는 모두 동일하다

시리얼 패킷 파서, 쿼리/명령 패킷 생성 구문 모두 다른 기기들의 코드를 90% 이상 재활용할 수 있으므로 금방 구현할 수 있을 것 같다

반응형
Comments