06. MQTT ν”„λ‘œν† μ½œ

06. MQTT ν”„λ‘œν† μ½œ

ν•™μŠ΅ λͺ©ν‘œ

  • MQTT ν”„λ‘œν† μ½œμ˜ 원리와 νŠΉμ§• 이해
  • Mosquitto 브둜컀 μ„€μΉ˜ 및 μ„€μ •
  • Topic ꡬ쑰와 QoS 레벨 이해
  • paho-mqtt 라이브러리 μ‚¬μš©λ²• μŠ΅λ“
  • λ©”μ‹œμ§€ λ°œν–‰ 및 ꡬ독 κ΅¬ν˜„

1. MQTT ν”„λ‘œν† μ½œ κ°œμš”

1.1 MQTTλž€?

MQTT (Message Queuing Telemetry Transport)λŠ” κ²½λŸ‰ λ©”μ‹œμ§• ν”„λ‘œν† μ½œλ‘œ, IoT ν™˜κ²½μ— μ΅œμ ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    MQTT μ•„ν‚€ν…μ²˜                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚   Publisher                    Broker                        β”‚
β”‚   (μ„Όμ„œ)                       (μ€‘κ³„μž)                       β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚   β”‚ μ˜¨λ„    β”‚ ──PUBLISH────▢ β”‚         β”‚                   β”‚
β”‚   β”‚ μ„Όμ„œ    β”‚    (topic:     β”‚ Mosquittoβ”‚                   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   home/temp)   β”‚         β”‚                   β”‚
β”‚                               β”‚         β”‚                   β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”‚         β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚   β”‚ μŠ΅λ„    β”‚ ──PUBLISH────▢ β”‚         β”‚ ──▢│ λͺ¨λ°”일  β”‚    β”‚
β”‚   β”‚ μ„Όμ„œ    β”‚    (topic:     β”‚         β”‚    β”‚   μ•±    β”‚    β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   home/humid)  β”‚         β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                               β”‚         β”‚    Subscriber     β”‚
β”‚                               β”‚         β”‚                   β”‚
β”‚                               β”‚         β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚                               β”‚         β”‚ ──▢│ μ›Ή      β”‚    β”‚
β”‚                               β”‚         β”‚    β”‚ λŒ€μ‹œλ³΄λ“œβ”‚    β”‚
β”‚                               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

1.2 MQTT νŠΉμ§•

νŠΉμ§• μ„€λͺ…
κ²½λŸ‰ μ΅œμ†Œ 2λ°”μ΄νŠΈ 헀더 (HTTP λŒ€λΉ„ 맀우 μž‘μŒ)
Pub/Sub λ°œν–‰/ꡬ독 νŒ¨ν„΄ (λŠμŠ¨ν•œ κ²°ν•©)
QoS 3κ°€μ§€ λ©”μ‹œμ§€ 전달 보μž₯ μˆ˜μ€€
Last Will 비정상 μ—°κ²° μ’…λ£Œ μ‹œ μ•Œλ¦Ό
Retained λ§ˆμ§€λ§‰ λ©”μ‹œμ§€ μ €μž₯
Keep Alive μ—°κ²° μƒνƒœ λͺ¨λ‹ˆν„°λ§

1.3 MQTT vs HTTP 비ꡐ

# ν”„λ‘œν† μ½œ 비ꡐ
comparison = {
    "Header Size": {
        "MQTT": "2 bytes (μ΅œμ†Œ)",
        "HTTP": "~800 bytes (평균)"
    },
    "Pattern": {
        "MQTT": "Pub/Sub (비동기)",
        "HTTP": "Request/Response (동기)"
    },
    "Connection": {
        "MQTT": "지속 μ—°κ²°",
        "HTTP": "λΉ„μ—°κ²° (HTTP/1.1) λ˜λŠ” 지속 (HTTP/2)"
    },
    "Bidirectional": {
        "MQTT": "지원 (μ–‘λ°©ν–₯)",
        "HTTP": "μ„œλ²„ ν‘Έμ‹œ μ œν•œμ "
    },
    "Use Case": {
        "MQTT": "μ‹€μ‹œκ°„ μ„Όμ„œ, μ €μ „λ ₯, μ €λŒ€μ—­ν­",
        "HTTP": "μ›Ή API, λŒ€μš©λŸ‰ 데이터"
    }
}

2. Mosquitto 브둜컀

2.1 μ„€μΉ˜

# Ubuntu/Debian (라즈베리파이)
sudo apt update
sudo apt install mosquitto mosquitto-clients

# μ„œλΉ„μŠ€ μ‹œμž‘ 및 ν™œμ„±ν™”
sudo systemctl start mosquitto
sudo systemctl enable mosquitto

# μƒνƒœ 확인
sudo systemctl status mosquitto

2.2 κΈ°λ³Έ μ„€μ •

# μ„€μ • 파일 νŽΈμ§‘
sudo nano /etc/mosquitto/mosquitto.conf
# /etc/mosquitto/mosquitto.conf

# κΈ°λ³Έ μ„€μ •
pid_file /run/mosquitto/mosquitto.pid

# λ¦¬μŠ€λ„ˆ μ„€μ •
listener 1883
protocol mqtt

# 읡λͺ… 접속 (ν…ŒμŠ€νŠΈμš©)
allow_anonymous true

# 둜그 μ„€μ •
log_dest file /var/log/mosquitto/mosquitto.log
log_type all

# 지속성 (λ©”μ‹œμ§€ μ €μž₯)
persistence true
persistence_location /var/lib/mosquitto/

# μΆ”κ°€ μ„€μ • 파일 포함
include_dir /etc/mosquitto/conf.d

2.3 인증 μ„€μ •

# λΉ„λ°€λ²ˆν˜Έ 파일 생성
sudo mosquitto_passwd -c /etc/mosquitto/passwd iotuser

# μΆ”κ°€ μ‚¬μš©μž
sudo mosquitto_passwd /etc/mosquitto/passwd anotheruser
# /etc/mosquitto/conf.d/auth.conf

# 읡λͺ… 접속 λΉ„ν™œμ„±ν™”
allow_anonymous false

# λΉ„λ°€λ²ˆν˜Έ 파일
password_file /etc/mosquitto/passwd
# μ„€μ • 적용
sudo systemctl restart mosquitto

2.4 TLS μ„€μ • (λ³΄μ•ˆ μ—°κ²°)

# μΈμ¦μ„œ 생성 (자체 μ„œλͺ…)
mkdir -p ~/mqtt-certs && cd ~/mqtt-certs

# CA ν‚€ 및 μΈμ¦μ„œ
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -out ca.crt

# μ„œλ²„ ν‚€ 및 μΈμ¦μ„œ
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
# /etc/mosquitto/conf.d/tls.conf

listener 8883
protocol mqtt

cafile /home/pi/mqtt-certs/ca.crt
certfile /home/pi/mqtt-certs/server.crt
keyfile /home/pi/mqtt-certs/server.key

require_certificate false

2.5 CLI ν…ŒμŠ€νŠΈ

# 터미널 1: ꡬ독
mosquitto_sub -h localhost -t "test/topic" -v

# 터미널 2: λ°œν–‰
mosquitto_pub -h localhost -t "test/topic" -m "Hello MQTT!"

# 인증 포함
mosquitto_pub -h localhost -t "test/topic" -m "Hello" -u iotuser -P password

# QoS μ§€μ •
mosquitto_pub -h localhost -t "test/topic" -m "QoS 1" -q 1

3. Topicκ³Ό QoS

3.1 Topic ꡬ쑰

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    MQTT Topic ꡬ쑰                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚   계측적 ꡬ쑰 (μŠ¬λž˜μ‹œλ‘œ ꡬ뢄)                                 β”‚
β”‚                                                              β”‚
β”‚   home/                                                      β”‚
β”‚   β”œβ”€β”€ living-room/                                          β”‚
β”‚   β”‚   β”œβ”€β”€ temperature      β†’ κ±°μ‹€ μ˜¨λ„                      β”‚
β”‚   β”‚   β”œβ”€β”€ humidity         β†’ κ±°μ‹€ μŠ΅λ„                      β”‚
β”‚   β”‚   └── light            β†’ κ±°μ‹€ μ‘°λͺ…                      β”‚
β”‚   β”œβ”€β”€ bedroom/                                              β”‚
β”‚   β”‚   β”œβ”€β”€ temperature                                       β”‚
β”‚   β”‚   └── motion           β†’ μΉ¨μ‹€ λͺ¨μ…˜ μ„Όμ„œ                 β”‚
β”‚   └── kitchen/                                              β”‚
β”‚       └── smoke            β†’ μ£Όλ°© μ—°κΈ° 감지                 β”‚
β”‚                                                              β”‚
β”‚   μ™€μΌλ“œμΉ΄λ“œ:                                                β”‚
β”‚   β€’ + (단일 레벨): home/+/temperature                       β”‚
β”‚     β†’ home/living-room/temperature, home/bedroom/temperatureβ”‚
β”‚                                                              β”‚
β”‚   β€’ # (닀쀑 레벨): home/#                                    β”‚
β”‚     β†’ home μ•„λž˜ λͺ¨λ“  ν† ν”½                                   β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.2 Topic 섀계 κ°€μ΄λ“œ

# 쒋은 Topic 섀계 μ˜ˆμ‹œ
topic_examples = {
    # μœ„μΉ˜/μž₯치/μ„Όμ„œ
    "home/living-room/temperature": "κ±°μ‹€ μ˜¨λ„",
    "office/floor1/room101/hvac/status": "사무싀 HVAC μƒνƒœ",

    # μž₯치ID/λ°μ΄ν„°νƒ€μž…
    "sensor/abc123/data": "μ„Όμ„œ 데이터",
    "sensor/abc123/status": "μ„Όμ„œ μƒνƒœ",

    # λͺ…λ Ή 및 응닡
    "device/led001/command": "LED λͺ…λ Ή",
    "device/led001/response": "LED 응닡",

    # ν΄λΌμš°λ“œ 연동
    "aws/things/sensor001/shadow/update": "AWS IoT μ„€λ„μš°",
}

# ν”Όν•΄μ•Ό ν•  νŒ¨ν„΄
bad_patterns = [
    "/leading/slash",     # μ„ ν–‰ μŠ¬λž˜μ‹œ λΆˆν•„μš”
    "space in topic",     # 곡백 ν”Όν•˜κΈ°
    "UpperCase/Mixed",    # μ†Œλ¬Έμž ꢌμž₯
    "too/deep/hierarchy/a/b/c/d/e",  # κ³Όλ„ν•œ 깊이
]

3.3 QoS (Quality of Service)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    MQTT QoS 레벨                             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  QoS 0: At most once (μ΅œλŒ€ 1회)                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚
β”‚  β”‚Publisher│──PUBLISH──▢│Brokerβ”‚                             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚
β”‚  β€’ 전솑 확인 μ—†μŒ                                            β”‚
β”‚  β€’ κ°€μž₯ 빠름, λ©”μ‹œμ§€ 손싀 κ°€λŠ₯                               β”‚
β”‚  β€’ μš©λ„: μ„Όμ„œ 데이터 (손싀 ν—ˆμš©)                             β”‚
β”‚                                                              β”‚
β”‚  QoS 1: At least once (μ΅œμ†Œ 1회)                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚
β”‚  β”‚Publisher│──PUBLISH──▢│Brokerβ”‚                             β”‚
β”‚  β”‚        │◀──PUBACK───│      β”‚                             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚
β”‚  β€’ 확인 응닡, μž¬μ „μ†‘ κ°€λŠ₯                                    β”‚
β”‚  β€’ 쀑볡 κ°€λŠ₯, 손싀 μ—†μŒ                                      β”‚
β”‚  β€’ μš©λ„: μ€‘μš” μ•Œλ¦Ό                                          β”‚
β”‚                                                              β”‚
β”‚  QoS 2: Exactly once (μ •ν™•νžˆ 1회)                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚
β”‚  β”‚Publisher│──PUBLISH──▢│Brokerβ”‚                             β”‚
β”‚  β”‚        │◀──PUBREC───│      β”‚                             β”‚
β”‚  β”‚        │──PUBREL──▢│      β”‚                             β”‚
β”‚  β”‚        │◀──PUBCOMP──│      β”‚                             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚
β”‚  β€’ 4-way handshake                                          β”‚
β”‚  β€’ κ°€μž₯ 느림, μ •ν™•ν•œ 전달 보μž₯                               β”‚
β”‚  β€’ μš©λ„: 결제, μ€‘μš” λͺ…λ Ή                                    β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3.4 Retained λ©”μ‹œμ§€

# Retained λ©”μ‹œμ§€ κ°œλ…
"""
Retained Message:
- λΈŒλ‘œμ»€κ°€ λ§ˆμ§€λ§‰ λ©”μ‹œμ§€λ₯Ό μ €μž₯
- μƒˆ κ΅¬λ…μžκ°€ μ—°κ²° μ‹œ μ¦‰μ‹œ μˆ˜μ‹ 
- μ„Όμ„œ ν˜„μž¬ μƒνƒœ 전달에 유용

예:
1. μ„Όμ„œκ°€ μ˜¨λ„ 25도λ₯Ό retain=True둜 λ°œν–‰
2. λΈŒλ‘œμ»€κ°€ λ©”μ‹œμ§€ μ €μž₯
3. μƒˆ κ΅¬λ…μž μ—°κ²° μ‹œ μ¦‰μ‹œ 25도 μˆ˜μ‹ 
4. μ„Όμ„œ μ˜€ν”„λΌμΈμ΄μ–΄λ„ λ§ˆμ§€λ§‰ κ°’ μœ μ§€
"""

# μ‚¬μš© 예
retained_use_cases = {
    "μž₯치 μƒνƒœ": "device/sensor01/status (online/offline)",
    "ν˜„μž¬ κ°’": "home/temperature (λ§ˆμ§€λ§‰ μΈ‘μ •κ°’)",
    "μ„€μ •": "device/config (ν˜„μž¬ μ„€μ •)",
}

4. paho-mqtt 라이브러리

4.1 μ„€μΉ˜

pip install paho-mqtt

4.2 κΈ°λ³Έ Publisher

#!/usr/bin/env python3
"""MQTT Publisher 기본 예제"""

import paho.mqtt.client as mqtt
import json
import time

# 브둜컀 μ„€μ •
BROKER_HOST = "localhost"
BROKER_PORT = 1883
TOPIC = "sensor/temperature"

def on_connect(client, userdata, flags, rc):
    """μ—°κ²° 콜백"""
    if rc == 0:
        print("브둜컀 μ—°κ²° 성곡")
    else:
        print(f"μ—°κ²° μ‹€νŒ¨: {rc}")

def on_publish(client, userdata, mid):
    """λ°œν–‰ μ™„λ£Œ 콜백"""
    print(f"λ©”μ‹œμ§€ λ°œν–‰λ¨: mid={mid}")

def publish_sensor_data():
    """μ„Όμ„œ 데이터 λ°œν–‰"""
    client = mqtt.Client(client_id="temperature_sensor_01")
    client.on_connect = on_connect
    client.on_publish = on_publish

    # μ—°κ²°
    client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)
    client.loop_start()

    try:
        while True:
            # μ„Όμ„œ 데이터 생성
            data = {
                "sensor_id": "temp_01",
                "temperature": round(20 + (time.time() % 10), 1),
                "timestamp": int(time.time())
            }

            payload = json.dumps(data)

            # λ°œν–‰ (QoS 1, Retained μ‚¬μš©)
            result = client.publish(TOPIC, payload, qos=1, retain=True)

            if result.rc == mqtt.MQTT_ERR_SUCCESS:
                print(f"λ°œν–‰: {payload}")
            else:
                print(f"λ°œν–‰ μ‹€νŒ¨: {result.rc}")

            time.sleep(5)

    except KeyboardInterrupt:
        print("\nμ’…λ£Œ")
    finally:
        client.loop_stop()
        client.disconnect()

if __name__ == "__main__":
    publish_sensor_data()

4.3 κΈ°λ³Έ Subscriber

#!/usr/bin/env python3
"""MQTT Subscriber 기본 예제"""

import paho.mqtt.client as mqtt
import json

BROKER_HOST = "localhost"
BROKER_PORT = 1883
TOPICS = [
    ("sensor/temperature", 1),
    ("sensor/humidity", 1),
]

def on_connect(client, userdata, flags, rc):
    """μ—°κ²° 콜백"""
    if rc == 0:
        print("브둜컀 μ—°κ²° 성곡")
        # ν† ν”½ ꡬ독
        for topic, qos in TOPICS:
            client.subscribe(topic, qos)
            print(f"ꡬ독: {topic} (QoS {qos})")
    else:
        print(f"μ—°κ²° μ‹€νŒ¨: {rc}")

def on_message(client, userdata, msg):
    """λ©”μ‹œμ§€ μˆ˜μ‹  콜백"""
    try:
        payload = json.loads(msg.payload.decode())
        print(f"[{msg.topic}] {payload}")
    except json.JSONDecodeError:
        print(f"[{msg.topic}] {msg.payload.decode()}")

def on_disconnect(client, userdata, rc):
    """μ—°κ²° ν•΄μ œ 콜백"""
    print(f"μ—°κ²° ν•΄μ œ: {rc}")

def subscribe_sensors():
    """μ„Όμ„œ 데이터 ꡬ독"""
    client = mqtt.Client(client_id="sensor_monitor_01")
    client.on_connect = on_connect
    client.on_message = on_message
    client.on_disconnect = on_disconnect

    client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)

    try:
        client.loop_forever()
    except KeyboardInterrupt:
        print("\nμ’…λ£Œ")
        client.disconnect()

if __name__ == "__main__":
    subscribe_sensors()

4.4 인증 μ‚¬μš©

#!/usr/bin/env python3
"""MQTT 인증 μ—°κ²°"""

import paho.mqtt.client as mqtt
import ssl

BROKER_HOST = "mqtt.example.com"
BROKER_PORT = 8883  # TLS

def create_secure_client(username: str, password: str) -> mqtt.Client:
    """λ³΄μ•ˆ MQTT ν΄λΌμ΄μ–ΈνŠΈ 생성"""
    client = mqtt.Client(client_id="secure_client_01")

    # 인증 μ„€μ •
    client.username_pw_set(username, password)

    # TLS μ„€μ •
    client.tls_set(
        ca_certs="/path/to/ca.crt",
        certfile="/path/to/client.crt",  # ν΄λΌμ΄μ–ΈνŠΈ μΈμ¦μ„œ (μ˜΅μ…˜)
        keyfile="/path/to/client.key",   # ν΄λΌμ΄μ–ΈνŠΈ ν‚€ (μ˜΅μ…˜)
        tls_version=ssl.PROTOCOL_TLS
    )

    # 호슀트λͺ… 검증 λΉ„ν™œμ„±ν™” (자체 μ„œλͺ… μΈμ¦μ„œμš©)
    # client.tls_insecure_set(True)

    return client

def connect_secure():
    """λ³΄μ•ˆ μ—°κ²°"""
    client = create_secure_client("iotuser", "password123")

    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("λ³΄μ•ˆ μ—°κ²° 성곡")
        else:
            print(f"μ—°κ²° μ‹€νŒ¨: {rc}")

    client.on_connect = on_connect
    client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)
    client.loop_forever()

4.5 Last Will and Testament (LWT)

#!/usr/bin/env python3
"""MQTT Last Will (비정상 μ’…λ£Œ μ•Œλ¦Ό)"""

import paho.mqtt.client as mqtt
import time

def create_client_with_lwt(client_id: str) -> mqtt.Client:
    """LWTκ°€ μ„€μ •λœ ν΄λΌμ΄μ–ΈνŠΈ 생성"""
    client = mqtt.Client(client_id=client_id)

    # Last Will μ„€μ •
    # 비정상 μ—°κ²° μ’…λ£Œ μ‹œ 이 λ©”μ‹œμ§€κ°€ λ°œν–‰λ¨
    client.will_set(
        topic=f"device/{client_id}/status",
        payload="offline",
        qos=1,
        retain=True
    )

    return client

def run_sensor_with_lwt():
    """LWTκ°€ μžˆλŠ” μ„Όμ„œ μ‹€ν–‰"""
    client_id = "sensor_with_lwt"
    client = create_client_with_lwt(client_id)

    def on_connect(client, userdata, flags, rc):
        if rc == 0:
            print("연결됨")
            # 온라인 μƒνƒœ λ°œν–‰
            client.publish(
                f"device/{client_id}/status",
                "online",
                qos=1,
                retain=True
            )

    client.on_connect = on_connect
    client.connect("localhost", 1883, keepalive=60)
    client.loop_start()

    try:
        while True:
            client.publish(f"device/{client_id}/data", "sensor data")
            time.sleep(5)
    except KeyboardInterrupt:
        # 정상 μ’…λ£Œ μ‹œ μ˜€ν”„λΌμΈ μƒνƒœ λ°œν–‰
        client.publish(f"device/{client_id}/status", "offline", qos=1, retain=True)
        client.disconnect()

5. κ³ κΈ‰ νŒ¨ν„΄

5.1 λ©”μ‹œμ§€ λΌμš°νŒ…

#!/usr/bin/env python3
"""ν† ν”½ 기반 λ©”μ‹œμ§€ λΌμš°νŒ…"""

import paho.mqtt.client as mqtt
import json
from typing import Callable

class MQTTRouter:
    """MQTT λ©”μ‹œμ§€ λΌμš°ν„°"""

    def __init__(self, broker_host: str, broker_port: int = 1883):
        self.client = mqtt.Client()
        self.broker_host = broker_host
        self.broker_port = broker_port
        self.routes: dict[str, Callable] = {}

        self.client.on_connect = self._on_connect
        self.client.on_message = self._on_message

    def route(self, topic_pattern: str):
        """라우트 λ°μ½”λ ˆμ΄ν„°"""
        def decorator(func: Callable):
            self.routes[topic_pattern] = func
            return func
        return decorator

    def _on_connect(self, client, userdata, flags, rc):
        if rc == 0:
            print("λΌμš°ν„° 연결됨")
            for topic in self.routes.keys():
                client.subscribe(topic)
                print(f"라우트 등둝: {topic}")

    def _on_message(self, client, userdata, msg):
        # λ§€μΉ­λ˜λŠ” ν•Έλ“€λŸ¬ μ°ΎκΈ°
        for pattern, handler in self.routes.items():
            if mqtt.topic_matches_sub(pattern, msg.topic):
                try:
                    payload = json.loads(msg.payload.decode())
                except json.JSONDecodeError:
                    payload = msg.payload.decode()

                handler(msg.topic, payload)
                break

    def run(self):
        """λΌμš°ν„° μ‹€ν–‰"""
        self.client.connect(self.broker_host, self.broker_port)
        self.client.loop_forever()

# μ‚¬μš© 예
router = MQTTRouter("localhost")

@router.route("sensor/+/temperature")
def handle_temperature(topic: str, payload: dict):
    sensor_id = topic.split('/')[1]
    print(f"μ˜¨λ„ [{sensor_id}]: {payload}")

@router.route("sensor/+/humidity")
def handle_humidity(topic: str, payload: dict):
    sensor_id = topic.split('/')[1]
    print(f"μŠ΅λ„ [{sensor_id}]: {payload}")

@router.route("device/+/command")
def handle_command(topic: str, payload: dict):
    device_id = topic.split('/')[1]
    print(f"λͺ…λ Ή [{device_id}]: {payload}")

if __name__ == "__main__":
    router.run()

5.2 비동기 MQTT (asyncio)

#!/usr/bin/env python3
"""비동기 MQTT ν΄λΌμ΄μ–ΈνŠΈ (asyncio-mqtt)"""

import asyncio
import aiomqtt  # pip install aiomqtt
import json

async def publish_sensor_data():
    """비동기 λ°œν–‰"""
    async with aiomqtt.Client("localhost") as client:
        while True:
            data = {
                "temperature": 25.5,
                "timestamp": asyncio.get_event_loop().time()
            }
            await client.publish("sensor/temp", json.dumps(data))
            print(f"λ°œν–‰: {data}")
            await asyncio.sleep(5)

async def subscribe_sensor_data():
    """비동기 ꡬ독"""
    async with aiomqtt.Client("localhost") as client:
        async with client.messages() as messages:
            await client.subscribe("sensor/#")

            async for message in messages:
                print(f"[{message.topic}] {message.payload.decode()}")

async def main():
    """λ°œν–‰κ³Ό ꡬ독 λ™μ‹œ μ‹€ν–‰"""
    await asyncio.gather(
        publish_sensor_data(),
        subscribe_sensor_data()
    )

if __name__ == "__main__":
    asyncio.run(main())

5.3 μž¬μ—°κ²° 둜직

#!/usr/bin/env python3
"""μžλ™ μž¬μ—°κ²° MQTT ν΄λΌμ΄μ–ΈνŠΈ"""

import paho.mqtt.client as mqtt
import time

class RobustMQTTClient:
    """μžλ™ μž¬μ—°κ²°μ„ μ§€μ›ν•˜λŠ” MQTT ν΄λΌμ΄μ–ΈνŠΈ"""

    def __init__(self, broker_host: str, broker_port: int = 1883):
        self.broker_host = broker_host
        self.broker_port = broker_port
        self.client = mqtt.Client()
        self.connected = False
        self.reconnect_delay = 1
        self.max_reconnect_delay = 60

        self.client.on_connect = self._on_connect
        self.client.on_disconnect = self._on_disconnect

    def _on_connect(self, client, userdata, flags, rc):
        if rc == 0:
            print("연결됨")
            self.connected = True
            self.reconnect_delay = 1  # 리셋
        else:
            print(f"μ—°κ²° μ‹€νŒ¨: {rc}")

    def _on_disconnect(self, client, userdata, rc):
        print(f"μ—°κ²° ν•΄μ œ: {rc}")
        self.connected = False

        if rc != 0:
            self._reconnect()

    def _reconnect(self):
        """μž¬μ—°κ²° μ‹œλ„"""
        while not self.connected:
            try:
                print(f"μž¬μ—°κ²° μ‹œλ„... ({self.reconnect_delay}초 ν›„)")
                time.sleep(self.reconnect_delay)
                self.client.reconnect()
            except Exception as e:
                print(f"μž¬μ—°κ²° μ‹€νŒ¨: {e}")
                # μ§€μˆ˜ λ°±μ˜€ν”„
                self.reconnect_delay = min(
                    self.reconnect_delay * 2,
                    self.max_reconnect_delay
                )

    def connect(self):
        """초기 μ—°κ²°"""
        self.client.connect(self.broker_host, self.broker_port)

    def run(self):
        """ν΄λΌμ΄μ–ΈνŠΈ μ‹€ν–‰"""
        self.connect()
        self.client.loop_forever()

if __name__ == "__main__":
    client = RobustMQTTClient("localhost")
    client.run()

μ—°μŠ΅ 문제

문제 1: μ˜¨μŠ΅λ„ λͺ¨λ‹ˆν„°

  1. μ˜¨λ„μ™€ μŠ΅λ„ 데이터λ₯Ό 5μ΄ˆλ§ˆλ‹€ λ°œν–‰ν•˜λŠ” Publisherλ₯Ό μž‘μ„±ν•˜μ„Έμš”.
  2. ν•΄λ‹Ή 데이터λ₯Ό κ΅¬λ…ν•˜μ—¬ μ½˜μ†”μ— 좜λ ₯ν•˜λŠ” Subscriberλ₯Ό μž‘μ„±ν•˜μ„Έμš”.

문제 2: μž₯치 μƒνƒœ 관리

  1. LWTλ₯Ό μ‚¬μš©ν•˜μ—¬ μž₯치 온라인/μ˜€ν”„λΌμΈ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜μ„Έμš”.
  2. μ™€μΌλ“œμΉ΄λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λͺ¨λ“  μž₯치 μƒνƒœλ₯Ό λͺ¨λ‹ˆν„°λ§ν•˜μ„Έμš”.

문제 3: λͺ…λ Ή-응닡 μ‹œμŠ€ν…œ

  1. λͺ…λ Ή ν† ν”½μœΌλ‘œ LED μ œμ–΄ λͺ…령을 μˆ˜μ‹ ν•˜μ„Έμš”.
  2. 응닡 ν† ν”½μœΌλ‘œ μ‹€ν–‰ κ²°κ³Όλ₯Ό λ°œν–‰ν•˜μ„Έμš”.

λ‹€μŒ 단계


μ΅œμ’… μ—…λ°μ΄νŠΈ: 2026-02-01

to navigate between lessons