YOGYUI

광교아이파크::Bestin 현관문 도어락 연동 (2) - Final 본문

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

광교아이파크::Bestin 현관문 도어락 연동 (2) - Final

요겨 2022. 3. 30. 19:41
반응형

제일 심플한 방법이 뭘까 고민해보니, 홈네트워크 서버로 쓰고 있는 라즈베리파이의 GPIO를 사용하면 되겠다는 생각이 들었다!

배선을 위해 약간의 작업을 해줬다


길이가 긴 wire 한쌍이랑, 점퍼 와이어 암/수 한쌍 준비

 

점퍼 와이어 반토막

 

원래는 절연테이프로 칭칭 감을려고 했는데, 집에 찾아보니 무탈피 와이어 커넥터가 있어서 쓰기로 했다

 

전선 양단 모두 야무지게 연결해주면 준비 끝

 

구글링으로 라즈베리파이4 GPIO Map을 찾은 뒤에 연결이 쉬워보이는 GPIO23이랑 GND에 각각 연결

https://fishpoint.tistory.com/6181

 

홈네트워크 게이트웨어 몰렉스 커넥터쪽은 그냥 쑤셔넣어서 연결

라즈베리파이4 GPIO 출력 전압은 3.3V로 앞서 오실로스코프로 살펴본 시그널 라인 전압레벨과 맞지 않아 신호 분리를 하는게 원칙이다 (커플러, 로직 컨버터 등)
적어도 저항은 달아줘야 월패드/라즈베리파이 모두 원활하게 동작할텐데, 집에 리드 타입 저항 남는게 없어서 SW상으로 해결했다 (뒤에서 후술)

라즈비안 OS 설치시 기본으로 제공되는 python-rpi.gpio 패키지를 활용해서 파이썬으로 GPIO를 제어하는 프로토타입 코드를 작성해봤다

import time
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.OUT)
GPIO.output(23, GPIO.LOW)

for i in range(2):
    GPIO.output(23, GPIO.HIGH)
    time.sleep(0.2)
    GPIO.output(23, GPIO.LOW)
    time.sleep(0.2)

GPIO.cleanup()

GPIO23을 두 번 연속 200ms 간격으로 HIGH - LOW 레벨을 반복하니 도어락이 열리는 걸 확인할 수 있었다

 

이제 기존 홈네트워크 서버 코드에 추가할 차례

간단하게 Device클래스를 상속받는 Door 클래스를 하나 만들어줬다

[Door.py]

import time
import json
import threading
from typing import Union
from Device import Device
import RPi.GPIO as GPIO
from Common import Callback, writeLog

class ThreadOpen(threading.Thread):
    def __init__(self, gpio_port: int, repeat: int, interval_ms: int):
        threading.Thread.__init__(self)
        self.gpio_port = gpio_port
        self.repeat = repeat
        self.interval_ms = interval_ms
        self.sig_terminated = Callback()
    
    def run(self):
        writeLog('Started', self)
        for i in range(self.repeat):
            GPIO.output(self.gpio_port, GPIO.HIGH)
            time.sleep(self.interval_ms / 1000)
            GPIO.output(self.gpio_port, GPIO.LOW)
            time.sleep(self.interval_ms / 1000)
        writeLog('Terminated', self)
        self.sig_terminated.emit()

class Door(Device):
    gpio_port: int
    thread_open: Union[ThreadOpen, None] = None

    def __init__(self, name: str = 'Door', **kwargs):
        super().__init__(name, **kwargs)
        # TODO: parameterize
        self.gpio_port = 23
        self.repeat = 2
        self.interval_ms = 200
        self.mqtt_publish_topic = 'home/ipark/door/state'
        self.mqtt_subscribe_topics.append('home/ipark/door/command')

        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.gpio_port, GPIO.IN, GPIO.PUD_DOWN)  # GPIO IN, Pull Down 설정

    def startThreadOpen(self):
        if self.thread_open is None:
            GPIO.setup(self.gpio_port, GPIO.OUT)
            GPIO.output(self.gpio_port, GPIO.LOW)
            self.thread_open = ThreadOpen(self.gpio_port, self.repeat, self.interval_ms)
            self.thread_open.sig_terminated.connect(self.onThreadOpenTerminated)
            self.thread_open.start()
        else:
            writeLog('Thread is still working', self)

    def onThreadOpenTerminated(self):
        del self.thread_open
        self.thread_open = None
        self.publish_mqtt()
        GPIO.setup(self.gpio_port, GPIO.IN, GPIO.PUD_DOWN)  # GPIO IN, Pull Down 설정

    def open(self):
        self.startThreadOpen()

    def publish_mqtt(self):
        obj = {"state": int(self.state == 1)}
        self.mqtt_client.publish(self.mqtt_publish_topic, json.dumps(obj), 1)

    def __repr__(self):
        repr_txt = f'<{self.name}({self.__class__.__name__} at {hex(id(self))})'
        repr_txt += '>'
        return repr_txt

GPIO On/Off 시 time delay(sleep)이 수백ms가 넘기 때문에 단일 프로세스 블럭 이슈가 발생할 것 같아 GPIO On/Off 기능은 쓰레드로 별도로 구현했다

 

앞서 말한 전압레벨 불균형과 관련해 트릭을 써봤다

평소에는 GPIO23번을 INPUT으로 설정해뒀다가, 홈네트워크로 문을 열라는 명령이 올때(쓰레드가 구동할 때)만 OUTPUT으로 설정한 뒤, 쓰레드가 종료되면 다시 INPUT으로 설정하는 테크닉(?) ㅎㅎ

평소 전압레벨은 LOW이므로 풀다운 저항(PUD_DOWN) 모드로 설정하는 것도 중요하다

 

기존의 Home 클래스에도 Door 관련 코드를 추가해줬다

[Home.py]

class Home:
    # 중략
    door: Door
    
    def initialize(self, init_service: bool, serial_conn: bool):
        # 중략
        self.door = Door(name='Door', mqtt_client=self.mqtt_client)
        self.device_list.append(self.door)
    
    def onMqttClientMessage(self, _, userdata, message):
        # 중략
        if 'door/command' in topic:
            # do not use command queue becase door module handles only gpio
            if msg_dict['state'] == 1:
                self.door.open()

 

자세한 변경내역은 Git 변경내역 참고 (commit id: 0f6cc1dd9cb8d8931f60a2c8c260ec4056ad8a18)

https://github.com/YOGYUI/HomeNetwork/commit/0f6cc1dd9cb8d8931f60a2c8c260ec4056ad8a18

 

- add doorlock open class · YOGYUI/HomeNetwork@0f6cc1d

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

github.com

 

이제 Homebridge에도 추가해줄 차례

엘리베이터와 마찬가지로 mqtt 액세서리-스위치로 추가해주면 된다

[config.json]

{
    "accessory": "mqttthing",
    "type": "switch",
    "name": "Door Open (MQTT)",
    "url": "mqtt:://yogyui.iptime.org:30003",
    "username": "yogyui",
    "password": "Lsh312453124%",
    "topics": {
        "getOn": {
            "topic": "home/ipark/door/state",
            "apply": "return JSON.parse(message).state;"
        },
        "setOn": {
            "topic": "home/ipark/door/command",
            "apply": "return JSON.stringify({state: message});"
        }
    },
    "integerValue": true,
    "onValue": 1,
    "offValue": 0,
    "logMqtt": true
}

안타깝게도 도어락 무선통신 모듈은 열라는 명령을 보낼수만 있지, 열려있는지 닫혀있는지 등에 대한 상태 정보는 가져올 수 없기 때문에 스위치도 그냥 열고나서 바로 꺼지도록 구현한 상황 흑...

 

홈브리지를 재시작하면 Door Open 스위치 액세서리가 추가된다

 

애플기기 홈 앱에 액세서리가 추가된 것을 확인

 

 

홈어시스턴트(HA) 설정 파일도 다음과 같이 추가해주자

[configuration.yaml]

  - platform: mqtt
    name: "현관문 도어락 열기"
    unique_id: "opendoorlock"
    state_topic: "home/ipark/door/state"
    command_topic: "home/ipark/door/command"
    value_template: '{ "state": {{ value_json.state }} }'
    payload_on: '{ "state": 1 }'
    payload_off: '{ "state": 0 }'
    retain: false
    icon: mdi:elevator-passenger

HA 재시작 후 HA 대시보드에 액세서리가 추가된 것을 확인할 수 있다

 

HA와 연동한 구글 홈 앱도 새로고침하면 해당 액세서리가 정상적으로 추가된다

 

실제 동작 동영상을 찍어봤다

※ 도어락이 열렸을 때 LED가 번쩍인다거나, 손잡이가 돌아간다거나 이런 기능이 없기 때문에 소리를 켜야 동작 여부를 확인할 수 있다

원하는 대로 잘 동작한다 ㅎㅎ

(물론 기존 월패드에서도 정상적으로 열린다)


월패드 제어기능 중에 마지막 남은 도어락 열기 기능도 홈네트워크(홈브리지-애플, 홈어시스턴트-구글)와 연동하는데 성공했다

그냥 GPIO blink 정도만 구현하면 되는거라 1시간 정도 걸려서 후딱 해치웠다

 

이제 화장실에서 응가할 때 지인이 찾아오면 현관문을 문제없이 열어줄 수 있다 (응??)

혹은 양손 가득 장바구니를 들고 있을 때, 시리로 현관문을 열 수 있다

(지금은 "현관문 열기 켜줘"로 요상한 명령어를 내려야 알아듣는데, 음성 인식 방식을 어떻게 바꿀 수 있을지 고민 좀 해봐야겠다)

[주의]
모바일 기기 털리거나 혹은 홈네트워크 해킹당하게 되면 집까지 홀랑 털려버리게 된다
본인이 평소 물리적/소프트웨어적 보안에 크게 연연하지 않는 삶을 살고 있다면, 이 기능은 굳이 따라 구현하지 않기를 추천~
(나는 문열림 센서, 재실 감지 센서, 보안 카메라 등등 쓸데없이 많은 장치를 달아놓긴 했다 ㅎㅎ 네트워크 보안도 물론 어느정도 신경쓰고 있고)

 

[시리즈]

광교아이파크::Bestin 현관문 도어락 연동 (1)

광교아이파크::Bestin 현관문 도어락 연동 (2) - Final

반응형