YOGYUI

광교아이파크::조명 Apple 홈킷 연동 (7) - Final 본문

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

광교아이파크::조명 Apple 홈킷 연동 (7) - Final

요겨 2021. 1. 1. 11:59
반응형

[7] 최종 구현 결과

앞서 구현한 결과를 토대로 주방 조명 4개, 안방(침실) 조명 2개, 작은방(컴퓨터방) 조명 2개 모두 http-switch 액세서리로 등록한 후 display name을 적절하게 변경

8개 조명 제어 http-switch 등록 완료

Home 앱에서 스위치들을 각각 알맞는 방에 배정

스위치 모두 제대로 작동하고, Siri로 제어 가능한 것도 확인! (애플워치로도 시리 접근 가능하니 굉장히 편하다)

최소 비용으로 집안 조명을 모바일 기기랑 연동해서 제어할 수 있게 만들었다!!!

(분양받은 아파트로 이사가면 아예 모든 기기를 처음부터 Homekit이랑 연동할 수 있게 대공사할 계획)

 

지금까지 구현한 시스템의 schematic은 다음과 같다

home network schematic

 

구현 코드는 다음과 같다 (Git 업로드는 상황봐서 할 예정)

객체지향 코딩을 하긴 했는데 허접하긴 하다 (제대로 돌아가는거에 초점을 맞춰서...)

# common.py
from typing import List

class RoomInfo:
    lights: List[bool]
    lights_prev: List[bool]
    lights_init: List[bool]
    packet_light_on: List[str]
    packet_light_off: List[str]
    packet_status: List[str]

    def __init__(self, light_count: int = 1):
        self.lights = [False] * light_count
        self.lights_prev = [False] * light_count
        self.lights_init = [False] * light_count
        self.packet_light_on = [''] * light_count
        self.packet_light_off = [''] * light_count
        self.packet_status = ''

    @property
    def light_count(self):
        return len(self.lights)
# app.py
import os
import sys
import time
import requests
from typing import List
from flask import Flask, request, json, render_template
from common import RoomInfo
from SerialComm import SerialComm
from EnergyParser import EnergyParser

ser_energy = SerialComm()
PORT_485_ENERGY = '/dev/ttyUSB0'

# room 0: null, 1: bedroom, 2: pc room
rooms = [RoomInfo(), RoomInfo(4), RoomInfo(2), RoomInfo(2)]
rooms[1].packet_light_on[0] = '02 31 0D 01 D0 01 81 00 00 00 00 04 76'
rooms[1].packet_light_off[0] = '02 31 0D 01 D7 01 01 00 00 00 00 00 F5'
rooms[1].packet_light_on[1] = '02 31 0D 01 58 01 82 00 00 00 00 04 E9'
rooms[1].packet_light_off[1] = '02 31 0D 01 5F 01 02 00 00 00 00 00 6A'
rooms[1].packet_light_on[2] = '02 31 0D 01 5C 01 84 00 00 00 00 04 EF'
rooms[1].packet_light_off[2] = '02 31 0D 01 63 01 04 00 00 00 00 00 6C'
rooms[1].packet_light_on[3] = '02 31 0D 01 2B 01 88 00 00 00 00 04 94'
rooms[1].packet_light_off[3] = '02 31 0D 01 33 01 08 00 00 00 00 00 20'
rooms[1].packet_status = '02 31 07 11 9B 01 C0'

rooms[2].packet_light_on[0] = '02 31 0D 01 8C 02 81 00 00 00 00 04 3F'
rooms[2].packet_light_off[0] = '02 31 0D 01 93 02 01 00 00 00 00 00 B8'
rooms[2].packet_light_on[1] = '02 31 0D 01 7B 02 82 00 00 00 00 04 CB'
rooms[2].packet_light_off[1] = '02 31 0D 01 84 02 02 00 00 00 00 00 C4'
rooms[2].packet_status = '02 31 07 11 93 02 B5'

rooms[3].packet_light_on[0] = '02 31 0D 01 3B 03 81 00 00 00 00 04 97'
rooms[3].packet_light_off[0] = '02 31 0D 01 43 03 01 00 00 00 00 00 8B'
rooms[3].packet_light_on[1] = '02 31 0D 01 76 03 82 00 00 00 00 04 D5'
rooms[3].packet_light_off[1] = '02 31 0D 01 7E 03 02 00 00 00 00 00 49'
rooms[3].packet_status = '02 31 07 11 94 03 B1'

def parse_energy_result(chunk: bytearray):
    try:
        if len(chunk) < 7:
            return
        header = chunk[1]
        command = chunk[3]
        if header == 0x31 and command == 0x91:
            room_idx = chunk[5] & 0x0F
            room = rooms[room_idx]
            for i in range(room.light_count):
                room.lights[i] = bool(chunk[6] & (0x01 << i))
                if room.lights[i] != room.lights_prev[i] or not room.lights_init[i]:
                    url = "http://0.0.0.0:-----/"
                    url += "switch_room{}_light{}?password=----".format(room_idx, i + 1)
                    json_obj = {"characteristic": "On", "value": room.lights[i]}
                    print('Notify: {}, {}'.format(url, json_obj))
                    requests.post(url, json=json_obj)
                    room.lights_init[i] = True
                room.lights_prev[i] = room.lights[i]
    except Exception as e:
        pass

def checkSerialEnergyConn():
    if not ser_energy.isConnected():
        ser_energy.connect(PORT_485_ENERGY, 9600)

def sendSerialEnergyPacket(packet: str):
    checkSerialEnergyConn()
    ser_energy.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():
            checkSerialEnergyConn()
        super(MyFlaskApp, self).run(host=host, port=port, debug=debug, load_dotenv=load_dotenv, **options)

app = MyFlaskApp(__name__)

@app.route('/process', methods=['POST'])
def process():
    response = ""
    if not request.is_json:
        req = request.get_data().decode(encoding='utf-8')
        req = req.replace('\r', '')
        req = req.replace('\n', '')
        if 'json=' in req:
            params = json.loads(req[5:])
        else:
            if req[0] == '{' and req[-1] == '}':
                try:
                    params = json.loads(req)
                except Exception:
                    params = {}
            else:
                params = {}
    else:
        params = json.loads(request.get_data(), encoding='utf-8')

    print('POST json: {}'.format(params))
    if 'room' in params.keys() and 'light' in params.keys():
        room_idx = params['room']
        light_idx = params['light']
        if 'command' in params.keys():
            if params['command'].lower() == 'on':
                packet = rooms[room_idx].packet_light_on[light_idx]
                target = True
            else:
                packet = rooms[room_idx].packet_light_off[light_idx]
                target = False
            while rooms[room_idx].lights[light_idx] != target:
                sendSerialEnergyPacket(packet)
                time.sleep(0.25)
                sendSerialEnergyPacket(rooms[room_idx].packet_status)
                time.sleep(0.25)
        elif 'status' in params.keys():
            response = '{}'.format(int(rooms[room_idx].lights[light_idx]))
    return response


if __name__ == '__main__':
    par_energy = EnergyParser(ser_energy)
    par_energy.sig_parse.connect(parse_energy_result)
    app.run(host='0.0.0.0', port=-----, debug=False)
    ser_energy.release()
/* config.json */
{
    "bridge": {
        "name": "Homebridge 349E",
        "username": "--:--:--:--:--:--",
        "port": ----,
        "pin": "XXX-XX-XXX"
    },
    "accessories": [
        {
            "accessory": "HTTP-SWITCH",
            "name": "Kitchen Light1",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 0,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 0,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 0,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room1_light1",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "Kitchen Light2",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 1,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 1,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 1,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room1_light2",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "Kitchen Light3",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 2,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 2,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 2,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room1_light3",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "Kitchen Light4",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 3,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 3,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 1,
                    "light": 3,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room1_light4",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "Bedroom Light1",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 0,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 0,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 0,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room2_light1",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "Bedroom Light2",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 1,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 1,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 2,
                    "light": 1,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room2_light2",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "PC Room Light1",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 0,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 0,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 0,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room3_light1",
            "notificationPassword": "----"
        },
        {
            "accessory": "HTTP-SWITCH",
            "name": "PC Room Light2",
            "switchType": "stateful",
            "onUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 1,
                    "command": "on"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "offUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 1,
                    "command": "off"
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "statusUrl": {
                "url": "http://localhost:----/process",
                "method": "POST",
                "body": {
                    "room": 3,
                    "light": 1,
                    "status": 0
                },
                "headers": {
                    "Accept": "application/json"
                }
            },
            "notificationID": "switch_room3_light2",
            "notificationPassword": "----"
        },
    ],
    "platforms": [
        {
            "name": "Config",
            "port": ----,
            "platform": "config"
        }
    ]
}

 

TODO

  1. 거실 조명 제어
    거실 조명들은 RS-485를 통해 제어되지 않는 것으로 보인다 (중앙 월패드에서만 제어 가능)
    중앙 월패드 뜯어볼 예정

  2. Control Device 제어
    난방, 환기, 가스 제어 추가 (RS-485)

  3. 기타
    엘리베이터 호출, 도어락 열기 등 편의 기능 추가

[시리즈 링크]

광교아이파크::조명 Apple 홈킷 연동 (1)

광교아이파크::조명 Apple 홈킷 연동 (2)

광교아이파크::조명 Apple 홈킷 연동 (3)

광교아이파크::조명 Apple 홈킷 연동 (4)

광교아이파크::조명 Apple 홈킷 연동 (5)

광교아이파크::조명 Apple 홈킷 연동 (6)

광교아이파크::조명 Apple 홈킷 연동 (7)

반응형
Comments