YOGYUI

광교아이파크::환기(전열교환기) Apple 홈킷 연동 (2) 본문

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

광교아이파크::환기(전열교환기) Apple 홈킷 연동 (2)

요겨 2021. 1. 5. 02:29
반응형

[2] Homebridge 설정

1. Homebridge 플러그인 설치

플러그인 검색 키워드로 ventilation/ventilate로 검색해보니 적당한 결과가 없어서 일반 선풍기처럼 사용하고자 Fan 키워드로 검색, @Tommorodrigues의 homebridge-web-fab 플러그인을 발견

https://github.com/Tommrodrigues/homebridge-web-fan

 

Tommrodrigues/homebridge-web-fan

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

github.com

(난방 제어 시 사용한 web-thermostat 플러그인의 개발자가 유사한 방식으로 올려두었기에 채택 - 개발 편의상)

2. 액세서리 등록

        {
            "accessory": "WebFan",
            "name": "ventilator",
            "apiroute": "http://localhost:9999/ventilator",
            "rotationSpeed": true,
            "listener": true,
            "port": 12348,
            "manufacturer": "Bestin",
            "serial": "yogyui ventilator",
            "model": "Bestin",
            "pollInterval": 60
        },

풍량이 있기 때문에 rotationSpeed 속성을 true로 설정

>> 아쉽게도 풍량 Min, Max 값 설정은 미구현 상태, default 0 ~ 100이라 값 변환이 필요할듯, TODO: 소스코드 변경

     테스트는 필요하겠지만, index.js에 다음 코드 추가하면 될듯?

// plugin source code (index.js)
function WebFan (log, config) {
    // 중략
    
    this.rotationSpeedMin = config.rotationSpeedMin || 0
    this.rotationSpeedMax = config.rotationSpeedMax || 100
    
    // 중략
    
    this.service.getCharacteristic(Characteristic.RotationSpeed).setProps({
        minValue: this.rotationSpeedMin,
        maxValue: this.rotationSpeedMax
    })
    
    // 중략
}    

3. 서버 코드 수정

# app.py 
# ventilator 연관 부분만 발췌
from typing import List

from Serial485.SerialComm import SerialComm
from Serial485.ControlParser import ControlParser

class Ventilator:
    init: bool = False
    state: bool = False
    state_prev: bool = False
    speed: int = 0
    speed_prev: int = 0
    listener_port: int = 0
    packet_off: str = ''
    packet_on: str = ''
    packet_get_status: str = ''
    packet_set_rotation_speed: List[str]

PORT_485_CONTROL = '/dev/ttyUSB1'
ventilator = Ventilator()
ventilator.packet_off = '02 61 01 4C 00 00 01 00 00 2F'
ventilator.packet_on = '02 61 01 E3 00 01 01 00 00 89'
ventilator.packet_get_status = '02 61 00 F1 00 00 00 00 00 9A'
ventilator.packet_set_rotation_speed[0] = '02 61 03 EB 00 00 01 00 00 8A'
ventilator.packet_set_rotation_speed[1] = '02 61 03 94 00 00 02 00 00 00'
ventilator.packet_set_rotation_speed[2] = '02 61 03 9F 00 00 03 00 00 FC'
ventilator.listener_port = 12348
ser_control = SerialComm()
par_energy = EnergyParser(ser_energy)

def parse_control_result(chunk: bytearray):
    if len(chunk) < 10:
        return
    header = chunk[1]
    if header == 0x61 and chunk[2] in [0x80, 0x81, 0x83, 0x84, 0x87]:
        # 환기 관련 패킷
        ventilator.state = bool(chunk[5] & 0x01)
        ventilator.speed = chunk[6]
        if ventilator.state != ventilator.state_prev or ventilator.speed != ventilator.speed_prev or not ventilator.init:
            url = "http://0.0.0.0:{}/".format(ventilator.listener_port)
            url += "state?value={}".format(int(ventilator.state))
            requests.get(url)
            print('Control Notify (ventilator): {}'.format(url))

            url = "http://0.0.0.0:{}/".format(ventilator.listener_port)
            url += "rotationSpeed?value={}".format(int(ventilator.speed / 3 * 100))
            requests.get(url)
            print('Control Notify (ventilator): {}'.format(url))
            ventilator.init = True
        ventilator.state_prev = ventilator.state
        ventilator.speed_prev = ventilator.speed

def checkSerialControlConn():
    if not ser_control.isConnected():
        ser_control.connect(PORT_485_CONTROL, 9600)

def sendSerialControlPacket(packet: str):
    checkSerialControlConn()
    ser_control.sendData(bytearray([int(x, 16) for x in packet.split(' ')]))

class MyFlaskApp(Flask):
    def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
        with self.app_context():
            checkSerialControlConn()
        super(MyFlaskApp, self).run(host=host, port=port, debug=debug, load_dotenv=load_dotenv, **options)

app = MyFlaskApp(__name__)

@app.route('/ventilator/status', methods=['GET'])
def ventilator_get_status():
    obj = {
        'currentState': int(ventilator.state),
        'rotationSpeed': int(ventilator.speed / 3 * 100)
        }
    print('ventilator_status response: {}'.format(obj))
    return jsonify(obj)

@app.route('/ventilator/setState', methods=['GET'])
def ventilator_set_status():
    value = request.args.get('value', default=0, type=int)
    if value == 1:
        while not ventilator.state:
            sendSerialControlPacket(ventilator.packet_on)
            time.sleep(0.5)
            sendSerialControlPacket(ventilator.packet_get_status)
            time.sleep(0.5)
    elif value == 0:
        while ventilator.state:
            sendSerialControlPacket(ventilator.packet_off)
            time.sleep(0.5)
            sendSerialControlPacket(ventilator.packet_get_status)
            time.sleep(0.5)
    return ''

@app.route('/ventilator/setRotationSpeed', methods=['GET'])
def ventilator_set_rotation_speed():
    value = request.args.get('value', default=0, type=int)
    conv = min(3, max(0, int(value / 100 * 3) + 1))
    while ventilator.speed != conv:
        sendSerialControlPacket(ventilator.packet_set_rotation_speed[conv - 1])
        time.sleep(0.5)
        sendSerialControlPacket(ventilator.packet_get_status)
        time.sleep(0.5)
    return ''

if __name__ == '__main__':
    par_control = ControlParser(ser_control)
    par_control.sig_parse.connect(parse_control_result)
    app.run(host='0.0.0.0', port=9999, debug=False)
    ser_control.release()

4. 액세서리 확인

방 추가(다용도실) 후 액세서리 이름 변경 (전열교환기)

Home App에서 액세서리 확인

[시리즈 링크]

광교아이파크::환기(전열교환기) Apple 홈킷 연동 (1)

광교아이파크::환기(전열교환기) Apple 홈킷 연동 (2)

광교아이파크::환기(전열교환기) Apple 홈킷 연동 (3)

반응형