안녕하세요, 피플펀드 테크 그룹의 구영민입니다. 피플펀드 테크 그룹은 신규 입사자가 회사에 적응할 수 있도록 개발 업무에 들어가기 전 소프트 랜딩 기간을 제공하고 있습니다. 소프트 랜딩 기간에는 업무 시간을 토이 프로젝트를 진행하는 데 사용할 수 있도록 지원합니다. 이 글에서는 제가 소프트 랜딩 기간 동안 작업한 내용과 결과를 공유합니다.

피플펀드의 출입문

피플펀드의 사무실은 외부인이 임의로 출입하는 것을 막기 위해 출입문이 자동으로 잠기도록 되어 있습니다. 안에서는 버튼으로 문을 열 수 있지만, 바깥에서 문을 열려면 출입증이 있어야 합니다. 출입증을 가져가지 않는 분들이 다시 사무실로 들어오기 위해서는 Slack이라는 사내에서 사용하는 메신저에 양해를 구해야만 했습니다.

문좀...

밖에 있는 사람은 누군가 문을 열어줄 때까지 기다려야 하고 안에 있는 사람은 문까지 걸어가야 하는 귀찮음을 해결할 수 있도록 출입증이 없어도 문을 열 수 있도록 버튼에 사물인터넷을 접목해 보았습니다.

보안

가장 먼저 생각해 볼 문제는 보안입니다. 사무실에 아무나 들어와서는 안 되고, 회사와 물리적으로 떨어진 곳에서는 문을 열 수 없도록 구성해야 합니다. 여러 방법이 있지만, 회사 직원만 이용할 수 있는 Wi-Fi 네트워크를 이용하는 방법을 사용했습니다.

시스템 제어

Raspberry Pi는 리눅스를 기반으로 작동하는 컴퓨터입니다. 일반적인 컴퓨터와는 다르게 전기 신호를 제어할 수 있는 GPIO (General-Purpose Input/Output) 단자가 있습니다. 전체 시스템을 제어할 컴퓨터로 Raspberry Pi가 적합하다고 판단해 Wi-Fi 기능이 있는 Raspberry Pi Zero W 모델을 구매했습니다.

Raspberry Pi의 전원을 켜기 위해서는 공식 다운로드 페이지에서 Raspbian OS를 내려받아 micro SD 카드에 설치해야 합니다. Windows에서 설치하기 위해서는 공식 설치 가이드를 확인하세요. macOS나 Linux에서는 다음 명령을 사용하면 됩니다.

$ sudo dd bs=1m if=2017-11-29-raspbian-stretch-lite.img of=/dev/rdiskX conv=sync

여기서 rdiskX는 디스크 유틸리티에서 확인할 수 있는 디스크 번호입니다. 예를 들어 메모리 카드의 기기 란에 disk2s1이라고 나오면 rdiskXrdisk2로 바꾸면 됩니다.

버튼과 Relay 모듈

눌러주세요

기계가 버튼을 누르려면 어떻게 해야 할까요? 간단하게는 기계 손가락을 만들어 버튼을 누르면 됩니다.

하지만 조금 더 생각해 보면 더 간단한 방법이 있습니다. 버튼을 살펴보면 뒤로 두 개의 선이 들어갑니다. 버튼이 눌리지 않으면 두 선은 연결되지 않습니다. 버튼이 눌리면 두 선은 합선(연결)됩니다. 두 선이 합선되면 SECOM 장비가 문의 잠금을 해제해 줍니다. 이제 버튼에서 두 선을 분리하고 특정 조건이 만족하였을 때 Raspberry Pi로 두 선을 합선되도록 하면 됩니다.

GPIO는 General-Purpose Input/Output라는 이름에서 알 수 있듯이 전기 신호를 입/출력하는 역할을 합니다. 작업에 필요한 두 선을 합선시키는 작업에 사용하기엔 적합하지 않습니다. 대신 사용하기 적합한 모듈은 전자석을 이용하여 두 선의 쇼트를 제어할 수 있는 Relay 모듈입니다. 버튼의 두 선을 Raspberry Pi에 직접 연결하는 대신 Relay 모듈에 연결합니다.

보드

Raspberry Pi와 연결하기

전원을 넣었을 때 Raspberry Pi의 전원 LED가 깜빡입니다. 이제 컴퓨터와 SSH로 연결하면 됩니다. 연결하는 방법에는 크게 두 가지 방법이 있습니다.

  • Raspberry Pi에 모니터와 키보드를 연결하여 초기 설정하기: 이 Raspberry Pi 모델이 사용하는 HDMI 규격은 미니 HDMI이며 USB 포트는 스마트폰에 많이 사용하는 마이크로 5핀 단자입니다. 하지만 저 단자에 맞는 케이블이 없어 다른 방법을 찾아보기로 했습니다.
  • SD 카드에 설정 파일을 직접 넣기 (headless setup): SD 카드의 boot 파티션에 빈 ssh 파일을 만들고, wpa_supplicant.conf 파일을 만들어 Wi-Fi SSID와 비밀번호를 입력합니다. 이렇게 하면 자동으로 설정된 Wi-Fi에 접속해 SSH 서버를 엽니다.
$ touch ssh
$ nano wpa_supplicant.conf
# 아래 내용 입력
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
network={
    ssid="WIFI_SSID"
    psk="WIFI_PASSWORD"
    key_mgmt=WPA-PSK
}

Raspberry Pi에 전원을 켜고 할당된 IP 주소를 찾기 위해 네트워크를 검색하는 프로그램을 다운로드 받아 검색해 봅니다. 이때 Vendor가 Raspberry Pi Foundation으로 되어 있는 주소를 찾으면 됩니다. 그 주소에 SSH를 이용해 연결해 줍니다. 초기 아이디는 pi이며 초기 비밀번호는 raspberry입니다. 비밀번호를 입력할 때 문자가 보이지 않아도 입력되고 있는 것입니다.

$ ssh pi@192.168.0.x
Password: raspberry

Relay 모듈 제어하기

Raspberry Pi Pinout 사이트를 참조하여 각자의 핀이 어떤 역할을 하는지 확인 후 연결합니다. 저는 BCM 18 핀에 Relay 모듈의 IN 핀을 연결했습니다. 예제 소스 코드들을 이용해 테스트해 보았습니다.

# High-level Trigger Relay 모듈 사용 시
import RPi.GPIO as GPIO 
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

while True:
    try:
        GPIO.output(18, GPIO.HIGH)
        time.sleep(1)

        GPIO.output(18, GPIO.LOW)
        time.sleep(1)

    except KeyboardInterrupt:
        GPIO.cleanup()
        break

예제를 확인해 보면 GPIO.LOW (0V) 상태에서는 Relay가 닫히고 GPIO.HIGH (3.3V) 상태에서는 열린다고 설명되어 있었지만 예상과 다르게 동작했습니다. Relay의 동작 전압이 인터넷의 전압과 다르다는 것을 예측하고, Raspberry Pi의 GPIO 핀에 전압이 얼마나 출력되는지 전압 측정기를 이용해 확인해 보았습니다. 그 결과,

  • GPIO를 제어하지 않을 때는 5V를 출력합니다.
  • GPIO를 제어할 때는 0V (GPIO.LOW) 혹은 3.3V (GPIO.HIGH)를 출력합니다.

구매한 Relay는 Low-Level Trigger Relay이기 때문에 IN으로 들어오는 전압이 높으면 닫히고 낮으면 열리게 되어 있었습니다. Relay 모듈이 작동하는 Threshold 전압이 약 4V-5V 사이이기 때문에 GPIO 제어를 할 때마다 Relay가 열렸습니다. (위에 서술했듯이 GPIO를 제어할 때 전압은 0V - 3.3V 사이로 제어되기 때문입니다) 이에 맞게 소스 코드를 수정해 줍니다.

# Low-level Trigger Relay 모듈 사용 시
import RPi.GPIO as GPIO
import time

while True:
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(18, GPIO.OUT, initial=GPIO.LOW)
        time.sleep(1)
        GPIO.cleanup()

        time.sleep(1)

    except KeyboardInterrupt:
        GPIO.cleanup()
        break

웹 서버 구축

Python의 웹 프레임워크인 Flask로 웹 서버를 만들며 위의 소스 코드를 응용해 보았습니다.

import time
import RPi.GPIO as GPIO
from flask import Flask, request

app = Flask(__name__)
lock = False


@app.route("/open_door")
def open_door():
    global lock
    if not lock:
        lock = True

        GPIO.setmode(GPIO.BCM)
        GPIO.setup(18, GPIO.OUT, initial=GPIO.LOW)
        time.sleep(0.3)
        GPIO.cleanup()
        time.sleep(1)

        lock = False

        try:
            requests.post("https://hooks.slack.com/services/...",
                          json={"channel": "#door", "username": "munjom",
                                "text": "문이 열렸습니다. 요청 IP는 " + request.remote_addr + "입니다.", "icon_emoji": ":door:"})
        except:
            pass
    return "<script> alert('문이 열렸습니다.'); window.close(); </script>"

if __name__ == "__main__":
    app.run("0.0.0.0", debug=False, threaded=True)

소스 코드의 Lock은 빠른 시간 내에 여러 번의 문을 열어달라는 요청이 오면 한 번만 문을 열어주기 위한 변수입니다.

Slack은 메시지를 보내는 API가 공개되어 있습니다. 문을 연 기록을 언제든지 확인할 수 있도록 문을 열 때마다 IP 주소를 Slack의 Incoming Hook API을 이용해 메시지를 전송하도록 했습니다.

부팅 시 자동 시작

Raspberry Pi는 배터리와 같은 비상 전원이 없어 정전과 같이 전기가 잠시라도 끊기면 언제든지 재부팅될 수 있습니다. 매번 SSH으로 로그인하여 웹 서버를 실행하는 대신, 자동으로 웹 서버가 켜지도록 작업해 보았습니다. 자동 실행을 위해서 supervisor라는 프로그램을 설치해 줍니다.

$ sudo apt-get install supervisor

이제 설정 파일을 만들어 줍니다. 설정 파일은 /etc/supervisor/conf.d/*.conf 형태로 만들면 됩니다.

$ sudo nano /etc/supervisor/conf.d/flask_app.conf
# 아래 내용 입력
[program:flask_app]
command = python server.py
directory = /home/pi
autostart = true
autorestart = true

설정이 끝났으므로 이제 supervisor 서비스를 켠 후, 웹 서버가 정상적으로 동작하는지 확인합니다.

$ sudo service supervisor start

완성!

짜잔! 스마트폰으로 문을 열 수 있는 링크가 완성되었습니다.

반응

slack

:)

감사합니다.