YOGYUI

광교아이파크::난방 Apple 홈킷 연동 (4) 본문

홈네트워크(IoT)/광교아이파크

광교아이파크::난방 Apple 홈킷 연동 (4)

요겨 2021. 1. 2. 21:46
반응형

[4] Homebridge plug-in 설정

플러그인 키워드 thermostat으로 검색해보니 대다수 플러그인들은 Nest나 Honeywell같은 IoT 제품군들과 연계하여 사용해야 하는 것들이다

thermostat plugin 검색 결과

좀 더 찾아보니 @tommrodrigues가 업로드한 homebridge-web-thermostat가 HTTP기반 제어가 가능해 선택!

https://github.com/Tommrodrigues/homebridge-web-thermostat#readme

 

Tommrodrigues/homebridge-web-thermostat

Homebridge plugin for a web-based thermostat. Contribute to Tommrodrigues/homebridge-web-thermostat development by creating an account on GitHub.

github.com

플러그인의 장점이자 단점이 액세서리별로 상태 변화에 대한 즉각적 업데이트가 가능한 listener 포트를 제공한다는 점

장점: notification-server 처럼 별도의 config 파일을 만들 필요가 없다

단점: 액세서리별로 단독 포트들이 오픈되니깐 부하가 걱정된다

또 다른 단점으로는 명령이나 상태 쿼리에 대해서 GET method 인자 방식만 매뉴얼에서 설명하고 있다는 점이다

액세서리별로 디바이스 온/오프 설정, 타겟 온도 설정, 상태값 쿼리 등에 대한 라우팅을 따로 둬야돼 코딩이 지저분해진다는 점인데...javascript 소스코드 훑어보고 정 안되면 직접 수정하는 걸로!

 

feasibility 확인할 겸 해서 플러그인 설치하고 매뉴얼대로 액세서리 추가

        {
            "accessory": "Thermostat",
            "name": "Living room thermostat",
            "apiroute": "http://localhost:9999/heat/room1",
            "temperatureDisplayUnits": 0,
            "currentRelativeHumidity": false,
            "heatOnly": true,
            "maxTemp": 40,
            "minTemp": 5,
            "minStep": 0.5,
            "listener": true,
            "port": 12345,
            "manufacturer": "Bestin",
            "serial": "",
            "model": "Bestin"
        },

방 1번 (거실/주방)에 대한 액세서리임을 명시, apiroute에 라우팅 부모경로를 지정해줘야 한다

cooling/heating 둘 다 지원하는지 여부, 온도 설정 최대값 및 최소값, 그리고 스텝값 등 필요한 속성들을 지정할 수 있다

액세서리에 명시한 대로 Flask 서버 코드도 수정

rooms[1].heat_listener_port = 12345
rooms[2].heat_listener_port = 12346
rooms[3].heat_listener_port = 12347

""" 중략 """

def parse_control_result(chunk: bytearray):
    if len(chunk) < 10:
    	return
    header = chunk[1]
    command = chunk[3]
    if header == 0x28 and command in [0x91, 0x92]:
        room_idx = chunk[5] & 0x0F
        room = rooms[room_idx]
        if chunk[6] == 0x02:
            room.heat = False
        elif chunk[6] == 0x11:
            room.heat = True
        room.heat_temp_setting = (chunk[7] & 0x3F) + (chunk[7] & 0x40 > 0) * 0.5
        room.heat_temp_current = chunk[9] / 10.0
        # notification
        if room.heat_prev != room.heat or room.heat_temp_setting != room.heat_temp_setting_prev or not room.heat_init:
            if room_idx == 1:
                url = "http://0.0.0.0:12345/"
                url += "targetHeatingCoolingState?value={}".format(int(room.heat))
                requests.get(url)
                url = "http://0.0.0.0:12345/"
                url += "targetTemperature?value={}".format(room.heat_temp_setting)
                requests.get(url)
            room.heat_init = True
        room.heat_prev = room.heat
        room.heat_temp_setting_prev = room.heat_temp_setting

""" 중략 """

@app.route('/heat/<room>/status', methods=['GET'])
def heat_room_get_status(room):
    room_idx = int(room[-1])
    heat = 1 if rooms[room_idx].heat else 0
    obj = {
        'targetHeatingCoolingState': heat,
        'targetTemperature': rooms[room_idx].heat_temp_setting,
        'currentHeatingCoolingState': heat,
        'currentTemperature': rooms[room_idx].heat_temp_current
        }
    return jsonify(obj)

@app.route('/heat/<room>/targetHeatingCoolingState', methods=['GET'])
def heat_room_set_target_state(room):
    # 변경된 상태 정보를 얻을 때까지 반복 전송
    room_idx = int(room[-1])
    value = request.args.get('value', default=1, type=int)
    if value == 1:
        while not rooms[room_idx].heat:
            sendSerialControlPacket(rooms[room_idx].packet_heat_on)
            time.sleep(0.25)
            sendSerialControlPacket(rooms[room_idx].packet_heat_status)
            time.sleep(0.25)
    elif value == 0:
        while rooms[room_idx].heat:
            sendSerialControlPacket(rooms[room_idx].packet_heat_off)
            time.sleep(0.25)
            sendSerialControlPacket(rooms[room_idx].packet_heat_status)
            time.sleep(0.25)
    return ''

@app.route('/heat/<room>/targetTemperature', methods=['GET'])
def heat_room_set_target_temperature():
    room_idx = int(room[-1])
    value = request.args.get('value', default=20., type=float)
    idx = max(0, min(70, int((value - 5.0) / 0.5)))
    while rooms[room_idx].heat_temp_setting != value:
        sendSerialControlPacket(rooms[room_idx].packet_heat_temp_set[idx])
        time.sleep(0.25)
        sendSerialControlPacket(rooms[room_idx].packet_heat_status)
        time.sleep(0.25)
    return ''

각 액세서리별로 /status, /targetHeatingCoolingState, /targetTemperature 세 종류의 라우팅을 뚫어줘야 제대로 동작한다( 동적 라우팅으로 공통화)

listener 포트에 메시지보낼때도 파라미터를 &로 묶어서 보내니 1번 파라미터만 응답한다

결국 On/Off 여부랑 현재 설정 온도 값에 대해서 두 번 나누어서 url 호출하도록 구현

플러그인 소스 코드 중 listener 부분 발췌

소스코드 열어보니 substr(1)에 대해서만 핸들러 함수 태우게 구현해두었네? >> 나중에 필요하면 수정!

 

홈브릿지 재시작

맥OS 상에서의 액세서리 디버깅

홈앱에 thermostat 액세서리가 등장했다

(인증 동영상은 결국 패드들고 월패드 앞에서 찍음...)

켜기/끄기/타겟온도 설정값 변경 등 잘 동작함을 확인! (외부에서 상태 변경했을 경우 업데이트도 빠르게 잘 된다)

 

기능상 별다른 문제없이 잘 동작은 하는데, Status off일 경우 json으로 업데이트해준 현재 온도 (currentTemperature)를 상태값으로 표기해주는데 단위가 0.5℃ 단위로 반올림?되어서 나온다

분명히 json으로는 22.4값을 보냈는데!
디스플레이는 22.5...

(json 전송값이 22.3이나 22.2일때는 22.0으로 표시됨..)

 

Homekit 소스코드(github.com/homebridge/HAP-NodeJS/blob/186ce56fab55b5868142e27b59af6c5ab53038d6/src/lib/gen/HomeKit.ts#L811) current Temperature 객체 선언에 minStep은 분명히 0.1로 되어있는데... 플러그인 소스코드에서도 해당 속성 건드리는 부분을 찾을 순 없었다.

export class CurrentTemperature extends Characteristic {

  static readonly UUID: string = '00000011-0000-1000-8000-0026BB765291';

  constructor() {
    super('Current Temperature', CurrentTemperature.UUID);
    this.setProps({
      format: Formats.FLOAT,
      unit: Units.CELSIUS,
      maxValue: 100,
      minValue: 0,
      minStep: 0.1,
      perms: [Perms.READ, Perms.NOTIFY]
    });
    this.value = this.getDefaultValue();
  }
}

홈브릿지 서버 상에서의 현재 온도 표기

홈브릿지 서버의 액세서리 탭에서는 정상적으로 0.1 단위로 표기되는거보니 애플 디바이스 홈 앱 자체의 특성같기도 하고...

 

[시리즈 링크]

광교아이파크::난방 Apple 홈킷 연동 (1)

광교아이파크::난방 Apple 홈킷 연동 (2)

광교아이파크::난방 Apple 홈킷 연동 (3)

광교아이파크::난방 Apple 홈킷 연동 (4)

광교아이파크::난방 Apple 홈킷 연동 (5)

반응형