Chapter 15. [종합] IoT 스마트 보안 시스템
지금까지 배운 모든 기술을 하나로 합칩니다. PIR 센서로 침입을 감지하고, 초음파 센서로 거리를 측정하며, LCD에 상태를 표시하고, 웹 서버를 통해 원격으로 경보를 해제하는 통합 시스템입니다.
각 선은 실제 GPIO 역할에 맞춰 끝점을 맞췄습니다. 초음파 센서는 GP17(Trig)과 GP16(Echo), LCD는 GP8/GP9 I2C, 부저는 GP14, PIR은 GP18을 사용합니다.
15.1 준비물과 연결 핀
아래 핀맵대로 연결하면 지금까지 배운 센서 입력, 출력 장치, 웹 제어가 한 프로젝트 안에서 함께 동작합니다. 초음파 센서의 Echo가 5V로 출력되는 모듈이라면 Pico 입력 보호를 위해 저항 분압을 사용하세요.
| 부품 | Pico 2 W 핀 | 역할 |
|---|---|---|
| PIR 센서 OUT | GP18 |
사람 움직임 감지 |
| 초음파 Trig / Echo | GP17 / GP16 |
거리 측정 |
| I2C LCD SDA / SCL | GP8 / GP9 |
상태 메시지 표시 |
| 부저 + | GP14 |
침입 감지 시 경보음 |
15.2 통합 시스템 메인 소스 코드
이 코드는 Ch 5(Web), Ch 7(Sensors), Ch 9(LCD)의 내용을 실제로 합친 예시입니다. 브라우저에서 Pico의 IP 주소로 접속하면 보안 모드 켜기/끄기, 경보 초기화, 현재 거리와 움직임 상태를 확인할 수 있습니다.
import machine
import network
import socket
import time
from pico_i2c_lcd import I2cLcd
# Wi-Fi 설정
SSID = "YOUR_WIFI_NAME"
PASSWORD = "YOUR_WIFI_PASSWORD"
# 보안 기준값
DIST_LIMIT_CM = 20
BUZZER_TIME = 0.25
# 핀 설정
pir = machine.Pin(18, machine.Pin.IN)
trig = machine.Pin(17, machine.Pin.OUT)
echo = machine.Pin(16, machine.Pin.IN)
buzzer = machine.Pin(14, machine.Pin.OUT)
# LCD 설정
i2c = machine.I2C(0, sda=machine.Pin(8), scl=machine.Pin(9))
lcd = I2cLcd(i2c, 0x27, 2, 16)
is_armed = True
alarm_on = False
last_distance = 0
last_motion = 0
def lcd_message(line1, line2=""):
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(line1[:16])
lcd.move_to(0, 1)
lcd.putstr(line2[:16])
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(SSID, PASSWORD)
lcd_message("Wi-Fi Connect", "Please wait...")
for _ in range(20):
if wlan.isconnected():
ip = wlan.ifconfig()[0]
lcd_message("Web Server IP", ip)
print("Connected:", ip)
return ip
time.sleep(0.5)
lcd_message("Wi-Fi Failed", "Check SSID/PW")
raise RuntimeError("Wi-Fi connection failed")
def wait_pin_value(pin, target, timeout_us=30000):
start = time.ticks_us()
while pin.value() != target:
if time.ticks_diff(time.ticks_us(), start) > timeout_us:
return None
return time.ticks_us()
def get_distance():
trig.low()
time.sleep_us(2)
trig.high()
time.sleep_us(10)
trig.low()
start = wait_pin_value(echo, 1)
if start is None:
return 999
end = wait_pin_value(echo, 0)
if end is None:
return 999
return (time.ticks_diff(end, start) * 0.0343) / 2
def beep(count=1):
for _ in range(count):
buzzer.high()
time.sleep(BUZZER_TIME)
buzzer.low()
time.sleep(0.08)
def render_page():
armed_text = "ON" if is_armed else "OFF"
alarm_text = "DETECTED" if alarm_on else "CLEAR"
motion_text = "YES" if last_motion else "NO"
return f"""<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pico Security</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 560px; margin: 40px auto; color: #0f172a; }}
.box {{ border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; }}
.danger {{ color: #dc2626; font-weight: bold; }}
.ok {{ color: #16a34a; font-weight: bold; }}
a {{ display: inline-block; margin: 6px; padding: 10px 14px; border-radius: 8px; background: #0ea5e9; color: white; text-decoration: none; }}
a.warn {{ background: #f97316; }}
</style>
</head>
<body>
<h1>Pico 2 W Security</h1>
<div class="box">
<p>보안 모드: <strong>{armed_text}</strong></p>
<p>경보 상태: <span class="{'danger' if alarm_on else 'ok'}">{alarm_text}</span></p>
<p>움직임 감지: {motion_text}</p>
<p>거리: {last_distance:.1f} cm</p>
<a href="/arm">ARM</a>
<a href="/disarm" class="warn">DISARM</a>
<a href="/reset">RESET</a>
</div>
</body>
</html>"""
def handle_request(request):
global is_armed, alarm_on
if "GET /arm" in request:
is_armed = True
lcd_message("Security Mode", "ARMED")
elif "GET /disarm" in request:
is_armed = False
alarm_on = False
buzzer.low()
lcd_message("Security Mode", "DISARMED")
elif "GET /reset" in request:
alarm_on = False
buzzer.low()
lcd_message("Alarm Reset", "Monitoring...")
def start_server(ip):
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
server = socket.socket()
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(addr)
server.listen(1)
server.settimeout(0.05)
print("Open browser: http://" + ip)
return server
ip = connect_wifi()
server = start_server(ip)
lcd_message("System Ready", ip)
while True:
last_motion = pir.value()
last_distance = get_distance()
if is_armed:
if last_motion == 1 or last_distance < DIST_LIMIT_CM:
alarm_on = True
lcd_message("!! WARNING !!", "INTRUDER")
beep(2)
else:
lcd.move_to(0,0)
lcd.putstr("Status: Secure ")
lcd.move_to(0,1)
lcd.putstr(f"Dist:{last_distance:5.1f}cm ")
try:
client, addr = server.accept()
request = client.recv(1024).decode()
handle_request(request)
response = render_page()
client.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
client.send(response)
client.close()
except OSError:
pass
time.sleep(0.1)
💡 테스트 순서
먼저 SSID와 PASSWORD를 수정한 뒤 실행하세요. LCD에 표시된 IP 주소를 같은 Wi-Fi에 연결된 컴퓨터나 스마트폰 브라우저에 입력하면 제어 화면이 열립니다.
15.3 동작 흐름
시스템은 0.1초 간격으로 PIR과 초음파 센서를 읽습니다. 보안 모드가 켜져 있고 움직임이 감지되거나 거리가 20cm보다 가까워지면 LCD에 경고를 띄우고 부저를 울립니다. 웹 대시보드에서 DISARM을 누르면 감지를 잠시 끄고, ARM을 누르면 다시 감시를 시작합니다.
🏁 완강을 축하드립니다!
이제 여러분은 하드웨어 제어, 통신, 클라우드 연동까지 가능한 Pico 2 마스터가 되었습니다. 이 가이드북의 기초를 바탕으로 여러분만의 더 멋진 발명품을 만들어보세요!