YOGYUI

현대통신 월패드 새로운 난방 패킷 유형 발견 및 코드 적용(깃허브) 본문

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

현대통신 월패드 새로운 난방 패킷 유형 발견 및 코드 적용(깃허브)

요겨 2024. 3. 10. 18:12
반응형

Hyundai HT - Apply New Thermostat Packet Type

이번 주 금요일 (3월 8일), 내가 작성한 현대통신 월패드 RS-485 연동 코드를 사용하는 유저 한분으로부터 메일을 받았다 (이전에 HA 환경설정을 처음부터 도와드렸던 분)

 

메일 이력을 뒤져보니 난방이 제대로 되지 않는다고 이슈를 제기하셨는데, 내가 다른 프로젝트때문에 시간을 내기가 힘든 상황이라 스스로 분석해보시겠다고 하셨는데 그 결과를 공유해주신 것이었다

 

메일에 첨부된 사진을 살펴보니 한눈에 무엇이 문제였는지 파악할 수 있었다 (꼼꼼하게 잘 정리하셨다 wow)


1. 문제점 파악

현재 거주중인 힐스테이의 광교산 홈네트워크와 '쿼리에 대한 응답패킷'의 형식이 상이했다

힐스테이트 광교산::난방 제어 RS-485 패킷 분석

 

힐스테이트 광교산::난방 제어 RS-485 패킷 분석

지난번 도시가스차단기(밸브)쪽 포트의 RS-485 패킷 분석 시 (링크), 4번째 바이트가 0x1B인 패킷은 가스밸브와 관련된 패킷인 것을 알아냈다 그 외에도 4번째 바이트가 0x18, 0x1C, 0x2A, 0x2B, 0x34, 0x48인

yogyui.tistory.com

 

[힐스테이트 광교산 패킷 예시]
F7 0B 01 18 01 46 10 00 00 B2 EE : 평소 쿼리 패킷
F7 22 01 18 04 46 10 00 04 19 15 04 19 17 04 19 16 04 1A 12 00 00 00 00 00 00 00 00 00 00 00 00 9B EE : 평소 응답 패킷

평소 쿼리에 대한 응답 패킷은 총 길이 34바이트 (=0x22)로, 8번째 바이트부터 3바이트 단위로 난방 기기 1개의 정보를 담고 있다 (거실, 침실1, 침실2, 침실3 총 4개방에 대한 난방 정보가 하나의 패킷에 모두 포함됨)

34 바이트 = 10 + 3 × 최대 난방기 개수

난방 기기 1개당 첫번째 바이트는 난방 On/Off 여부 (0x04 = OFF, 0x01=ON), 두번째 바이트는 현재 온도, 세번째 바이트는 설정 온도로 해석을 완료했었다

[메일 주신분의 패킷 예시]
F7 4A 01 18 04 46 10 00 01 16 16 FF FF FF FF 00 01 16 17 FF FF FF FF 00 01 15 18 FF FF FF FF 00 01 16 17 FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FB EE: 평소 응답 패킷

메일 주신 분의 평소 응답 패킷은 총 길이 74바이트 (=0x4A)로, 8번째 바이트부터 8바이트 단위로 난방 기기 1개의 정보를 담고 있는 것으로 파악되었다 (이 분도 총 4대의 난방기기가 설치된 것으로 보인다)

74 바이트 = 10 + 8 × 최대 난방기 개수

※ 힐스테이트 광교산과 마찬가지로 최대 난방기 개수는 8로 고정이 되어 있는듯? 왠만큼 넓은 집이 아니고서는 난방기 8대를 초과해서 설치할 리가 없다는 계산하에 설계된 패킷 명세인건가? ㅋㅋ

힐스테이트 광교산과 마찬가지로 각 난방기 패킷의 첫번째 바이트는 On/Off 여부, 두번째 바이트는 현재 온도, 세번째 바이트는 설정 온도로 패킷의 의미가 동일하지 않을까 추측이 가능 (나머지 5바이트는 뭘 의미하는지 알 수가 없다 ~.,~ 그냥 FF 아니면 00으로 채워둔 걸 보면 예약 혹은 가동 시간 등의 기능을 위해 비워두지 않았을까...라는 추측만 해본다)

결론

환경에 따라 현대통신의 난방 쿼리에 대한 응답 패킷의 각 난방기별 패킷은 3바이트로 고정이 된 것이 아니라 8바이트 등 가변 길이를 가진다

※ 명령에 대한 응답 패킷은 구조가 동일한 것으로 판단됨

2. 솔루션

솔루션이 적용된 코드를 깃허브에 커밋했다

https://github.com/YOGYUI/HomeNetwork/commit/fae44918dc85cec4b987fbbb3466fa7ef351b44e

 

난방 노멀 쿼리 패킷 길이 다른 환경에 대한 대응 (thermo_len_per_dev) · YOGYUI/HomeNetwork@fae4491

YOGYUI committed Mar 8, 2024

github.com

 

변경 사항이 무엇인지 간단하게 살펴보자

2.1. PacketParser.py

class PacketParser:
    thermo_len_per_dev: int = 3  # 난방 노멀 쿼리 > 각 기기당 바이트 수는 3 혹은 8?
    
    def interpretPacket(self, packet: bytearray):
        if packet[3] == 0x18:  # 난방
            self.handleThermostat(packet)
    
    def handleThermostat(self, packet: bytearray):
        room_idx = packet[6] & 0x0F
        if packet[4] == 0x01:  # 상태 쿼리
            pass
        elif packet[4] == 0x02:  # On/Off, 온도 변경 명령
            pass
        elif packet[4] == 0x04:  # 상태 응답
            if room_idx == 0:  # 일반 쿼리 (존재하는 모든 디바이스)
                thermostat_count = (len(packet) - 10) // self.thermo_len_per_dev
                for idx in range(thermostat_count):
                    dev_packet = packet[8 + idx * self.thermo_len_per_dev: 8 + (idx + 1) * self.thermo_len_per_dev]
                    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': DeviceType.THERMOSTAT,
                            'room_index': idx + 1,
                            'state': state,
                            'temp_current': temp_current,
                            'temp_config': temp_config
                        }
                        self.updateDeviceState(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': DeviceType.THERMOSTAT,
                        'room_index': room_idx,
                        'state': state,
                        'temp_current': temp_current,
                        'temp_config': temp_config
                    }
                    self.updateDeviceState(result)

패킷 파서 클래스 내부적으로 헤더 바이트를 읽어 기기 종류별로 패킷을 해석하게 구현해뒀는데, 난방 관련 패킷을 해석할 때 평소 쿼리에 대한 응답 패킷의 경우 객체 내부 변수 thermo_len_per_dev 를 사용해 변수값만큼 패킷을 나눠 해석할 수 있게 수정했다 (기존에는 3으로 하드코딩된 값이었음)

2.2. Home.py

class Home:
    def loadRS485Config(self, node: ET.Element):
        self.rs485_info_list.clear()
        for cnode in list(node):
            try:
                thermo_len_per_dev = int(cnode.find('thermo_len_per_dev').text)
            except Exception as e:
                writeLog(f"Failed to read <thermo_len_per_dev> node ({e})", self)
                thermo_len_per_dev = 3
            parser = PacketParser(rs485, name, index, ParserType(packettype))
            parser.thermo_len_per_dev = thermo_len_per_dev

패킷 파서 객체 (PacketParser) 생성 시에 thermo_len_per_dev 변수를 설정할 수 있게 코드를 한 줄 추가했으며, 해당 변수값은 config.xml 파일에서 불러오도록 구현했다

2.3. Config.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<config>
    <rs485>
        <reconnect_limit>30</reconnect_limit>
        <port>
            <name>port1</name>
            <index>0</index>
            <enable>0</enable>
            <hwtype>0</hwtype>  
            <packettype>0</packettype>
            <usb2serial>
                <port>/dev/ttyUSB0</port>
                <baud>9600</baud>
                <databit>8</databit>
                <parity>N</parity>
                <stopbits>1</stopbits>
            </usb2serial>
            <ew11>
                <ipaddr>127.0.0.1</ipaddr>
                <port>8899</port>
            </ew11>
            <check>1</check>
            <buffsize>64</buffsize>
            <thermo_len_per_dev>3</thermo_len_per_dev>
        </port>
    </rs485>
</config>

RS-485 컨버터 관련 설정 항목은 <rs485> 태그 내부에 <thermo_len_per_dev> 태그를 추가해, Home 객체 초기화 시 PacketParser 클래스 생성하면서 이 값을 적용할 수 있게 만들었다


내 코드가 알음알음 그 존재가 세상에 알려지면서(?) 다양한 홈네트워크 환경에 대응하기 위해 코드가 점점 누더기가 되어가고 있다 (오픈소스로 프로젝트를 시작한 이상 어쩔 수 없는 일이긴 하지만, 예상보다 많은 유저들에 의해 사용되고 있다..)

어설픈 변경같긴 하지만, 내가 혼자 쓰려고 만들었을 땐 꽤나 코드가 깔끔했다고 자평한다 ㅋㅋㅋ

 

 

몇몇 분들의 요청에 따라 Home Assistant의 애드온으로 제작하는 프로젝트를 간간히 진행하고 있긴 한데 (속도가 많이 느리다...), 정식으로 릴리즈하기 전에 이런 예외사항들이 많이 접수되면 그만큼 코드가 유연해지는 것이니 매우 좋은 현상이라 할 수 있겠다 

반응형
Comments