YOGYUI

힐스테이트 광교산::난방/냉방 (반복) 타이머 기능 구현 본문

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

힐스테이트 광교산::난방/냉방 (반복) 타이머 기능 구현

요겨 2022. 12. 20. 17:23
반응형

 

저번 주말에 폭설이 오더니, 요 며칠사이 엄청 추워졌다 

글을 작성하고 있는 12월 17일 오전에도 눈이 내리고 있었다..

2022.12.17 오전 8시 - 눈 내리는 중

당분간 추위가 계속될 거라고 한다 ㅠ (추운게 더운것보다 훨씬 싫다)

https://www.delighti.co.kr/news/articleView.html?idxno=43181

 

[오늘날씨] 주말에도 냉동고...18일까지 서해안·제주에 폭설 - 딜라이트닷넷

[딜라이트닷넷 정호원 기자] 토요일인 17일 아침 최저기온이 영하 15도까지 떨어지는 등 동장군이 좀처럼 물러서지 않은 가운데 충남서해안과 전라권서부, 제주도를 중심으로 18일까지 매우 많은

www.delighti.co.kr

 

난방비가 급격하게 올랐다는 기사들을 접하면 난방을 켜는게 약간 두렵기도 한데...

https://imnews.imbc.com/replay/2022/nwdesk/article/6435930_35744.html

 

'난방비 폭탄' 걱정‥올해만 38% 인상에 혹독한 겨울나기

길고 매서운 추위, 올겨울 서민들에게는 한파만큼 무서운 게 난방비입니다. 난방과 온수 요금이 지난 겨울보다 줄잡아 3~40% 정도 더 올랐습니다. 임상재 기자입니다. ...

imnews.imbc.com

난방을 하긴 해야겠다... 집안 실내 온도가 섭씨 20~21도밖에 안되니 집안에서 돌아다니는 것도 힘들다

(산 중턱에서의 삶의 비애지 뭐 ㅋㅋ)

어차피 관리비 걱정할만큼 벌이가 적은 것도 아니다!


힐스테이트 광교산 카카오톡 오픈채팅방에도 난방 관련 이야기가 많이 오가고 있는데, 1시간마다 10분씩만 난방을 가동하고자 하는데 방법을 모르겠다는 글이 보였다

내가 알기로 이런 반복적 타이머 기능은 월패드나 힐스테이트 홈네트워크 원격 제어 앱인 Hi-oT, 애플 홈, 구글 어시스턴트 등 접근할 수 있는 홈네트워크 플랫폼들에서는 유연하게 구성하기 힘들기 때문에, 라즈베리파이에서 구동되는 RS485 제어 코드 (파이썬)에 직접 구현하기로 했다

프로그래밍은 삶의 질을 직접적으로 향상시킬 수 있는 가장 좋은 도구다

[목표]
일정 시간동안 디바이스(난방/냉방 등)를 제어하는 타이머 기능 구현
On time, Off time을 설정해 켜고 끄는 동작을 반복할 수 있도록 해야 한다

1. 코딩 작업 (구현)

홈 네트워크에 소속된 모든 기기가 타이머 기능을 갖출 수 있는 부모 클래스 DeviceTimerOnOff라는 명칭으로 짜줬다 (Thermostat, Airconditioner 등 객체는 모두 Device 클래스를 상속)

class Device:
    thread_timer_onoff = None
    timer_onoff_params: dict
    
    def __init__(self, name: str = 'Device', **kwargs):
        self.timer_onoff_params = {
            'on_time': 10,  # unit: minute
            'off_time': 50,  # unit: minute
            'repeat': True,  # boolean
            'off_when_terminate': True  # device 켜진 상태에서 타이머 종료될 때 동작
        }
        if 'timer_onoff_ontime' in kwargs.keys():
            self.timer_onoff_params['on_time'] = kwargs.get('timer_onoff_ontime')
        if 'timer_onoff_offtime' in kwargs.keys():
            self.timer_onoff_params['off_time'] = kwargs.get('timer_onoff_offtime')
        if 'timer_onoff_repeat' in kwargs.keys():
            self.timer_onoff_params['repeat'] = kwargs.get('timer_onoff_repeat')
	
    def startTimerOnOff(self):
        self.startThreadTimerOnOff()

    def stopTimerOnOff(self):
        self.stopThreadTimerOnOff()

    def startThreadTimerOnOff(self):
        if self.thread_timer_onoff is None:
            self.thread_timer_onoff = ThreadDeviceTimerOnOff(self)
            self.thread_timer_onoff.setDaemon(True)
            self.thread_timer_onoff.sig_set_state.connect(self.sig_set_state.emit)
            self.thread_timer_onoff.sig_terminated.connect(self.onThreadTimerOnOffTerminated)
            on_time = self.timer_onoff_params['on_time']
            off_time = self.timer_onoff_params['off_time']
            repeat = self.timer_onoff_params['repeat']
            self.thread_timer_onoff.setParams(on_time, off_time, repeat)
            self.thread_timer_onoff.start()

    def stopThreadTimerOnOff(self):
        if self.thread_timer_onoff is not None:
            self.thread_timer_onoff.stop()

    def onThreadTimerOnOffTerminated(self):
        del self.thread_timer_onoff
        self.thread_timer_onoff = None
        if self.timer_onoff_params['off_when_terminate']:
            self.sig_set_state.emit(0)
        else:
            self.publish_mqtt()

    def isTimerOnOffRunning(self) -> bool:
        if self.thread_timer_onoff is not None:
            return self.thread_timer_onoff.is_alive()
        return False
    
    def setTimerOnOffParams(self, on_time: float, off_time: float, repeat: bool):
        self.timer_onoff_params['on_time'] = on_time
        self.timer_onoff_params['off_time'] = off_time
        self.timer_onoff_params['repeat'] = repeat
        if self.thread_timer_onoff is not None:
            self.thread_timer_onoff.setParams(on_time, off_time, repeat)

그리고 타이머 기능이 작동되는 쓰레드는 ThreadDeviceTimerOnOff라는 직관적인 이름으로 만들어줬다

(threading.Thread 상속)

class ThreadDeviceTimerOnOff(threading.Thread):
    _keepAlive: bool = True
    _dev: Device
    _on_time: float = 10  # unit: minute
    _off_time: float = 50  # unit: minute
    _repeat: bool = True

    def __init__(self, dev: Device):
        threading.Thread.__init__(self, name=f'Device({dev}) On/Off Timer Thread')
        self._dev = dev
        self._timer_interval = 1  # unit: second
        self.sig_terminated = Callback()
        self.sig_set_state = Callback(int)
    
    def run(self):
        writeLog(f'{self.name} Started', self)
        step = 0
        tm: float = 0.
        wait_for_transition: bool = False
        while self._keepAlive:
            if step == 0:
                wait_for_transition = True
                self.sig_set_state.emit(1)
                tm_wait_state = time.perf_counter()
                while wait_for_transition:
                    if self._dev.state == 1:
                        writeLog(f'{self._dev} state changed to {self._dev.state}', self)
                        wait_for_transition = False
                    if time.perf_counter() - tm_wait_state > 10:
                        writeLog('timeout! terminate timer on/off', self)
                        self._keepAlive = False
                        break
                    time.sleep(self._timer_interval)
                tm = time.perf_counter()
                step = 1
            elif step == 1:
                if time.perf_counter() - tm >= self._on_time * 60:
                    step = 2
            elif step == 2:
                wait_for_transition = True
                self.sig_set_state.emit(0)
                tm_wait_state = time.perf_counter()
                while wait_for_transition:
                    if self._dev.state == 0:
                        writeLog(f'{self._dev} state changed to {self._dev.state}', self)
                        wait_for_transition = False
                    if time.perf_counter() - tm_wait_state > 10:
                        writeLog('timeout! terminate timer on/off', self)
                        self._keepAlive = False
                        break
                    time.sleep(self._timer_interval)
                tm = time.perf_counter()
                step = 3
            elif step == 3:
                if time.perf_counter() - tm >= self._off_time * 60:
                    if self._repeat:
                        step = 0
                    else:
                        break
            time.sleep(self._timer_interval)
        writeLog(f'{self.name} Terminated', self)
        self.sig_terminated.emit()

    def stop(self):
        self._keepAlive = False

    def setParams(self, on_time: float, off_time: float, repeat: bool):
        self._on_time = on_time
        self._off_time = off_time
        self._repeat = repeat

코드 가독성을 높이기 위해 쓰레드 내부 동작 (run 메서드) 코드는 정말 직관적으로 구현했다

(드물게 내 코드를 읽으시는 분들이 계시더라는... 솔직히 이렇게 짜는게 훨씬 쉽긴 하다 ㅎㅎ)

  • step=0: 디바이스를 켜는 콜백 호출 (set state as 1) 후 디바이스의 현재 상태가 1이 될 때까지 대기
  • step=1: 설정된 On Time만큼 아무것도 하지 않는다
  • step=2: 디바이스를 끄는 콜백 호출 (set state as 0) 후 디바이스의 현재 상태가 0이 될 때까지 대기
  • step=3: 설정된 Off Time만큼 아무것도 하지 않는다, 그리고 반복(repeat)가 설정되어 있으면 다시 step을 0으로 만든다

요렇게 단계별로 '켜기' - '대기' - '끄기' - '대기' 를 반복하게 만들었으며, 외부 호출(stop 메서드)에 의해 쓰레드가 즉시 종료될 수 있게 해줬다

반복(repeat) 플래그를 0으로 설정하면 1번만 수행되니, 냉/난방 말고 다른 기기(조명, 전열교환기 등)들에도 유용하게 사용할 수 있는 기능일 것 같다

※ On Time과 Off Time은 10분, 50분을 default값으로 구현했는데, 나중에 MQTT를 통해서건, 웹 API를 통해서건 변경할 수 있게 추가해야겠다

 

그리고 Home 클래스는 내부에 소속된 Device들의 콜백을 다음과 같이 처리하게 구현했다

(일단은 난방과 냉방만 동작하게~)

class Home:
    device_list: List[Device]
    
    def initialize(self, init_service: bool, connect_rs485: bool):
        self.initialize(init_service, False)
    
    def initialize(self, init_service: bool, connect_rs485: bool):
        for dev in self.device_list:
            dev.sig_set_state.connect(partial(self.onDeviceSetState, dev))
    
    def onDeviceSetState(self, dev: Device, state: int):
        if isinstance(dev, AirConditioner):
            self.command(
                device=dev,
                category='active',
                target=state
            )
        elif isinstance(dev, Thermostat):
            self.command(
                device=dev,
                category='state',
                target='HEAT' if state else 'OFF'
            )

난방(Thermostat)과 냉방(Airconditioner)은 현재 On/Off 타이머가 동작중인지를 MQTT로 메시지 발행(publish)하는 구문만 추가해줬다 (홈네트워크 플랫폼에서 타이머가 동작중인지 여부를 알아야 한다)

class AirConditioner(Device):
    def publish_mqtt(self):
        if self.state:
            state = 'COOLING'
        else:
            state = 'INACTIVE'
        obj = {
            "active": self.state,
            "state": state,
            "currentTemperature": self.temp_current,
            "targetTemperature": self.temp_config,
            "timer": int(self.isTimerOnOffRunning())
        }
        if self.rotation_speed == 0x02:  # 미풍
            obj['rotationspeed'] = 50
            obj['rotationspeed_name'] = 'Min'
        elif self.rotation_speed == 0x03:  # 약풍
            obj['rotationspeed'] = 75
            obj['rotationspeed_name'] = 'Medium'
        elif self.rotation_speed == 0x04:  # 강풍
            obj['rotationspeed'] = 100
            obj['rotationspeed_name'] = 'Max'
        else:
            obj['rotationspeed'] = 25
            obj['rotationspeed_name'] = 'Auto'
        if self.mqtt_client is not None:
            self.mqtt_client.publish(self.mqtt_publish_topic, json.dumps(obj), 1)

class Thermostat(Device):
    def publish_mqtt(self):
        obj = {
            "state": 'HEAT' if self.state == 1 else 'OFF',
            "currentTemperature": self.temp_current, 
            "targetTemperature": self.temp_config,
            "timer": int(self.isTimerOnOffRunning())
        }
        if self.mqtt_client is not None:
            self.mqtt_client.publish(self.mqtt_publish_topic, json.dumps(obj), 1)

생각보다 손봐야될 코드가 많다

그래도 이정도면 모듈화 구현을 통해 유지보수가 상당히 빠르고 쉽다(고 자평해본다)

2. Homebridge 액세서리 추가

홈브릿지 MqttThing 플러그인의 switch 액세서리로 활용해보자

테스트를 위해 밤에 내가 자는 공간인 침실(방 인덱스=2)에만 냉방, 난방 타이머 액세서리를 추가해줬다

{
    "accessory": "mqttthing",
    "type": "switch",
    "name": "Bedroom AirConditioner Timer (MQTT)",
    "topics": {
        "getOn": {
            "topic": "home/hillstate/airconditioner/state/2",
            "apply": "return (JSON.parse(message).timer == 1);"
        },
        "setOn": {
            "topic": "home/hillstate/airconditioner/command/2",
            "apply": "return JSON.stringify({timer: message});"
        }
    },
    "integerValue": true,
    "onValue": 1,
    "offValue": 0,
    "logMqtt": false
},
{
    "accessory": "mqttthing",
    "type": "switch",
    "name": "Bedroom Thermostat Timer (MQTT)",
    "topics": {
        "getOn": {
            "topic": "home/hillstate/thermostat/state/2",
            "apply": "return (JSON.parse(message).timer == 1);"
        },
        "setOn": {
            "topic": "home/hillstate/thermostat/command/2",
            "apply": "return JSON.stringify({timer: message});"
        }
    },
    "integerValue": true,
    "onValue": 1,
    "offValue": 0,
    "logMqtt": false
}

 

MQTT 토픽(topic)은 

  • 난방: home/hillstate/thermostat/state/{방 인덱스}
  • 냉방: home/hillstate/airconditioner/state/{방 인덱스}

페이로드(payload)는 단순히 {timer: 1} 혹은 {timer: 0}으로 타이머를 켜고 끈다

액세서리 스크립트 추가 후 homebridge를 재시작해보면 타이머 액세서리 (스위치)를 애플 홈 앱에서 볼 수 있다

난방 타이머 스위치를 켜니 난방이 즉시 동작하기 시작했다~ Good!

하루종일 켜져있는 걸 방지하기 위해 매일 오전 7시 모든 난방과 난방 타이머 동작을 해제하는 자동화도 만들어주자

 

3. 테스트

16일 저녁에 영화 한편 조지고 자정 무렵부터 코딩을 시작해서 1시간 정도 걸려서 작업을 마무리했다

테스트를 위해 새벽 2시 무렵 애플 홈 앱에서 타이머를 켜고 잠자리에 들었고, 기상 후 라즈베리파이의 콘솔 로그를 확인해봤다


<02:08:01.844677> [Home] MQTT Message: home/hillstate/thermostat/command/2: {'timer': 1}
<02:08:01.845606> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> On/Off Timer Thread Started
<02:08:01.846065> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: HEAT
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<02:08:01.851787> [ParserVarious] Send >> F7 0B 01 18 02 46 12 01 00 B2 EE
<02:08:05.359135> [ThreadCommandQueue] set_state_common::send # = 35, elapsed = 3512.13 msec
<02:08:05.850553> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 1
<02:18:08.321300> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<02:18:08.322397> [ParserVarious] Send >> F7 0B 01 18 02 46 12 04 00 B7 EE
<02:18:08.522031> [ThreadCommandQueue] set_state_common::send # = 2, elapsed = 200.358 msec
<02:18:09.322035> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 0
<03:08:11.669890> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: HEAT
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<03:08:11.671172> [ParserVarious] Send >> F7 0B 01 18 02 46 12 01 00 B2 EE
<03:08:12.372871> [ThreadCommandQueue] set_state_common::send # = 7, elapsed = 702.522 msec
<03:08:12.670812> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 1
<03:18:15.147517> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<03:18:15.148947> [ParserVarious] Send >> F7 0B 01 18 02 46 12 04 00 B7 EE
<03:18:15.348403> [ThreadCommandQueue] set_state_common::send # = 2, elapsed = 200.533 msec
<03:18:16.148184> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 0
<04:08:18.401166> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: HEAT
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<04:08:18.402970> [ParserVarious] Send >> F7 0B 01 18 02 46 12 01 00 B2 EE
<04:08:19.400831> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 1
<04:08:19.404979> [ThreadCommandQueue] set_state_common::send # = 10, elapsed = 1003.16 msec
<04:18:21.888466> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<04:18:21.890100> [ParserVarious] Send >> F7 0B 01 18 02 46 12 04 00 B7 EE
<04:18:22.791491> [ThreadCommandQueue] set_state_common::send # = 9, elapsed = 902.532 msec
<04:18:22.888564> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 0
<05:08:25.168969> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: HEAT
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<05:08:25.170529> [ParserVarious] Send >> F7 0B 01 18 02 46 12 01 00 B2 EE
<05:08:26.169792> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 1
<05:08:26.171765> [ThreadCommandQueue] set_state_common::send # = 10, elapsed = 1002.48 msec
<05:18:28.640335> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<05:18:28.641546> [ParserVarious] Send >> F7 0B 01 18 02 46 12 04 00 B7 EE
<05:18:29.544230> [ThreadCommandQueue] set_state_common::send # = 9, elapsed = 903.559 msec
<05:18:29.639822> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 0
<06:08:31.991177> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: HEAT
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<06:08:31.992956> [ParserVarious] Send >> F7 0B 01 18 02 46 12 01 00 B2 EE
<06:08:32.192356> [ThreadCommandQueue] set_state_common::send # = 2, elapsed = 200.601 msec
<06:08:32.991273> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 1
<06:11:41.415318> [ParserVarious] Timestamp Packet: F7 14 01 44 0C 62 00 00 16 0C 11 06 0B 27 01 01 0A 00 E3 EE
<06:18:35.436598> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<06:18:35.438306> [ParserVarious] Send >> F7 0B 01 18 02 46 12 04 00 B7 EE
<06:18:36.739934> [ThreadCommandQueue] set_state_common::send # = 13, elapsed = 1302.9 msec
<06:18:37.437668> [ThreadDeviceTimerOnOff] <Thermostat Room Idx: 2> state changed to 0
<07:00:02.519101> [Home] MQTT Message: home/hillstate/thermostat/command/4: {'state': 'OFF'}
<07:00:02.519763> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 4>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.520191> [Home] MQTT Message: home/hillstate/thermostat/command/4: {'targetTemperature': 30}
<07:00:02.530328> [Home] MQTT Message: home/hillstate/thermostat/command/2: {'timer': 0}
<07:00:02.530881> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 4>
  category: temperature
  target: 30
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.538440> [Home] MQTT Message: home/hillstate/thermostat/command/2: {'targetTemperature': 25}
<07:00:02.539508> [Home] MQTT Message: home/hillstate/thermostat/command/3: {'state': 'OFF'}
<07:00:02.539959> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: temperature
  target: 25
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.541167> [Home] MQTT Message: home/hillstate/thermostat/command/2: {'state': 'OFF'}
<07:00:02.542695> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 3>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.543571> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.545171> [Home] MQTT Message: home/hillstate/thermostat/command/3: {'targetTemperature': 30}
<07:00:02.545712> [Home] MQTT Message: home/hillstate/thermostat/command/1: {'state': 'OFF'}
<07:00:02.545893> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 3>
  category: temperature
  target: 30
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.548049> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 1>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:02.546310> [Home] MQTT Message: home/hillstate/thermostat/command/1: {'targetTemperature': 25}
<07:00:02.552566> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 1>
  category: temperature
  target: 25
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}
<07:00:03.357626> [ThreadDeviceTimerOnOff] Device(<Thermostat Room Idx: 2>) On/Off Timer Thread Terminated
<07:00:03.358916> [ThreadCommandQueue] Get Command Queue: 
{
  device: <Thermostat Room Idx: 2>
  category: state
  target: OFF
  parser: <ParserVarious.ParserVarious object at 0xb26d8590>
}


오전 2시 8분 타이머 쓰레드가 가동되기 시작했고, 10분간 켜지고 50분간 꺼져서 1시간에 10분씩 가동되었다

그리고 애플 홈 자동화에 의해 오전 7시 모든 난방과 난방 타이머의 작동이 중단되었다

(콘솔 로그상으로는 원하는대로 동작이 잘 됐다~)

 

Home Assistant의 로그북을 통해 동작 여부를 확인해보자

1시간 간격으로 10분씩 난방이 정상적으로 가동됐다 (희망온도=25도)

월패드의 온도 센서로 감지하는 실내온도(현재온도)는 23도로 큰 변동이 없다 (사실 이 녀석은 크게 믿지 않는게 좋다)

 

방 벽면에 부착해둔 Aqara 온습도 센서의 온도 모니터링 결과를 확인해보자

난방이 되지 않은 16일 오후에는 침실 실내온도가 21도 수준이었는데, 타이머가 가동된 17일 오전 2시 ~ 오전 7시에는 실내 온도가 23.5도 정도로 유지된 것을 확인할 수 있다 (난방 가동 희망온도는 25도)

이정도면 충분하지 않을까? 라는 생각은 든다 ㅎㅎ

※ 실제로 오전 8시 기상했을 때 방바닥에 미미하게 온기가 남아있었다

4. 마무리

위에서 말한 기능깃허브 저장소에 'device_onoff_timer' 이름의 브랜치로 커밋해뒀다

https://github.com/YOGYUI/HomeNetwork/tree/device_onoff_timer

 

GitHub - YOGYUI/HomeNetwork: HomeNetwork(Homebridge) Repo

HomeNetwork(Homebridge) Repo. Contribute to YOGYUI/HomeNetwork development by creating an account on GitHub.

github.com

[TODO]

  • 타이머 파라미터(on time, off time, repeat)를 외부에서 수정할 수 있는 기능 추가 (MQTT, 웹 API)
  • Home Assistant 타이머 액세서리 추가 (구글 어시스턴트 연동)

이래저래 타이머 On Time과 Off Time을 조정해보면서 최적의 실내온도를 유지할 수 있는 방안을 찾아봐야겠다 ㅎㅎ (실내 온도가 일정 수준 이하로 떨어지면 타이머를 자동으로 켜는 자동화도 만들어볼만 하다)

이제 자는 동안 추위에 떨 일은 없겠구먼~

※ 여름되면 냉방(에어컨) 타이머도 유용하게 사용할 수 있을 것 같다!


[22.12.20] 추가

타이머 파라미터 (On time, Off time, Repeat 여부)를 수정할 수 있는 웹페이지를 추가해줬다 (core: python - Flask)

※ POST 방식의 web API로 간단하게 구현

프론트엔드 개발은 문외한이라 그냥 필요한 최소한의 기능만 충족시키는 걸로~ (UI/UX bye bye~)

웹페이지 추가된 소스코드는 동일 브랜치(device_onoff_timer)

commit id 0c033df16aae216e788306cec20137bf6a6dca0d 로 푸시해뒀다

이제 거실 + 방3개 모두 에어컨/난방 각각의 타이머 관련 파라미터를 요리조리 수정할 수 있다

 

Homebridge에도 타이머 관련 액세서리 (스위치) 총 8개 구현 완료~

매일 오전 에어컨과 난방 및 타이머들을 종료시켜주는 자동화도 홈 앱에서 구현 완료~


끝~!

반응형