일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 국내주식
- Home Assistant
- 코스피
- 파이썬
- 나스닥
- 배당
- 해외주식
- 공모주
- ConnectedHomeIP
- Bestin
- 오블완
- 월패드
- esp32
- Espressif
- homebridge
- 매터
- matter
- 애플
- 홈네트워크
- 티스토리챌린지
- MQTT
- 힐스테이트 광교산
- cluster
- RS-485
- Apple
- 현대통신
- raspberry pi
- SK텔레콤
- Python
- 미국주식
- Today
- Total
YOGYUI
힐스테이트 광교산::엘리베이터 - 애플 홈킷 + 구글 어시스턴트 연동 본문
지난 포스트에서 엘리베이터 호출 관련 RS-485 패킷 후킹 및 분석을 완료했다(링크)
python 시리얼 패킷 파서 및 MQTT 메시지 핸들러 구문을 구현하고, Homebridge 및 Home Assistant 액세서를 추가해서 홈네트워크 플랫폼과 연동을 시켜보자
1. python 코드 작성
깃헙 저장소 hillstate-elevator 브랜치로 소스코드 커밋 완료
https://github.com/YOGYUI/HomeNetwork/tree/hillstate-elevator
시리얼 패킷 파서 구문은 지난번 패킷 후킹때 구현했던 것과 크게 차이가 없다
class ParserVarious(SerialParser):
def interpretPacket(self, packet: bytearray):
try:
if packet[3] == 0x18: # 난방
self.handleThermostat(packet)
elif packet[3] == 0x1B: # 가스차단기
self.handleGasValve(packet)
elif packet[3] == 0x1C: # 시스템에어컨
self.handleAirconditioner(packet)
elif packet[3] == 0x2B: # 환기 (전열교환기)
self.handleVentilator(packet)
elif packet[3] == 0x34: # 엘리베이터
self.handleElevator(packet)
else:
if packet[4] == 0x02:
print(self.prettifyPacket(packet))
except Exception as e:
writeLog('interpretPacket::Exception::{} ({})'.format(e, packet), self)
def handleElevator(self, packet: bytearray):
if packet[4] == 0x01: # 상태 쿼리 (월패드 -> 복도 미니패드)
state = packet[8] & 0x0F # 0 = idle, 1 = arrived, 5 = moving(up), 6 = moving(down)
elevator_index = (packet[8] & 0xF0) >> 4 # 0x0A or 0x0B
floor = ['??', '??']
if elevator_index == 0x0A:
floor[0] = '{:02X}'.format(packet[9])
elif elevator_index == 0x0B:
floor[1] = '{:02X}'.format(packet[9])
result = {
'device': 'elevator',
'state': state,
'floor': floor
}
self.sig_parse_result.emit(result)
elif packet[4] == 0x02:
pass
elif packet[4] == 0x04: # 상태 응답 (복도 미니패드 -> 월패드)
state = packet[8] & 0x0F # 0 = idle, 1 = arrived, 5 = moving(up), 6 = moving(down)
result = {
'device': 'elevator',
'state': state
}
self.sig_parse_result.emit(result)
엘리베이터의 상태(state)는 총 4가지로 나뉘게 되며, 각 값에 따른 의미는 다음과 같다
- 0: IDLE, 호출되지 않은 상태
- 1: ARRIVED, 도착한 상태
- 5: MOVING, 하행 호출 후 이동중인 상태
- 6: MOVING, 상행 호출 후 이동중인 상태
이 값들은 패킷 9번째 바이트의 하위 4비트값을 토대로 추출한 값이며, 이를 그대로 homebridge와 home assistant 액세서리들에도 적용하도록 하자 (따로 인코딩하기 귀찮고 헷갈린다)
엘리베이터 관련 객체는 다음과 같이 구현해봤다
class Elevator(Device):
time_arrived: float = 0.
time_threshold_arrived_change: float = 10.
floor_list: List[str]
moving_list: List[bool]
def __init__(self, name: str = 'Elevator', count: int = 2, **kwargs):
super().__init__(name, **kwargs)
self.floor_list = ['??'] * count
self.moving_list = [False] * count
def __repr__(self):
repr_txt = f'<{self.name}({self.__class__.__name__} at {hex(id(self))})'
repr_txt += '>'
return repr_txt
def publish_mqtt(self):
obj = {"state": self.state}
if self.mqtt_client is not None:
self.mqtt_client.publish(self.mqtt_publish_topic, json.dumps(obj), 1)
def updateState(self, state: int, **kwargs):
self.state = state # 0 = idle, 1 = arrived, 5 = moving(up), 6 = moving(down)
if not self.init:
self.publish_mqtt()
self.init = True
if self.state != self.state_prev:
if self.state == 1:
self.time_arrived = time.perf_counter()
self.publish_mqtt()
self.state_prev = self.state
else:
if self.state_prev == 1:
# 도착 후 상태가 다시 idle로 바뀔 때 시간차가 적으면 occupancy sensor가 즉시 off가 되어
# notification이 제대로 되지 않는 문제가 있어, 상태 변화 딜레이를 줘야한다
time_elapsed_last_arrived = time.perf_counter() - self.time_arrived
if time_elapsed_last_arrived > self.time_threshold_arrived_change:
self.publish_mqtt()
self.state_prev = self.state
else:
self.publish_mqtt()
self.state_prev = self.state
if 'floor' in kwargs.keys():
floor = kwargs.get('floor')
try:
for i in range(len(self.floor_list)):
if floor[i] != '??':
self.floor_list[i] = floor[i]
except Exception:
pass
def makePacketCallDownside(self) -> bytearray:
# 하행 호출
# F7 0B 01 34 02 41 10 06 00 XX EE
packet = bytearray([0xF7, 0x0B, 0x01, 0x34])
packet.append(0x02)
packet.extend([0x41, 0x10, 0x06, 0x00])
packet.append(self.calcXORChecksum(packet))
packet.append(0xEE)
return packet
엘리베이터가 이동중일 때 현재 층수(floor)도 일단 구현은 해봤는데, 패킷에 나타나는 값이 의미하는걸 정확히 파악하지 못해서 딱히 특별한 용도로 사용하지는 않는다 ㅎㅎ
또한, 패킷 파서에 의해 엘리베이터가 도착(state=1) 후 1초도 되지 않아 IDLE(state=0)로 상태가 변하게 되는데, 즉시 상태를 변화시키면 도착 알림 센서를 트리거할 시간이 짧아 제대로 작동하지 않는 경우가 있어 도착 후 10초간 상태를 유지하도록 간단하게 트릭을 추가해줬다
class ThreadCommandQueue(threading.Thread):
_keepAlive: bool = True
def __init__(self, queue_: queue.Queue):
threading.Thread.__init__(self, name='Command Queue Thread')
self._queue = queue_
self._retry_cnt = 10
self._delay_response = 0.4
self.sig_terminated = Callback()
def run(self):
writeLog('Started', self)
while self._keepAlive:
if not self._queue.empty():
elem = self._queue.get()
elem_txt = '\n'
for k, v in elem.items():
elem_txt += f' {k}: {v}\n'
writeLog(f'Get Command Queue: \n{{{elem_txt}}}', self)
try:
dev = elem['device']
category = elem['category']
target = elem['target']
parser = elem['parser']
if target is None:
continue
if isinstance(dev, Light):
# 생략
elif isinstance(dev, Outlet):
# 생략
elif isinstance(dev, GasValve):
# 생략
elif isinstance(dev, Thermostat):
# 생략
elif isinstance(dev, Ventilator):
# 생략
elif isinstance(dev, AirConditioner):
# 생략
elif isinstance(dev, Elevator):
if category == 'state':
self.set_elevator_call(dev, target, parser)
except Exception as e:
writeLog(str(e), self)
else:
time.sleep(1e-3)
writeLog('Terminated', self)
self.sig_terminated.emit()
def stop(self):
self._keepAlive = False
def set_elevator_call(self, dev: Elevator, target: int, parser: SerialParser):
cnt = 0
if target == 5:
packet_command = dev.makePacketCallUpside()
elif target == 6:
packet_command = dev.makePacketCallDownside()
else:
return
while cnt < self._retry_cnt:
if dev.state == target:
break
if parser.isSerialLineBusy():
time.sleep(1e-3) # prevent cpu occupation
continue
parser.sendPacket(packet_command)
cnt += 1
time.sleep(0.2) # wait for parsing response
if cnt > 0:
writeLog('set_elevator_call({})::send # = {}'.format(target, cnt), self)
time.sleep(self._delay_response)
dev.publish_mqtt()
명령 큐 쓰레드에서는 엘리베이터 호출 관련 메서드를 따로 만들어주고, 목표 상태값(target)은 5 혹은 6으로 받을 수 있게 구현해줬다 (어차피 6=상행 호출은 작동하지도 않지만 ㅠ)
2. Homebridge, Home Assistant 액세서리 추가
광교아이파크때와 마찬가지로 호출용 switch, 그리고 도착을 알리기 위해 occupancy sensor 두 개의 액세서리를 활용하기로 했다 (나중에 좀 더 합리적인 아이템으로 바꾸는 방법을 찾아봐야겠다;;)
{
"accessory": "mqttthing",
"type": "switch",
"name": "Elevator Call Downside (MQTT)",
"url": "mosquitto broker url",
"username": "mosquitto auth id",
"password": "mosquitto auth password",
"topics": {
"getOn": {
"topic": "home/hillstate/elevator/state",
"apply": "return (JSON.parse(message).state == 6);"
},
"setOn": {
"topic": "home/hillstate/elevator/command",
"apply": "return JSON.stringify({state: 6});"
}
},
"integerValue": true,
"onValue": 1,
"offValue": 0,
"logMqtt": true
},
{
"accessory": "mqttthing",
"type": "occupancySensor",
"name": "Elevator Arrived (MQTT)",
"url": "mosquitto broker url",
"username": "mosquitto auth id",
"password": "mosquitto auth password",
"topics": {
"getOccupancyDetected": {
"topic": "home/hillstate/elevator/state",
"apply": "return (JSON.parse(message).state == 1);"
}
},
"integerValue": true,
"onValue": 1,
"offValue": 0,
"logMqtt": true
}
Home Assistant도 유사하게 추가해줬다 (안드로이드 기기는 거의 쓰지 않다보니 더 대충 구현해버렸다 ㅋㅋ)
HA 커뮤니티를 보면 엘리베이터가 도착하면 스피커(구글 홈 미니)로 도착했다는 음성 메시지를 호출하는 등 고급 기능들을 구현한 사람들이 많던데.. 나중에 시간내서 한번 따라해봐야겠다 ㅎㅎ
3. 작동 테스트
엘리베이터 호출(하행) - 도착 시 알림 모두 정상적으로 동작한다 (물론 Siri 음성명령도 잘 먹힌다!)
아이폰으로 호출을 하면 마치 복도의 제어패드에서 사람이 엘리베이터 호출 버튼을 누른 것과 같은 동작을 하는 것을 알 수 있다 (즉, 명령 패킷으로는 복도 제어패드가 토글되는 동작을 프로그래밍으로 구현할 수 있는 것을 알 수 있다)
이제 홈네트워크 연동 작업이 마무리단계다
아마 RS-485 포트 2개를 통해 연계할 수 있는 건 거의 다 한 느낌인데.. 월패드로 이것저것 건드려보면서 더 할만한게 남았나 살펴봐야겠다 (아직 4번째 바이트가 0x2A랑 0x48인 패킷의 정체도 남았다!)
[TODO]
조명 On/Off아울렛(전원 콘센트) On/Off - 실시간 전력량 조회는 불가능도시가스 차단난방 제어 (On/Off, 희망 온도 설정, 현재 방 온도 가져오기)환기 (전열교환기)시스템 에어컨 (냉방 및 공기청정)엘리베이터 호출- 도어락 해제
- Optional: 현관 비디오폰 영상, 거실 천장 모션 센서
'홈네트워크(IoT) > 힐스테이트 광교산' 카테고리의 다른 글
힐스테이트 광교산::다용도실 전등 스위치 - Aqara 스마트 조명 스위치 (1구)로 교체 (0) | 2022.06.28 |
---|---|
힐스테이트 광교산::현관 도어락 - 애플 홈킷 + 구글 어시스턴트 연동 (2) | 2022.06.26 |
힐스테이트 광교산::엘리베이터 호출 제어 RS-485 패킷 분석 (9) | 2022.06.23 |
힐스테이트 광교산::시스템에어컨 - 애플 홈킷 + 구글 어시스턴트 연동 (0) | 2022.06.20 |
힐스테이트 광교산::시스템에어컨 제어 RS-485 패킷 분석 (0) | 2022.06.19 |