Chapter 11. MQTT와 Adafruit IO 클라우드 연동

IoT의 핵심 프로토콜인 MQTT를 학습합니다. 내 방의 센서 데이터를 전 세계 어디서든 스마트폰 대시보드로 모니터링할 수 있는 시스템을 구축합니다.

Pico 2 W MQTT 클라우드 흐름도 Pico가 센서 데이터를 Adafruit IO MQTT 브로커로 발행하고, 대시보드의 LED 명령을 구독해 제어합니다. 센서 노드 MQTT Broker 클라우드 대시보드 Pico 2 W DHT11 + LED Publish / Subscribe temperature humidity led/cmd Adafruit IO MQTT Broker feeds로 데이터 중계 24.5 C 58 % LED ON 스마트폰 / 웹 대시보드 Publish는 센서 데이터 전송, Subscribe는 클라우드 명령 수신입니다. 두 방향을 구분하면 IoT 구조가 훨씬 선명해집니다.

📡 MQTT란 무엇인가요?

MQTT(Message Queuing Telemetry Transport)는 IoT 기기처럼 배터리와 데이터 사용량이 제한된 장치를 위해 설계된 초경량 통신 프로토콜입니다. 웹사이트가 쓰는 HTTP보다 훨씬 가볍고, 실시간으로 값이 바뀔 때마다 즉시 전달할 수 있어 센서 데이터 전송에 매우 적합합니다.

MQTT 통신에는 3개의 역할이 있습니다.

  • Publisher(발행자) — 데이터를 보내는 쪽. 예) Pico가 측정한 온습도 값
  • Broker(브로커) — 메시지를 중계하는 서버. 이 챕터에서는 Adafruit IO가 브로커 역할을 합니다
  • Subscriber(구독자) — 데이터를 받는 쪽. 예) 스마트폰 대시보드, 혹은 명령을 받는 Pico 자신

중요한 점은 Publisher와 Subscriber가 서로의 IP 주소나 위치를 몰라도 된다는 것입니다. 모두가 브로커의 Topic(토픽)이라는 "채널"만 바라보고 통신하기 때문에, Pico 한 대가 데이터를 발행(publish)하면서 동시에 다른 토픽을 구독(subscribe)하는 양방향 통신도 자연스럽게 가능합니다.

11.1 Adafruit IO 설정 및 연결 구성

Adafruit IO는 무료로 사용할 수 있는 MQTT 브로커 겸 클라우드 대시보드 서비스입니다. 센서 데이터를 받아 그래프로 보여주고, 버튼이나 스위치 위젯으로 다시 기기에 명령을 내려보낼 수 있습니다. Adafruit IO에서는 각 데이터 채널을 Feed(피드)라고 부르며, MQTT 토픽 이름은 항상 사용자이름/feeds/피드이름 형식을 따릅니다.

1. Adafruit IO에 가입하여 AIO Key를 복사해 두세요.
2. Thonny의 [Tools] -> [Manage Packages]에서 umqtt.simple을 검색해 설치해야 합니다.

항목 설정값 역할
DHT11 DataGP15온습도 측정
상태 LEDLED클라우드 명령 확인
MQTT Feedtemperature, humidity, led-cmd데이터 전송과 원격 제어

11.2 MQTT 발행과 구독 소스 코드

import time
import machine
import network
import dht
from umqtt.simple import MQTTClient

# Wi-Fi & Adafruit IO 설정
WIFI_SSID = "your_wifi_name"
WIFI_PASS = "your_wifi_password"
AIO_USER = "your_username"
AIO_KEY = "your_aio_key"

BROKER = "io.adafruit.com"
CLIENT_ID = "pico2-weather-node"
TEMP_FEED = AIO_USER + "/feeds/temperature"
HUM_FEED = AIO_USER + "/feeds/humidity"
LED_CMD_FEED = AIO_USER + "/feeds/led-cmd"

sensor = dht.DHT11(machine.Pin(15))
led = machine.Pin("LED", machine.Pin.OUT)

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(WIFI_SSID, WIFI_PASS)
    print("Wi-Fi 연결 중...")

    for _ in range(20):
        if wlan.isconnected():
            print("Wi-Fi Connected:", wlan.ifconfig()[0])
            return
        time.sleep(0.5)

    raise RuntimeError("Wi-Fi connection failed")

def on_message(topic, msg):
    command = msg.decode().strip().lower()
    print("수신:", topic.decode(), command)

    if command in ("on", "1", "true"):
        led.on()
    elif command in ("off", "0", "false"):
        led.off()

def connect_mqtt():
    client = MQTTClient(CLIENT_ID, BROKER, user=AIO_USER, password=AIO_KEY)
    client.set_callback(on_message)
    client.connect()
    client.subscribe(LED_CMD_FEED)
    print("MQTT Connected and subscribed:", LED_CMD_FEED)
    return client

connect_wifi()
client = connect_mqtt()
last_publish = time.ticks_ms()

while True:
    try:
        # 구독한 led-cmd feed에 새 명령이 있는지 확인합니다.
        client.check_msg()

        if time.ticks_diff(time.ticks_ms(), last_publish) >= 10000:
            sensor.measure()
            temp = sensor.temperature()
            hum = sensor.humidity()

            client.publish(TEMP_FEED, str(temp))
            client.publish(HUM_FEED, str(hum))
            print(f"Published: {temp}C, {hum}%")
            last_publish = time.ticks_ms()

        time.sleep(0.1)

    except OSError as e:
        print("MQTT 오류, 재연결 시도:", e)
        time.sleep(3)
        connect_wifi()
        client = connect_mqtt()

💡 Feed 이름 확인

Adafruit IO에서 temperature, humidity, led-cmd feed를 먼저 만들어 두세요. 대시보드 버튼에서 led-cmdon 또는 off를 보내면 Pico의 내장 LED가 반응합니다.

11.3 동작 흐름

Pico는 10초마다 DHT11 값을 읽어 온도와 습도 feed로 발행합니다. 동시에 led-cmd feed를 구독하고 있다가 클라우드 대시보드에서 명령이 들어오면 내장 LED를 켜거나 끕니다. 이 구조가 IoT에서 가장 기본적인 양방향 통신 패턴입니다.

🏆 과제 해결해보기!

과제 1. 발행 주기 단축하고 새 Feed 추가하기

현재 코드는 10초마다 temperature, humidity 두 개의 feed만 발행합니다. 발행 주기를 5초로 줄이고, DHT11의 습도값이 70% 이상일 때 alert라는 이름의 새 feed에 문자열 "HIGH_HUMIDITY"를 추가로 발행하도록 코드를 수정해 보세요.

💻 정답 코드 보기
ALERT_FEED = AIO_USER + "/feeds/alert"

# ... 기존 while True 반복문 안, 발행 조건을 아래처럼 수정합니다.
if time.ticks_diff(time.ticks_ms(), last_publish) >= 5000:   # 10000 -> 5000으로 단축
    sensor.measure()
    temp = sensor.temperature()
    hum = sensor.humidity()

    client.publish(TEMP_FEED, str(temp))
    client.publish(HUM_FEED, str(hum))

    if hum >= 70:
        client.publish(ALERT_FEED, "HIGH_HUMIDITY")
        print("경고: 습도가 너무 높습니다!")

    print(f"Published: {temp}C, {hum}%")
    last_publish = time.ticks_ms()

과제 2. 여러 개의 명령어로 LED 밝기 제어하기

현재 led-cmd feed는 on/off 두 가지 명령만 처리합니다. PWM을 이용해 0~100 사이의 숫자 문자열을 받으면 LED 밝기를 그 값에 비례하도록 조절하는 on_message 콜백을 작성해 보세요. (힌트: machine.PWMduty_u16()을 사용합니다.)

💻 정답 코드 보기
from machine import Pin, PWM

led_pwm = PWM(Pin("LED"))
led_pwm.freq(1000)

def on_message(topic, msg):
    command = msg.decode().strip().lower()
    print("수신:", topic.decode(), command)

    if command in ("on", "1", "true"):
        led_pwm.duty_u16(65535)
    elif command in ("off", "0", "false"):
        led_pwm.duty_u16(0)
    elif command.isdigit():
        percent = max(0, min(100, int(command)))
        led_pwm.duty_u16(int(percent / 100 * 65535))

11.4 Wokwi 시뮬레이션으로 실습하기

Chapter 1(1.8)에서 소개한 Wokwi 온라인 시뮬레이터로 이 챕터의 코드 일부를 미리 테스트해볼 수 있습니다.

⚠️ Wokwi에서는 실제 MQTT 통신을 테스트할 수 없어요

Wokwi의 무료 시뮬레이션 Pico 보드는 기본적으로 실제 인터넷에 연결되지 않습니다. 따라서 network.WLAN으로 Wi-Fi에 접속하거나 umqtt.simple로 Adafruit IO 브로커에 실제로 연결하는 부분은 시뮬레이터에서 동작하지 않습니다.

대신 아래처럼 나누어 연습하는 것을 권장합니다.

  1. Wokwi에서 연습: DHT11 센서를 읽어 온도/습도 값을 출력하는 부분(sensor.measure(), sensor.temperature(), sensor.humidity())만 따로 떼어 로컬 동작을 확인합니다.
  2. 실제 하드웨어에서 확인: Wi-Fi 연결과 client.publish(), client.subscribe() 등 실제 MQTT 송수신은 반드시 진짜 Pico 2 W 보드와 실제 Wi-Fi 환경에서 테스트하세요.

💡 Tip: 아래는 DHT11 센서 읽기 로직만 검증할 수 있는 최소 diagram.json 예시입니다. Wokwi 화면에서 [+ New Project] → [MicroPython] 선택 후, 아래 diagram.json 내용을 프로젝트의 diagram 편집기에 붙여넣고, main.py에는 DHT11을 읽어 print()로 출력하는 부분만 넣어 초록색 ▶️ 재생 버튼을 눌러 실행하세요.

{
  "version": 1,
  "author": "Pico 2 Guide",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-pi-pico", "id": "pico", "top": 0, "left": 0, "attrs": { "env": "micropython-20231005-v1.21.0" } },
    { "type": "wokwi-dht22", "id": "dht1", "top": -40, "left": 100, "attrs": { "temperature": "24", "humidity": "55" } }
  ],
  "connections": [
    [ "pico:GP15", "dht1:SDA", "green", [] ],
    [ "dht1:VCC", "pico:3V3", "red", [] ],
    [ "dht1:GND", "pico:GND", "black", [] ]
  ]
}

💡 참고: Wokwi 부품 라이브러리에는 DHT11 전용 부품이 없어 wokwi-dht22 부품으로 대체했습니다(코드에서 dht.DHT11 대신 dht.DHT22를 사용하면 됩니다). 정확한 부품 이름은 Wokwi 편집기 좌측의 부품 검색창에서 다시 한번 확인하는 것을 권장합니다.