YOGYUI

힐스테이트 광교산::조명 제어 RS-485 패킷 분석 (2) 본문

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

힐스테이트 광교산::조명 제어 RS-485 패킷 분석 (2)

요겨 2022. 6. 12. 11:08
반응형

5. 외부 제어 (앱) 설치

외부에서 홈네트워크 명령을 주는 용도로 사용하기 위해 SMARTHOME Hi-OT (힐스테이트 스마트홈) 앱을 설치했다

스마트폰 앱으로 제어 연동할 수 있는건 나름 노력해서 잘 만든것 같은데, 단점이 한두개가 아닌 것 같다 ㅋㅋ

  • 방 이름 변경을 할 수가 없다 (설정 창에서 방이름 변경 시 키보드 레이아웃이 안뜬다... 블루투스 키보드 사용자만 변경하라는건가? ㅋㅋㅋ
  • 제어 반응성이 썩 좋지않다 (변경사항이 적용될 때까지 메시지박스가 서버로부터 응답을 기다리는 동안 다른 작업을 할 수가 없다)

아무래도 안드로이드랑 아이폰 둘 다 동일한 인터페이스로 만들다보니 발생한 문제같은데... (UI가 iOS에 특화된 컨트롤들이 아니다) 기업 입장에서야 인건비 아끼는건 뭐라 할게 아니지만 사용자 입장에서 성의가 없어보이는건 어쩔 수 없다 ㅎㅎ

 

어쨌든 외부에서 제어 명령 내렸을 때 패킷 후킹용으로는 적당한 것 같다

 

6. 제어 명령 후 패킷 변경 내용 확인

컴퓨터방(방3)의 조명을 켜고 끄는 명령을 앱을 통해 내렸을 때 다음과 같은 변경사항을 확인했다


[평상시]

F7 0B 01 19 01 40 40 00 00 E5 EE

[조명 켜는 명령 시]

F7 0B 01 19 02 40 41 01 00 E6 EE

[조명 끄는 명령 시]

F7 0B 01 19 02 40 41 02 00 E5 EE


오호라... 이제서야 감이 확실하게 잡힌다

3, 4번째 바이트가 [0x01, 0x19]인 패킷의 5번째 패킷이 0x01일 경우 현재 상태를 쿼리하게 되며, 0x02일 경우가 상태를 바꾸는 명령이 된다

 

동일하게 각 방별로 상태를 바꾸면서 패킷을 모아보자

장소 명령 패킷
거실 평상시 (쿼리) F7 0B 01 19 01 40 10 00 00 B5 EE
조명 1 ON F7 0B 01 19 02 40 11 01 00 B6 EE
조명 1 OFF F7 0B 01 19 02 40 11 02 00 B5 EE
조명 2 ON F7 0B 01 19 02 40 12 01 00 B5 EE
조명 2 OFF F7 0B 01 19 02 40 12 02 00 B6 EE
조명 3 ON F7 0B 01 19 02 40 13 01 00 B4 EE
조명 3 OFF F7 0B 01 19 02 40 13 02 00 B7 EE
조명 모두 ON F7 0B 01 19 02 40 11 01 00 B6 EE
F7 0B 01 19 02 40 12 01 00 B5 EE
F7 0B 01 19 02 40 13 01 00 B4 EE
조명 모두 OFF F7 0B 01 19 02 40 11 02 00 B5 EE
F7 0B 01 19 02 40 12 02 00 B6 EE
F7 0B 01 19 02 40 13 02 00 B7 EE
침실(방1) 평상시 (쿼리) F7 0B 01 19 01 40 20 00 00 85 EE
조명 1 ON F7 0B 01 19 02 40 21 01 00 86 EE
조명 1 OFF F7 0B 01 19 02 40 21 02 00 85 EE
조명 2 ON F7 0B 01 19 02 40 22 01 00 85 EE
조명 2 OFF F7 0B 01 19 02 40 22 02 00 86 EE
조명 모두 ON F7 0B 01 19 02 40 21 01 00 86 EE
F7 0B 01 19 02 40 22 01 00 85 EE
조명 모두 OFF F7 0B 01 19 02 40 21 02 00 85 EE
F7 0B 01 19 02 40 22 02 00 86 EE
서재(방2) 평상시 (쿼리) F7 0B 01 19 01 40 30 00 00 95 EE
조명 ON F7 0B 01 19 02 40 31 01 00 96 EE
조명 OFF F7 0B 01 19 02 40 31 02 00 95 EE
컴퓨터방(방3) 평상시 (쿼리) F7 0B 01 19 01 40 40 00 00 E5 EE
조명 ON F7 0B 01 19 02 40 41 01 00 E6 EE
조명 OFF F7 0B 01 19 02 40 41 02 00 E5 EE
주방 평상시 (쿼리) F7 0B 01 19 01 40 60 00 00 C5 EE
조명 1 ON F7 0B 01 19 02 40 61 01 00 C6 EE
조명 1 OFF F7 0B 01 19 02 40 61 02 00 C5 EE
조명 2 ON F7 0B 01 19 02 40 62 01 00 C5 EE
조명 2 OFF F7 0B 01 19 02 40 62 02 00 C6 EE
조명 모두 ON F7 0B 01 19 02 40 61 01 00 C6 EE
F7 0B 01 19 02 40 62 01 00 C5 EE
조명 모두 OFF F7 0B 01 19 02 40 61 02 00 C5 EE
F7 0B 01 19 02 40 62 02 00 C6 EE
  • 3,4번 바이트가 [0x01, 0x19]일 때, 5번째 바이트가 0x02이면 조명 상태를 변경하는 명령이 된다
  • 7번째 바이트의 상위 4비트는 장소 인덱스가 된다
    0x1□ = 거실, 0x2□ = 침실(방1), 0x3□ = 서재(방2), 0x4□ = 컴퓨터방(방3), 0x6□ = 주방
  • 7번째 바이트의 하위 4비트는 제어하고자 하는 전등의 인덱스가 된다
    0x□1 = 첫번째 전등, 0x□2 = 두번째 전등, 0x□3 = 세번째 전등
  • 조명 일괄 ON/OFF에 대한 별도의 명령은 없고, 각 조명별 제어 명령을 순차적으로 전송할 뿐이다
  • 8번째 바이트가 0x01이면 조명을 켜는 명령, 0x02이면 조명을 끄는 명령이 된다

7. 체크섬 바이트 계산

패킷의 뒤에서 2번째 바이트값은 Xor 방식의 체크섬이 아닐까 생각되어 앞서 후킹한 패킷으로 계산해봤다 (XOR 계산 시작값을 0으로 설정)


Hexa Binary XOR Step Hexa Binary XOR Step Hexa Binary XOR Step
F7 1111 0111 1111 0111 F7 1111 0111 1111 0111 F7 1111 0111 1111 0111
0B 0000 1011 1111 1100 0B 0000 1011 1111 1100 0C 0000 1100 1111 1011
01 0000 0001 1111 1101 01 0000 0001 1111 1101 01 0000 0001 1111 1010
19 0001 1001 1110 0100 19 0001 1001 1110 0100 19 0001 1001 1110 0011
01 0000 0001 1110 0101 01 0000 0001 1110 0101 04 0000 0100 1110 0111
40 0100 0000 1010 0101 40 0100 0000 1010 0101 40 0100 0000 1010 0111
10 0001 0000 1011 0101 20 0010 0000 1000 0101 60 0110 0000 1100 0111
00 0000 0000 1011 0101 00 0000 0000 1000 0101 00 0000 0000 1100 0111
00 0000 0000 1011 0101 00 0000 0000 1000 0101 02 0000 0010 1100 0101
B5 1011 0101   85 1000 0101   02 0000 0020 1100 0111
EE 1110 1110   EE 1110 1110   C7 1100 0111  
            EE 1110 1110  

XOR Sum 방식으로 계산하다보면, 패킷의 시작 바이트 (0xF7)부터 순차적으로 계산한 값이 패킷의 뒤에서 두번째 패킷과 일치하는 것을 알 수 있다

 

여러 패킷들에 대한 계산 결과를 자동으로 판별할 수 있도록 테스트코드를 짜보자

from functools import reduce

packet_string_list = [
    'F7 0B 01 19 01 40 10 00 00 B5 EE',
    'F7 0B 01 19 02 40 11 01 00 B6 EE',
    'F7 0B 01 19 02 40 12 01 00 B5 EE',
    'F7 0B 01 19 01 40 20 00 00 85 EE',
    'F7 0B 01 19 01 40 30 00 00 95 EE',
    'F7 0B 01 19 01 40 40 00 00 E5 EE',
    'F7 0B 01 19 01 40 60 00 00 C5 EE',
    'F7 0C 01 19 04 40 60 00 02 02 C7 EE',
    'F7 0D 01 19 04 40 10 00 01 01 01 B7 EE',
    'F7 0B 01 19 04 40 40 00 02 E2 EE',
    'F7 0B 01 1F 01 40 60 00 00 C3 EE',
    'F7 1C 01 1F 04 40 60 00 61 01 00 00 00 00 00 00 02 62 01 00 00 00 00 00 00 02 D2 EE'
]

def convert(byte_str: str):
    return bytearray([int(x, 16) for x in byte_str.split(' ')])

packets = [convert(x)for x in packet_string_list]

def calc(packet: bytearray):
    checksum_in_packet = packet[-2]
    checksum_calc = reduce(lambda x, y: x ^ y, packet[:-2], 0)
    print('checksum_in_packet: %02X, checksum_calc: %02X' % (checksum_in_packet, checksum_calc))

for p in packets:
    calc(p)
checksum_in_packet: B5, checksum_calc: B5
checksum_in_packet: B6, checksum_calc: B6
checksum_in_packet: B5, checksum_calc: B5
checksum_in_packet: 85, checksum_calc: 85
checksum_in_packet: 95, checksum_calc: 95
checksum_in_packet: E5, checksum_calc: E5
checksum_in_packet: C5, checksum_calc: C5
checksum_in_packet: C7, checksum_calc: C7
checksum_in_packet: B7, checksum_calc: B7
checksum_in_packet: E2, checksum_calc: E2
checksum_in_packet: C3, checksum_calc: C3
checksum_in_packet: D2, checksum_calc: D2

체크섬 구하는 방법도 알았으니, 이제 조명 관련 패킷 파싱 및 명령 패킷 생성하는 코드를 작성할 준비가 완료됐다!

8. 주의사항

지난 글에서 조명 상태 응답 패킷은 다음과 같은 형태를 가지는 것을 알 수 있었다


[주방 조명 2개 모두 OFF시]

F7 0C 01 19 04 40 60 00 02 02 C7 EE

[주방 1번만 조명 ON시]

F7 0C 01 19 04 40 60 00 01 02 C4 EE

[주방 2번만 조명 ON시]

F7 0C 01 19 04 40 60 00 02 01 C4 EE

[주방 조명 모두 ON시]

F7 0C 01 19 04 40 60 00 01 01 C7 EE


그런데, 상태 명령을 내린 직후 약간 다른 포맷의 응답 패킷이 날아오는 것을 확인할 수 있었다


[주방 1번 조명 ON 명령 직후]

F7 0B 01 19 04 40 61 01 01 C1 EE

[주방 2번 조명 ON 명령 직후]

F7 0B 01 19 04 40 62 02 02 C2 EE


즉, 3~4번째 바이트가 [0x01, 0x19]인 패킷의 7번째 바이트의 상위 4비트는 장소 인덱스이며, 하위 4비트는 디바이스(조명) 인덱스가 된다

이 때, 하위 4비트 디바이스 인덱스가 0일 경우 장착된 모든 디바이스의 상태 정보를 담고 있지만, 인덱스가 0이 아닐 경우 특정 디바이스의 상태 정보만을 담고 있으므로 전체 패킷의 길이가 달라지게 된다!

파서 구현 코딩할 때 반드시 참고해야 한다 

(단순히 패킷 길이만으로 판단하면 out of index 예외가 발생하더라...)

9. 정리

조명 관련 RS485 패킷을 해석하는 방법과, 쿼리 패킷 및 On/Off 명령 패킷에 대한 정보를 모두 알아냈다

광교아이파크때와 유사하게, Flask를 사용해서 웹서버를 구축하여 이벤트 루프로 구동하여 홈네트워크 플랫폼(홈킷 + 구글 어시스턴트)와 연동할 수 있도록 구현해나갈 계획

  • 어차피 MQTT로 Homebridge 및 Home Assistant랑 연동할 계획이니, 기존 코드를 최대한 재활용
  • 코드는 다른 디바이스(아울렛, 환기, 냉/난방 등) 제어를 추가하면서 계속 수정해나갈 계획

※ 아이파크 Bestin때와는 달리 체크섬을 간단하게 구할 수 있어서 너무 좋다 ㅎㅎ

베스틴할때는 체크섬 공식을 끝내 알아내지 못해서 명령 패킷을 모두 모아야하는 번거로움이 있었다..

 

[시리즈]

힐스테이트 광교산::조명 제어 RS-485 패킷 분석 (1)

힐스테이트 광교산::조명 제어 RS-485 패킷 분석 (2)

반응형