์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ MQTT ๋ธŒ๋กœ์ปค์™€ ํด๋ผ์ด์–ธํŠธ ์˜ˆ์ œ ์†Œ์Šค์ฝ”๋“œ

๐Ÿš€ MQTT ๋ธŒ๋กœ์ปค์™€ ํด๋ผ์ด์–ธํŠธ ์˜ˆ์ œ ์†Œ์Šค์ฝ”๋“œ

IoT ์„ธ์ƒ์˜ ๋ฉ”์‹ ์ €, MQTT๋ฅผ ์‹ค์ „ ์ฝ”๋“œ๋กœ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

์•ˆ๋…•, ์นœ๊ตฌ! ๐Ÿ˜Š ์˜ค๋Š˜์€ IoT ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  MQTT(Message Queuing Telemetry Transport)์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊นŒ ํ•ด. ์š”์ฆ˜ ์Šค๋งˆํŠธํ™ˆ, ์ž์œจ์ฃผํ–‰์ฐจ, ์‚ฐ์—…์šฉ ์„ผ์„œ ๋“ฑ ๊ฑฐ์˜ ๋ชจ๋“  IoT ๊ธฐ๊ธฐ๋“ค์ด MQTT๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ฑฐ๋“ . ๊ทธ๋Ÿฐ๋ฐ ๋ง‰์ƒ ์‹œ์ž‘ํ•˜๋ ค๋ฉด "์–ด๋””์„œ๋ถ€ํ„ฐ ์†๋Œ€์•ผ ํ•˜์ง€?" ํ•˜๋Š” ๋ง‰๋ง‰ํ•จ์ด ์žˆ์ž–์•„? ๊ทธ๋ž˜์„œ ์˜ค๋Š˜์€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ์˜ˆ์ œ ์†Œ์Šค์ฝ”๋“œ์™€ ํ•จ๊ป˜ MQTT ๋ธŒ๋กœ์ปค์™€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ •๋ณตํ•ด๋ณด๋ ค๊ณ  ํ•ด! ๐ŸŽฏ

MQTT Broker Message Hub Publisher Sensor Data Subscriber Display Device PUBLISH SUBSCRIBE ๐Ÿ“ฑ ๐Ÿ–ฅ๏ธ ๐Ÿ’ก

๐Ÿ“š MQTT๊ฐ€ ๋ญ๊ธธ๋ž˜ ์ด๋ ‡๊ฒŒ ์ธ๊ธฐ๊ฐ€ ๋งŽ์„๊นŒ?

MQTT๋Š” 1999๋…„ IBM์—์„œ ๊ฐœ๋ฐœํ•œ ๊ฒฝ๋Ÿ‰ ๋ฉ”์‹œ์ง• ํ”„๋กœํ† ์ฝœ์ด์•ผ. "๊ฒฝ๋Ÿ‰"์ด๋ผ๋Š” ๊ฒŒ ํ•ต์‹ฌ์ธ๋ฐ, HTTP๋ณด๋‹ค ํ›จ์”ฌ ์ ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„ ์•ˆ์ •์ ์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿชถ ํŠนํžˆ ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์ด ์ œํ•œ์ ์ด๊ฑฐ๋‚˜ ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์ด ์ค‘์š”ํ•œ IoT ํ™˜๊ฒฝ์—์„œ ๋น›์„ ๋ฐœํ•˜์ง€!

๐ŸŽฏ MQTT์˜ ํ•ต์‹ฌ ํŠน์ง• 1. Publish/Subscribe ๋ชจ๋ธ: ๋ฐœํ–‰์ž์™€ ๊ตฌ๋…์ž๊ฐ€ ์ง์ ‘ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š๊ณ  ๋ธŒ๋กœ์ปค๋ฅผ ํ†ตํ•ด ํ†ต์‹ ํ•ด
2. QoS(Quality of Service): ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ๋ณด์žฅ ์ˆ˜์ค€์„ 0, 1, 2๋กœ ์„ ํƒ ๊ฐ€๋Šฅ
3. ๊ฒฝ๋Ÿ‰ ํ”„๋กœํ† ์ฝœ: ์ตœ์†Œ 2๋ฐ”์ดํŠธ์˜ ํ—ค๋”๋กœ ํ†ต์‹  ๊ฐ€๋Šฅ
4. ์–‘๋ฐฉํ–ฅ ํ†ต์‹ : ํด๋ผ์ด์–ธํŠธ ๊ฐ„ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ตํ™˜
5. Last Will & Testament: ํด๋ผ์ด์–ธํŠธ ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹œ ์ž๋™ ์•Œ๋ฆผ

์‹ค์ œ๋กœ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ๋•Œ MQTT๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํšจ์œจ์ ์ด์•ผ. ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ์šด ์žฌ๋Šฅ ๊ฑฐ๋ž˜ ์š”์ฒญ์„ ๋ฐ›์•˜์„ ๋•Œ ์ฆ‰์‹œ ํ‘ธ์‹œ ์•Œ๋ฆผ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿ“ฒ

๐Ÿ—๏ธ MQTT ๋ธŒ๋กœ์ปค ์„ค์น˜ํ•˜๊ธฐ - Mosquitto

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹ค์Šต์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค MQTT ๋ธŒ๋กœ์ปค์ธ Mosquitto๋ฅผ ์„ค์น˜ํ•ด๋ณด์ž. ๋ฌด๋ฃŒ์ด๋ฉด์„œ๋„ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ฑฐ๋“ ! ๐Ÿ’ช

๐Ÿ’ก Tip: Mosquitto๋Š” Eclipse Foundation์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋กœ, ์ƒ์—…์  ์šฉ๋„๋กœ๋„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด๋ถ€ํ„ฐ ํด๋ผ์šฐ๋“œ ์„œ๋ฒ„๊นŒ์ง€ ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•ด!

๐Ÿง Linux/Ubuntu ์„ค์น˜

# Mosquitto ๋ธŒ๋กœ์ปค ์„ค์น˜
sudo apt-get update
sudo apt-get install mosquitto mosquitto-clients

# ์„œ๋น„์Šค ์‹œ์ž‘
sudo systemctl start mosquitto

# ๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹œ์ž‘ ์„ค์ •
sudo systemctl enable mosquitto

# ์ƒํƒœ ํ™•์ธ
sudo systemctl status mosquitto

๐ŸชŸ Windows ์„ค์น˜

Windows์—์„œ๋Š” ๊ณต์‹ ์›น์‚ฌ์ดํŠธ(mosquitto.org)์—์„œ ์ธ์Šคํ†จ๋Ÿฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ด์„œ ์„ค์น˜ํ•˜๋ฉด ๋ผ. ์„ค์น˜ ํ›„ ๋ช…๋ น ํ”„๋กฌํ”„ํŠธ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด:

# Mosquitto ๋ธŒ๋กœ์ปค ์‹คํ–‰
mosquitto -v

# ์„ค์ • ํŒŒ์ผ๊ณผ ํ•จ๊ป˜ ์‹คํ–‰
mosquitto -c mosquitto.conf -v

๐ŸŽ macOS ์„ค์น˜

# Homebrew๋ฅผ ์‚ฌ์šฉํ•œ ์„ค์น˜
brew install mosquitto

# ์„œ๋น„์Šค ์‹œ์ž‘
brew services start mosquitto

# ๋˜๋Š” ์ง์ ‘ ์‹คํ–‰
/usr/local/sbin/mosquitto -c /usr/local/etc/mosquitto/mosquitto.conf

โš™๏ธ Mosquitto ๋ธŒ๋กœ์ปค ์„ค์ •ํ•˜๊ธฐ

๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ๋„ ๋™์ž‘ํ•˜์ง€๋งŒ, ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ์„ ์œ„ํ•ด ์„ค์ •์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•ด์•ผ ํ•ด. ์„ค์ • ํŒŒ์ผ์€ ๋ณดํ†ต /etc/mosquitto/mosquitto.conf์— ์œ„์น˜ํ•ด ์žˆ์–ด. ๐Ÿ“

# mosquitto.conf ๊ธฐ๋ณธ ์„ค์ • ์˜ˆ์ œ

# ๋ฆฌ์Šค๋„ˆ ํฌํŠธ ์„ค์ • (๊ธฐ๋ณธ๊ฐ’: 1883)
listener 1883
listener 9001
protocol websockets

# ์ต๋ช… ์ ‘์† ํ—ˆ์šฉ ์—ฌ๋ถ€ (๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ true ๊ถŒ์žฅ)
allow_anonymous false

# ํŒจ์Šค์›Œ๋“œ ํŒŒ์ผ ๊ฒฝ๋กœ
password_file /etc/mosquitto/passwd

# ๋กœ๊ทธ ์„ค์ •
log_dest file /var/log/mosquitto/mosquitto.log
log_type all

# ์ตœ๋Œ€ ์—ฐ๊ฒฐ ์ˆ˜
max_connections 1000

# ๋ฉ”์‹œ์ง€ ํ ํฌ๊ธฐ
max_queued_messages 1000

# ์ง€์† ์—ฐ๊ฒฐ ์„ค์ •
persistence true
persistence_location /var/lib/mosquitto/

# ์ž๋™ ์ €์žฅ ๊ฐ„๊ฒฉ (์ดˆ)
autosave_interval 1800
โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ: ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ˜๋“œ์‹œ allow_anonymous false๋กœ ์„ค์ •ํ•˜๊ณ , ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ด. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ๋ธŒ๋กœ์ปค์— ์ ‘์†ํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋ณด์•ˆ ์œ„ํ—˜์ด ์ปค์ ธ!

๐Ÿ” ์‚ฌ์šฉ์ž ์ธ์ฆ ์„ค์ •

# ํŒจ์Šค์›Œ๋“œ ํŒŒ์ผ ์ƒ์„ฑ ๋ฐ ์‚ฌ์šฉ์ž ์ถ”๊ฐ€
sudo mosquitto_passwd -c /etc/mosquitto/passwd username1

# ์ถ”๊ฐ€ ์‚ฌ์šฉ์ž ๋“ฑ๋ก (-c ์˜ต์…˜ ์ œ์™ธ)
sudo mosquitto_passwd /etc/mosquitto/passwd username2

# ์‚ฌ์šฉ์ž ์‚ญ์ œ
sudo mosquitto_passwd -D /etc/mosquitto/passwd username1

# ๋ธŒ๋กœ์ปค ์žฌ์‹œ์ž‘
sudo systemctl restart mosquitto

๐Ÿ Python์œผ๋กœ MQTT ํด๋ผ์ด์–ธํŠธ ๋งŒ๋“ค๊ธฐ

์ด์ œ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ ์‹œ๊ฐ„์ด์•ผ! Python์˜ paho-mqtt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ๐ŸŽจ

๐Ÿ“ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

# pip๋ฅผ ์‚ฌ์šฉํ•œ ์„ค์น˜
pip install paho-mqtt

# ๋˜๋Š” ํŠน์ • ๋ฒ„์ „ ์„ค์น˜
pip install paho-mqtt==1.6.1

๐Ÿ“ค Publisher ํด๋ผ์ด์–ธํŠธ ์˜ˆ์ œ

๋จผ์ € ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐœํ–‰ํ•˜๋Š” Publisher๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ „์†กํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ฐ€์ •ํ• ๊ฒŒ:

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

# MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
BROKER_ADDRESS = "localhost"  # ๋˜๋Š” ๋ธŒ๋กœ์ปค IP ์ฃผ์†Œ
BROKER_PORT = 1883
TOPIC = "sensors/temperature"
CLIENT_ID = "temperature_sensor_01"

# ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
USERNAME = "your_username"
PASSWORD = "your_password"

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜: ์—ฐ๊ฒฐ ์„ฑ๊ณต ์‹œ
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… ๋ธŒ๋กœ์ปค์— ์„ฑ๊ณต์ ์œผ๋กœ ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ. ๋ฐ˜ํ™˜ ์ฝ”๋“œ: {rc}")

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜: ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ์™„๋ฃŒ ์‹œ
def on_publish(client, userdata, mid):
    print(f"๐Ÿ“ค ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ์™„๋ฃŒ (Message ID: {mid})")

# ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
client = mqtt.Client(CLIENT_ID)

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋“ฑ๋ก
client.on_connect = on_connect
client.on_publish = on_publish

# ์‚ฌ์šฉ์ž ์ธ์ฆ ์„ค์ • (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
client.username_pw_set(USERNAME, PASSWORD)

# ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
try:
    client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
    client.loop_start()  # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ ์‹œ์ž‘
    
    # ์˜จ๋„ ๋ฐ์ดํ„ฐ ์ฃผ๊ธฐ์  ๋ฐœํ–‰
    for i in range(10):
        # ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)
        temperature = round(20 + random.uniform(-5, 5), 2)
        humidity = round(50 + random.uniform(-10, 10), 2)
        
        # JSON ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ
        payload = {
            "sensor_id": CLIENT_ID,
            "timestamp": time.time(),
            "temperature": temperature,
            "humidity": humidity,
            "unit": "celsius"
        }
        
        # ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ (QoS 1)
        result = client.publish(
            TOPIC,
            json.dumps(payload),
            qos=1,
            retain=False
        )
        
        # ๋ฐœํ–‰ ๊ฒฐ๊ณผ ํ™•์ธ
        if result.rc == mqtt.MQTT_ERR_SUCCESS:
            print(f"๐ŸŒก๏ธ  ์˜จ๋„: {temperature}ยฐC, ์Šต๋„: {humidity}% ๋ฐœํ–‰")
        else:
            print(f"โš ๏ธ  ๋ฐœํ–‰ ์‹คํŒจ: {result.rc}")
        
        time.sleep(2)  # 2์ดˆ ๋Œ€๊ธฐ
    
    # ์ •๋ฆฌ
    client.loop_stop()
    client.disconnect()
    print("๐Ÿ”Œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ")
    
except Exception as e:
    print(f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
๐Ÿ’ก QoS ๋ ˆ๋ฒจ ์„ค๋ช…: QoS 0: ์ตœ๋Œ€ 1๋ฒˆ ์ „๋‹ฌ (Fire and Forget) - ๊ฐ€์žฅ ๋น ๋ฅด์ง€๋งŒ ๋ฉ”์‹œ์ง€ ์†์‹ค ๊ฐ€๋Šฅ
QoS 1: ์ตœ์†Œ 1๋ฒˆ ์ „๋‹ฌ - ๋ฉ”์‹œ์ง€ ์ค‘๋ณต ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์†์‹ค ์—†์Œ
QoS 2: ์ •ํ™•ํžˆ 1๋ฒˆ ์ „๋‹ฌ - ๊ฐ€์žฅ ๋А๋ฆฌ์ง€๋งŒ ์ค‘๋ณต๊ณผ ์†์‹ค ๋ชจ๋‘ ๋ฐฉ์ง€

๐Ÿ“ฅ Subscriber ํด๋ผ์ด์–ธํŠธ ์˜ˆ์ œ

์ด๋ฒˆ์—” ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ๋…ํ•˜๋Š” Subscriber๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊ฒŒ. ์‹ค์‹œ๊ฐ„์œผ๋กœ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ์•ผ:

import paho.mqtt.client as mqtt
import json
from datetime import datetime

# MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
BROKER_ADDRESS = "localhost"
BROKER_PORT = 1883
TOPIC = "sensors/#"  # ์™€์ผ๋“œ์นด๋“œ๋กœ ๋ชจ๋“  ์„ผ์„œ ๊ตฌ๋…
CLIENT_ID = "data_receiver_01"

USERNAME = "your_username"
PASSWORD = "your_password"

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜: ์—ฐ๊ฒฐ ์„ฑ๊ณต ์‹œ
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
        # ํ† ํ”ฝ ๊ตฌ๋…
        client.subscribe(TOPIC, qos=1)
        print(f"๐Ÿ“ก ํ† ํ”ฝ ๊ตฌ๋… ์‹œ์ž‘: {TOPIC}")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ. ์ฝ”๋“œ: {rc}")

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜: ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์‹œ
def on_message(client, userdata, message):
    try:
        # ๋ฉ”์‹œ์ง€ ๋””์ฝ”๋”ฉ
        payload = message.payload.decode('utf-8')
        topic = message.topic
        
        print(f"\n{'='*50}")
        print(f"๐Ÿ“ฌ ์ƒˆ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ !")
        print(f"๐Ÿ“ ํ† ํ”ฝ: {topic}")
        print(f"๐Ÿ”ข QoS: {message.qos}")
        
        # JSON ํŒŒ์‹ฑ
        data = json.loads(payload)
        
        # ํƒ€์ž„์Šคํƒฌํ”„ ๋ณ€ํ™˜
        timestamp = datetime.fromtimestamp(data['timestamp'])
        
        # ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
        print(f"๐Ÿ†” ์„ผ์„œ ID: {data['sensor_id']}")
        print(f"โฐ ์‹œ๊ฐ„: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"๐ŸŒก๏ธ  ์˜จ๋„: {data['temperature']}ยฐC")
        print(f"๐Ÿ’ง ์Šต๋„: {data['humidity']}%")
        print(f"{'='*50}\n")
        
        # ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ €์žฅ, ์•Œ๋ฆผ ์ „์†ก ๋“ฑ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
        process_sensor_data(data)
        
    except json.JSONDecodeError:
        print(f"โš ๏ธ  JSON ํŒŒ์‹ฑ ์‹คํŒจ: {payload}")
    except Exception as e:
        print(f"โŒ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜: ๊ตฌ๋… ์™„๋ฃŒ ์‹œ
def on_subscribe(client, userdata, mid, granted_qos):
    print(f"โœ… ๊ตฌ๋… ์™„๋ฃŒ! (QoS: {granted_qos[0]})")

# ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
def process_sensor_data(data):
    """
    ์ˆ˜์‹ ํ•œ ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ํ•จ์ˆ˜
    ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์—ฌ๊ธฐ์„œ DB ์ €์žฅ, ์•Œ๋ฆผ ๋“ฑ์„ ์ˆ˜ํ–‰
    """
    # ์˜จ๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
    if data['temperature'] > 30:
        print("๐Ÿ”ฅ ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!")
    elif data['temperature'] < 10:
        print("โ„๏ธ  ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!")
    
    # ์Šต๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
    if data['humidity'] > 70:
        print("๐Ÿ’ฆ ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!")
    elif data['humidity'] < 30:
        print("๐Ÿœ๏ธ  ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!")

# ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
client = mqtt.Client(CLIENT_ID)

# ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋“ฑ๋ก
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe

# ์‚ฌ์šฉ์ž ์ธ์ฆ
client.username_pw_set(USERNAME, PASSWORD)

# ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ๋ฐ ๋ฃจํ”„ ์‹œ์ž‘
try:
    client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
    print("๐Ÿ”„ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋Œ€๊ธฐ ์ค‘... (Ctrl+C๋กœ ์ข…๋ฃŒ)")
    client.loop_forever()  # ๋ฌดํ•œ ๋ฃจํ”„๋กœ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
    
except KeyboardInterrupt:
    print("\nโน๏ธ  ์‚ฌ์šฉ์ž์— ์˜ํ•ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
    client.disconnect()
except Exception as e:
    print(f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")

โ˜• Java๋กœ MQTT ํด๋ผ์ด์–ธํŠธ ๊ตฌํ˜„ํ•˜๊ธฐ

Java ๊ฐœ๋ฐœ์ž๋ผ๋ฉด? Eclipse Paho Java ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ! ์•ˆ๋“œ๋กœ์ด๋“œ ์•ฑ ๊ฐœ๋ฐœ์—๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ •๋ง ์œ ์šฉํ•˜์ง€. ๐Ÿ“ฑ

๐Ÿ“ฆ Maven ์˜์กด์„ฑ ์ถ”๊ฐ€

<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.5</version>
</dependency>

๐Ÿ“ค Java Publisher ์˜ˆ์ œ

import org.eclipse.paho.client.mqttv3.*;
import org.json.JSONObject;
import java.util.Random;

public class MqttPublisher {
    
    private static final String BROKER = "tcp://localhost:1883";
    private static final String CLIENT_ID = "JavaPublisher";
    private static final String TOPIC = "sensors/temperature";
    private static final String USERNAME = "your_username";
    private static final String PASSWORD = "your_password";
    
    public static void main(String[] args) {
        try {
            // MQTT ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
            MqttClient client = new MqttClient(BROKER, CLIENT_ID);
            
            // ์—ฐ๊ฒฐ ์˜ต์…˜ ์„ค์ •
            MqttConnectOptions options = new MqttConnectOptions();
            options.setUserName(USERNAME);
            options.setPassword(PASSWORD.toCharArray());
            options.setCleanSession(true);
            options.setKeepAliveInterval(60);
            options.setAutomaticReconnect(true);
            
            // ์ฝœ๋ฐฑ ์„ค์ •
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    System.out.println("โŒ ์—ฐ๊ฒฐ ๋Š๊น€: " + cause.getMessage());
                }
                
                @Override
                public void messageArrived(String topic, MqttMessage message) {
                    // Publisher๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์ง€ ์•Š์Œ
                }
                
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    System.out.println("โœ… ๋ฉ”์‹œ์ง€ ์ „๋‹ฌ ์™„๋ฃŒ");
                }
            });
            
            // ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
            System.out.println("๐Ÿ”Œ ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ค‘...");
            client.connect(options);
            System.out.println("โœ… ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
            
            Random random = new Random();
            
            // 10๋ฒˆ ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
            for (int i = 0; i < 10; i++) {
                // ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
                double temperature = 20 + (random.nextDouble() * 10 - 5);
                double humidity = 50 + (random.nextDouble() * 20 - 10);
                
                // JSON ๊ฐ์ฒด ์ƒ์„ฑ
                JSONObject payload = new JSONObject();
                payload.put("sensor_id", CLIENT_ID);
                payload.put("timestamp", System.currentTimeMillis() / 1000.0);
                payload.put("temperature", Math.round(temperature * 100.0) / 100.0);
                payload.put("humidity", Math.round(humidity * 100.0) / 100.0);
                payload.put("unit", "celsius");
                
                // MQTT ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
                MqttMessage message = new MqttMessage(payload.toString().getBytes());
                message.setQos(1);
                message.setRetained(false);
                
                // ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
                client.publish(TOPIC, message);
                System.out.println("๐Ÿ“ค ๋ฐœํ–‰: " + payload.toString());
                
                Thread.sleep(2000);  // 2์ดˆ ๋Œ€๊ธฐ
            }
            
            // ์—ฐ๊ฒฐ ์ข…๋ฃŒ
            client.disconnect();
            System.out.println("๐Ÿ”Œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ");
            
        } catch (MqttException e) {
            System.err.println("โŒ MQTT ์˜ค๋ฅ˜: " + e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.err.println("โŒ ์ธํ„ฐ๋ŸฝํŠธ ์˜ค๋ฅ˜: " + e.getMessage());
        }
    }
}

๐Ÿ“ฅ Java Subscriber ์˜ˆ์ œ

import org.eclipse.paho.client.mqttv3.*;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MqttSubscriber {
    
    private static final String BROKER = "tcp://localhost:1883";
    private static final String CLIENT_ID = "JavaSubscriber";
    private static final String TOPIC = "sensors/#";
    private static final String USERNAME = "your_username";
    private static final String PASSWORD = "your_password";
    
    public static void main(String[] args) {
        try {
            // MQTT ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
            MqttClient client = new MqttClient(BROKER, CLIENT_ID);
            
            // ์—ฐ๊ฒฐ ์˜ต์…˜ ์„ค์ •
            MqttConnectOptions options = new MqttConnectOptions();
            options.setUserName(USERNAME);
            options.setPassword(PASSWORD.toCharArray());
            options.setCleanSession(true);
            options.setKeepAliveInterval(60);
            options.setAutomaticReconnect(true);
            
            // ์ฝœ๋ฐฑ ์„ค์ •
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    System.out.println("โŒ ์—ฐ๊ฒฐ ๋Š๊น€: " + cause.getMessage());
                    System.out.println("๐Ÿ”„ ์ž๋™ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...");
                }
                
                @Override
                public void messageArrived(String topic, MqttMessage message) {
                    try {
                        String payload = new String(message.getPayload());
                        
                        System.out.println("\n" + "=".repeat(50));
                        System.out.println("๐Ÿ“ฌ ์ƒˆ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ !");
                        System.out.println("๐Ÿ“ ํ† ํ”ฝ: " + topic);
                        System.out.println("๐Ÿ”ข QoS: " + message.getQos());
                        
                        // JSON ํŒŒ์‹ฑ
                        JSONObject data = new JSONObject(payload);
                        
                        // ํƒ€์ž„์Šคํƒฌํ”„ ๋ณ€ํ™˜
                        long timestamp = (long)(data.getDouble("timestamp") * 1000);
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        String dateTime = sdf.format(new Date(timestamp));
                        
                        // ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
                        System.out.println("๐Ÿ†” ์„ผ์„œ ID: " + data.getString("sensor_id"));
                        System.out.println("โฐ ์‹œ๊ฐ„: " + dateTime);
                        System.out.println("๐ŸŒก๏ธ  ์˜จ๋„: " + data.getDouble("temperature") + "ยฐC");
                        System.out.println("๐Ÿ’ง ์Šต๋„: " + data.getDouble("humidity") + "%");
                        System.out.println("=".repeat(50) + "\n");
                        
                        // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
                        processSensorData(data);
                        
                    } catch (Exception e) {
                        System.err.println("โš ๏ธ  ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: " + e.getMessage());
                    }
                }
                
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    // Subscriber๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด์ง€ ์•Š์Œ
                }
            });
            
            // ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
            System.out.println("๐Ÿ”Œ ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ค‘...");
            client.connect(options);
            System.out.println("โœ… ์—ฐ๊ฒฐ ์„ฑ๊ณต!");
            
            // ํ† ํ”ฝ ๊ตฌ๋…
            client.subscribe(TOPIC, 1);
            System.out.println("๐Ÿ“ก ํ† ํ”ฝ ๊ตฌ๋…: " + TOPIC);
            System.out.println("๐Ÿ”„ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋Œ€๊ธฐ ์ค‘...\n");
            
            // ํ”„๋กœ๊ทธ๋žจ ๊ณ„์† ์‹คํ–‰
            while (true) {
                Thread.sleep(1000);
            }
            
        } catch (MqttException e) {
            System.err.println("โŒ MQTT ์˜ค๋ฅ˜: " + e.getMessage());
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.err.println("โŒ ์ธํ„ฐ๋ŸฝํŠธ ์˜ค๋ฅ˜: " + e.getMessage());
        }
    }
    
    private static void processSensorData(JSONObject data) {
        double temperature = data.getDouble("temperature");
        double humidity = data.getDouble("humidity");
        
        // ์˜จ๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
        if (temperature > 30) {
            System.out.println("๐Ÿ”ฅ ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!");
        } else if (temperature < 10) {
            System.out.println("โ„๏ธ  ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!");
        }
        
        // ์Šต๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
        if (humidity > 70) {
            System.out.println("๐Ÿ’ฆ ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!");
        } else if (humidity < 30) {
            System.out.println("๐Ÿœ๏ธ  ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!");
        }
    }
}

๐ŸŒ Node.js๋กœ MQTT ํด๋ผ์ด์–ธํŠธ ๋งŒ๋“ค๊ธฐ

์›น ๊ฐœ๋ฐœ์ž๋ผ๋ฉด Node.js๊ฐ€ ์ต์ˆ™ํ•˜๊ฒ ์ง€? JavaScript๋กœ๋„ MQTT ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ์›น์†Œ์ผ“์„ ํ†ตํ•œ ๋ธŒ๋ผ์šฐ์ € ์—ฐ๋™๋„ ๊ฐ€๋Šฅํ•ด์„œ ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌ์ถ•์— ์ตœ์ ์ด์•ผ! ๐Ÿš€

๐Ÿ“ฆ ํŒจํ‚ค์ง€ ์„ค์น˜

# npm์„ ์‚ฌ์šฉํ•œ ์„ค์น˜
npm install mqtt

# ๋˜๋Š” yarn ์‚ฌ์šฉ
yarn add mqtt

๐Ÿ“ค Node.js Publisher ์˜ˆ์ œ

const mqtt = require('mqtt');

// MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
const BROKER_URL = 'mqtt://localhost:1883';
const CLIENT_ID = 'NodePublisher_' + Math.random().toString(16).substr(2, 8);
const TOPIC = 'sensors/temperature';

// ์—ฐ๊ฒฐ ์˜ต์…˜
const options = {
    clientId: CLIENT_ID,
    username: 'your_username',
    password: 'your_password',
    clean: true,
    keepalive: 60,
    reconnectPeriod: 1000
};

// ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
console.log('๐Ÿ”Œ ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ค‘...');
const client = mqtt.connect(BROKER_URL, options);

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์—ฐ๊ฒฐ ์„ฑ๊ณต
client.on('connect', () => {
    console.log('โœ… ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์„ฑ๊ณต!');
    console.log(`๐Ÿ†” ํด๋ผ์ด์–ธํŠธ ID: ${CLIENT_ID}`);
    
    let count = 0;
    const maxCount = 10;
    
    // 2์ดˆ๋งˆ๋‹ค ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
    const interval = setInterval(() => {
        // ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
        const temperature = (20 + (Math.random() * 10 - 5)).toFixed(2);
        const humidity = (50 + (Math.random() * 20 - 10)).toFixed(2);
        
        // JSON ํŽ˜์ด๋กœ๋“œ ์ƒ์„ฑ
        const payload = JSON.stringify({
            sensor_id: CLIENT_ID,
            timestamp: Date.now() / 1000,
            temperature: parseFloat(temperature),
            humidity: parseFloat(humidity),
            unit: 'celsius'
        });
        
        // ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
        client.publish(TOPIC, payload, { qos: 1, retain: false }, (error) => {
            if (error) {
                console.error('โŒ ๋ฐœํ–‰ ์‹คํŒจ:', error);
            } else {
                console.log(`๐Ÿ“ค ๋ฐœํ–‰ [${count + 1}/${maxCount}]: ์˜จ๋„ ${temperature}ยฐC, ์Šต๋„ ${humidity}%`);
            }
        });
        
        count++;
        
        // 10๋ฒˆ ๋ฐœํ–‰ ํ›„ ์ข…๋ฃŒ
        if (count >= maxCount) {
            clearInterval(interval);
            setTimeout(() => {
                client.end();
                console.log('๐Ÿ”Œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ');
            }, 1000);
        }
    }, 2000);
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์˜ค๋ฅ˜ ๋ฐœ์ƒ
client.on('error', (error) => {
    console.error('โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์—ฐ๊ฒฐ ๋Š๊น€
client.on('close', () => {
    console.log('๐Ÿ”Œ ์—ฐ๊ฒฐ์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์žฌ์—ฐ๊ฒฐ ์‹œ๋„
client.on('reconnect', () => {
    console.log('๐Ÿ”„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...');
});

๐Ÿ“ฅ Node.js Subscriber ์˜ˆ์ œ

const mqtt = require('mqtt');

// MQTT ๋ธŒ๋กœ์ปค ์„ค์ •
const BROKER_URL = 'mqtt://localhost:1883';
const CLIENT_ID = 'NodeSubscriber_' + Math.random().toString(16).substr(2, 8);
const TOPIC = 'sensors/#';

// ์—ฐ๊ฒฐ ์˜ต์…˜
const options = {
    clientId: CLIENT_ID,
    username: 'your_username',
    password: 'your_password',
    clean: true,
    keepalive: 60,
    reconnectPeriod: 1000
};

// ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ
console.log('๐Ÿ”Œ ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ค‘...');
const client = mqtt.connect(BROKER_URL, options);

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์—ฐ๊ฒฐ ์„ฑ๊ณต
client.on('connect', () => {
    console.log('โœ… ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์„ฑ๊ณต!');
    console.log(`๐Ÿ†” ํด๋ผ์ด์–ธํŠธ ID: ${CLIENT_ID}`);
    
    // ํ† ํ”ฝ ๊ตฌ๋…
    client.subscribe(TOPIC, { qos: 1 }, (error) => {
        if (error) {
            console.error('โŒ ๊ตฌ๋… ์‹คํŒจ:', error);
        } else {
            console.log(`๐Ÿ“ก ํ† ํ”ฝ ๊ตฌ๋…: ${TOPIC}`);
            console.log('๐Ÿ”„ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ๋Œ€๊ธฐ ์ค‘...\n');
        }
    });
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ 
client.on('message', (topic, message) => {
    try {
        const payload = message.toString();
        
        console.log('\n' + '='.repeat(50));
        console.log('๐Ÿ“ฌ ์ƒˆ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ !');
        console.log(`๐Ÿ“ ํ† ํ”ฝ: ${topic}`);
        
        // JSON ํŒŒ์‹ฑ
        const data = JSON.parse(payload);
        
        // ํƒ€์ž„์Šคํƒฌํ”„ ๋ณ€ํ™˜
        const date = new Date(data.timestamp * 1000);
        const dateTime = date.toLocaleString('ko-KR', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit'
        });
        
        // ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ
        console.log(`๐Ÿ†” ์„ผ์„œ ID: ${data.sensor_id}`);
        console.log(`โฐ ์‹œ๊ฐ„: ${dateTime}`);
        console.log(`๐ŸŒก๏ธ  ์˜จ๋„: ${data.temperature}ยฐC`);
        console.log(`๐Ÿ’ง ์Šต๋„: ${data.humidity}%`);
        console.log('='.repeat(50) + '\n');
        
        // ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ
        processSensorData(data);
        
    } catch (error) {
        console.error('โš ๏ธ  ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜:', error.message);
    }
});

// ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
function processSensorData(data) {
    const { temperature, humidity } = data;
    
    // ์˜จ๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
    if (temperature > 30) {
        console.log('๐Ÿ”ฅ ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!');
        // ์—ฌ๊ธฐ์„œ ์•Œ๋ฆผ ์ „์†ก, ๋กœ๊ทธ ๊ธฐ๋ก ๋“ฑ ์ˆ˜ํ–‰
    } else if (temperature < 10) {
        console.log('โ„๏ธ  ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!');
    }
    
    // ์Šต๋„ ์ž„๊ณ„๊ฐ’ ์ฒดํฌ
    if (humidity > 70) {
        console.log('๐Ÿ’ฆ ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!');
    } else if (humidity < 30) {
        console.log('๐Ÿœ๏ธ  ๊ฒฝ๊ณ : ์Šต๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!');
    }
}

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์˜ค๋ฅ˜ ๋ฐœ์ƒ
client.on('error', (error) => {
    console.error('โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์—ฐ๊ฒฐ ๋Š๊น€
client.on('close', () => {
    console.log('๐Ÿ”Œ ์—ฐ๊ฒฐ์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
});

// ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ: ์žฌ์—ฐ๊ฒฐ ์‹œ๋„
client.on('reconnect', () => {
    console.log('๐Ÿ”„ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...');
});

// ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์ฒ˜๋ฆฌ
process.on('SIGINT', () => {
    console.log('\nโน๏ธ  ํ”„๋กœ๊ทธ๋žจ ์ข…๋ฃŒ ์ค‘...');
    client.end();
    process.exit();
});

๐ŸŽจ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ MQTT ์‚ฌ์šฉํ•˜๊ธฐ

์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ MQTT๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด WebSocket ํ”„๋กœํ† ์ฝœ์„ ํ™œ์šฉํ•ด์•ผ ํ•ด. ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ๋‚˜ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ๋•Œ ์ •๋ง ์œ ์šฉํ•˜์ง€! ๐Ÿ–ฅ๏ธ

๐Ÿ’ก WebSocket ์„ค์ •: Mosquitto ๋ธŒ๋กœ์ปค์—์„œ WebSocket์„ ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด ์„ค์ • ํŒŒ์ผ์— listener 9001๊ณผ protocol websockets๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด!

๐ŸŒ HTML + JavaScript ์˜ˆ์ œ

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MQTT ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง</title>
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
            background: #f5f6fa;
        }
        .container {
            background: white;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0 5px 15px rgba(0,0,0,0.1);
        }
        .status {
            padding: 15px;
            border-radius: 8px;
            margin: 15px 0;
            font-weight: bold;
        }
        .connected {
            background: #d4edda;
            color: #155724;
        }
        .disconnected {
            background: #f8d7da;
            color: #721c24;
        }
        .data-box {
            background: #e3f2fd;
            padding: 20px;
            border-radius: 10px;
            margin: 15px 0;
        }
        .temperature {
            font-size: 48px;
            color: #ff6b6b;
            font-weight: bold;
        }
        .humidity {
            font-size: 48px;
            color: #4ecdc4;
            font-weight: bold;
        }
        button {
            background: #6c5ce7;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 16px;
            margin: 5px;
        }
        button:hover {
            background: #5f4dd1;
        }
        #log {
            background: #2d3436;
            color: #dfe6e9;
            padding: 15px;
            border-radius: 8px;
            max-height: 300px;
            overflow-y: auto;
            font-family: 'Courier New', monospace;
            font-size: 13px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1 style="text-align:center; color:#6c5ce7;">๐ŸŒก๏ธ MQTT ์‹ค์‹œ๊ฐ„ ์„ผ์„œ ๋ชจ๋‹ˆํ„ฐ๋ง</h1>
        
        <div id="status" class="status disconnected">
            โŒ ์—ฐ๊ฒฐ ์•ˆ๋จ
        </div>
        
        <div style="text-align:center;">
            <button onclick="connect()">๐Ÿ”Œ ์—ฐ๊ฒฐ</button>
            <button onclick="disconnect()">๐Ÿ”Œ ์—ฐ๊ฒฐ ํ•ด์ œ</button>
        </div>
        
        <div class="data-box">
            <h3>๐Ÿ“Š ์ตœ์‹  ์„ผ์„œ ๋ฐ์ดํ„ฐ</h3>
            <div style="display:flex; justify-content:space-around;">
                <div style="text-align:center;">
                    <div>๐ŸŒก๏ธ ์˜จ๋„</div>
                    <div class="temperature" id="temperature">--</div>
                    <div>ยฐC</div>
                </div>
                <div style="text-align:center;">
                    <div>๐Ÿ’ง ์Šต๋„</div>
                    <div class="humidity" id="humidity">--</div>
                    <div>%</div>
                </div>
            </div>
            <div style="margin-top:15px; font-size:14px; color:#666;">
                <div>๐Ÿ†” ์„ผ์„œ: <span id="sensorId">-</span></div>
                <div>โฐ ์‹œ๊ฐ„: <span id="timestamp">-</span></div>
            </div>
        </div>
        
        <h3>๐Ÿ“ ๋กœ๊ทธ</h3>
        <div id="log"></div>
    </div>

    <script>
        let client = null;
        const BROKER_URL = 'ws://localhost:9001';  // WebSocket ํฌํŠธ
        const TOPIC = 'sensors/#';
        
        function addLog(message) {
            const log = document.getElementById('log');
            const time = new Date().toLocaleTimeString();
            log.innerHTML += `[${time}] ${message}<br>`;
            log.scrollTop = log.scrollHeight;
        }
        
        function connect() {
            if (client && client.connected) {
                addLog('โš ๏ธ  ์ด๋ฏธ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.');
                return;
            }
            
            const clientId = 'WebClient_' + Math.random().toString(16).substr(2, 8);
            
            addLog('๐Ÿ”Œ ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ค‘...');
            
            client = mqtt.connect(BROKER_URL, {
                clientId: clientId,
                username: 'your_username',
                password: 'your_password',
                clean: true,
                keepalive: 60
            });
            
            client.on('connect', () => {
                addLog('โœ… ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์„ฑ๊ณต!');
                document.getElementById('status').className = 'status connected';
                document.getElementById('status').textContent = 'โœ… ์—ฐ๊ฒฐ๋จ';
                
                client.subscribe(TOPIC, { qos: 1 }, (error) => {
                    if (error) {
                        addLog('โŒ ๊ตฌ๋… ์‹คํŒจ: ' + error);
                    } else {
                        addLog('๐Ÿ“ก ํ† ํ”ฝ ๊ตฌ๋…: ' + TOPIC);
                    }
                });
            });
            
            client.on('message', (topic, message) => {
                try {
                    const data = JSON.parse(message.toString());
                    
                    // UI ์—…๋ฐ์ดํŠธ
                    document.getElementById('temperature').textContent = 
                        data.temperature.toFixed(1);
                    document.getElementById('humidity').textContent = 
                        data.humidity.toFixed(1);
                    document.getElementById('sensorId').textContent = 
                        data.sensor_id;
                    
                    const date = new Date(data.timestamp * 1000);
                    document.getElementById('timestamp').textContent = 
                        date.toLocaleString('ko-KR');
                    
                    addLog(`๐Ÿ“ฌ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ : ์˜จ๋„ ${data.temperature.toFixed(1)}ยฐC, ` +
                           `์Šต๋„ ${data.humidity.toFixed(1)}%`);
                    
                    // ๊ฒฝ๊ณ  ์ฒดํฌ
                    if (data.temperature > 30) {
                        addLog('๐Ÿ”ฅ ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋†’์Šต๋‹ˆ๋‹ค!');
                    } else if (data.temperature < 10) {
                        addLog('โ„๏ธ  ๊ฒฝ๊ณ : ์˜จ๋„๊ฐ€ ๋„ˆ๋ฌด ๋‚ฎ์Šต๋‹ˆ๋‹ค!');
                    }
                    
                } catch (error) {
                    addLog('โš ๏ธ  ๋ฉ”์‹œ์ง€ ํŒŒ์‹ฑ ์˜ค๋ฅ˜: ' + error.message);
                }
            });
            
            client.on('error', (error) => {
                addLog('โŒ ์˜ค๋ฅ˜: ' + error.message);
            });
            
            client.on('close', () => {
                addLog('๐Ÿ”Œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ');
                document.getElementById('status').className = 'status disconnected';
                document.getElementById('status').textContent = 'โŒ ์—ฐ๊ฒฐ ์•ˆ๋จ';
            });
        }
        
        function disconnect() {
            if (client && client.connected) {
                client.end();
                addLog('๐Ÿ”Œ ์—ฐ๊ฒฐ ํ•ด์ œ ์š”์ฒญ');
            } else {
                addLog('โš ๏ธ  ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
            }
        }
        
        // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ž๋™ ์—ฐ๊ฒฐ
        window.addEventListener('load', () => {
            addLog('๐ŸŒ ํŽ˜์ด์ง€ ๋กœ๋“œ ์™„๋ฃŒ');
        });
    </script>
</body>
</html>

๐Ÿ”’ MQTT ๋ณด์•ˆ ๊ฐ•ํ™”ํ•˜๊ธฐ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ณด์•ˆ์ด ์ •๋ง ์ค‘์š”ํ•ด! ๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ๋Š” ๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด์„œ ์œ„ํ—˜ํ•˜๊ฑฐ๋“ . TLS/SSL ์•”ํ˜ธํ™”์™€ ์ธ์ฆ์„ ๋ฐ˜๋“œ์‹œ ์ ์šฉํ•ด์•ผ ํ•ด. ๐Ÿ›ก๏ธ

๐Ÿ” TLS/SSL ์ธ์ฆ์„œ ์ƒ์„ฑ

# OpenSSL์„ ์‚ฌ์šฉํ•œ ์ž์ฒด ์„œ๋ช… ์ธ์ฆ์„œ ์ƒ์„ฑ

# 1. CA(Certificate Authority) ํ‚ค ์ƒ์„ฑ
openssl genrsa -out ca.key 2048

# 2. CA ์ธ์ฆ์„œ ์ƒ์„ฑ (10๋…„ ์œ ํšจ)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

# 3. ์„œ๋ฒ„ ํ‚ค ์ƒ์„ฑ
openssl genrsa -out server.key 2048

# 4. ์„œ๋ฒ„ ์ธ์ฆ์„œ ์„œ๋ช… ์š”์ฒญ(CSR) ์ƒ์„ฑ
openssl req -new -key server.key -out server.csr

# 5. CA๋กœ ์„œ๋ฒ„ ์ธ์ฆ์„œ ์„œ๋ช…
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out server.crt -days 3650

# ํŒŒ์ผ ๊ถŒํ•œ ์„ค์ •
chmod 600 *.key
chmod 644 *.crt

โš™๏ธ TLS ํ™œ์„ฑํ™” ์„ค์ •

# mosquitto.conf์— TLS ์„ค์ • ์ถ”๊ฐ€

# ์ผ๋ฐ˜ ํฌํŠธ ๋น„ํ™œ์„ฑํ™”
#listener 1883

# TLS ํฌํŠธ ํ™œ์„ฑํ™”
listener 8883
protocol mqtt

# ์ธ์ฆ์„œ ๊ฒฝ๋กœ ์„ค์ •
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

# TLS ๋ฒ„์ „ ์„ค์ •
tls_version tlsv1.2

# ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ ์š”๊ตฌ (์„ ํƒ์‚ฌํ•ญ)
require_certificate false

# ์‚ฌ์šฉ์ž ์ธ์ฆ ํ•„์ˆ˜
allow_anonymous false
password_file /etc/mosquitto/passwd

๐Ÿ”’ Python์—์„œ TLS ์—ฐ๊ฒฐ

import paho.mqtt.client as mqtt
import ssl

BROKER_ADDRESS = "your-broker.com"
BROKER_PORT = 8883
CLIENT_ID = "SecureClient"

# TLS ์„ค์ •
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("โœ… ๋ณด์•ˆ ์—ฐ๊ฒฐ ์„ฑ๊ณต!")
    else:
        print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: {rc}")

client = mqtt.Client(CLIENT_ID)
client.on_connect = on_connect

# ์‚ฌ์šฉ์ž ์ธ์ฆ
client.username_pw_set("username", "password")

# TLS/SSL ์„ค์ •
client.tls_set(
    ca_certs="/path/to/ca.crt",
    certfile="/path/to/client.crt",  # ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„œ (์„ ํƒ)
    keyfile="/path/to/client.key",   # ํด๋ผ์ด์–ธํŠธ ํ‚ค (์„ ํƒ)
    cert_reqs=ssl.CERT_REQUIRED,
    tls_version=ssl.PROTOCOL_TLSv1_2,
    ciphers=None
)

# ์ธ์ฆ์„œ ๊ฒ€์ฆ ๋น„ํ™œ์„ฑํ™” (๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ!)
# client.tls_insecure_set(True)

# ์—ฐ๊ฒฐ
client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
client.loop_forever()

๐ŸŽฏ MQTT ํ† ํ”ฝ ์„ค๊ณ„ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

ํ† ํ”ฝ ๊ตฌ์กฐ๋ฅผ ์ž˜ ์„ค๊ณ„ํ•˜๋Š” ๊ฒŒ ์ •๋ง ์ค‘์š”ํ•ด! ๋‚˜์ค‘์— ์‹œ์Šคํ…œ์ด ์ปค์กŒ์„ ๋•Œ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฝ๋„๋ก ์ฒ˜์Œ๋ถ€ํ„ฐ ์ฒด๊ณ„์ ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•˜๊ฑฐ๋“ . ๐Ÿ“

โœ… ์ข‹์€ ํ† ํ”ฝ ๊ตฌ์กฐ ์˜ˆ์‹œ

company/location/building/floor/room/device/sensor

smart-home/living-room/temperature
factory/line-1/machine-5/status
vehicle/car-123/gps/location

โŒ ๋‚˜์œ ํ† ํ”ฝ ๊ตฌ์กฐ ์˜ˆ์‹œ

temp (๋„ˆ๋ฌด ์ผ๋ฐ˜์ )
sensor_data_123_temp_living_room (์–ธ๋”์Šค์ฝ”์–ด ๋‚จ์šฉ)
home/room/device/sensor/type/unit/value (๋„ˆ๋ฌด ๊นŠ์Œ)

๐ŸŒŸ ํ† ํ”ฝ ์„ค๊ณ„ ์›์น™

1. ๊ณ„์ธต ๊ตฌ์กฐ ์‚ฌ์šฉ: ์Šฌ๋ž˜์‹œ(/)๋กœ ๋…ผ๋ฆฌ์  ๊ณ„์ธต ๊ตฌ๋ถ„
2. ๋ช…ํ™•ํ•œ ๋„ค์ด๋ฐ: ๋ˆ„๊ฐ€ ๋ด๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฆ„ ์‚ฌ์šฉ
3. ์ ์ ˆํ•œ ๊นŠ์ด: 3~5๋‹จ๊ณ„๊ฐ€ ์ ๋‹น, ๋„ˆ๋ฌด ๊นŠ์œผ๋ฉด ๋ณต์žก
4. ์†Œ๋ฌธ์ž ์‚ฌ์šฉ: ๋Œ€์†Œ๋ฌธ์ž ํ˜ผ์šฉ ํ”ผํ•˜๊ธฐ
5. ์™€์ผ๋“œ์นด๋“œ ๊ณ ๋ ค: ๊ตฌ๋… ํŒจํ„ด์„ ์ƒ๊ฐํ•˜๋ฉฐ ์„ค๊ณ„
6. ํ™•์žฅ์„ฑ: ๋ฏธ๋ž˜์˜ ์ถ”๊ฐ€ ์š”๊ตฌ์‚ฌํ•ญ ๊ณ ๋ ค

๐Ÿ” ์™€์ผ๋“œ์นด๋“œ ์‚ฌ์šฉ๋ฒ•

์™€์ผ๋“œ์นด๋“œ ์˜๋ฏธ ์˜ˆ์‹œ ๋งค์นญ๋˜๋Š” ํ† ํ”ฝ
+ ๋‹จ์ผ ๋ ˆ๋ฒจ home/+/temperature home/living-room/temperature
home/bedroom/temperature
# ๋‹ค์ค‘ ๋ ˆ๋ฒจ home/# home/living-room/temperature
home/bedroom/light/status
์กฐํ•ฉ ํ˜ผํ•ฉ ์‚ฌ์šฉ factory/+/machine/# factory/line-1/machine/status
factory/line-2/machine/temp/sensor-1
โš ๏ธ ์ฃผ์˜: # ์™€์ผ๋“œ์นด๋“œ๋Š” ํ† ํ”ฝ์˜ ๋งˆ์ง€๋ง‰์—๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. home/#/temperature๋Š” ์ž˜๋ชป๋œ ์‚ฌ์šฉ๋ฒ•์ด์•ผ!

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

MQTT๋Š” ๊ฒฝ๋Ÿ‰ ํ”„๋กœํ† ์ฝœ์ด์ง€๋งŒ, ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ด. ๋ช‡ ๊ฐ€์ง€ ์‹ค์ „ ํŒ์„ ๊ณต์œ ํ• ๊ฒŒ! ๐Ÿš€

๐ŸŽฏ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ 1. QoS ๋ ˆ๋ฒจ ์„ ํƒ
โ€ข ์‹ค์‹œ๊ฐ„ ์„ผ์„œ ๋ฐ์ดํ„ฐ: QoS 0 (๋น ๋ฅธ ์ „์†ก)
โ€ข ์ค‘์š”ํ•œ ๋ช…๋ น: QoS 1 (์ตœ์†Œ 1๋ฒˆ ์ „๋‹ฌ)
โ€ข ๊ธˆ์œต ๊ฑฐ๋ž˜: QoS 2 (์ •ํ™•ํžˆ 1๋ฒˆ)

2. Keep Alive ์„ค์ •
โ€ข ๋ชจ๋ฐ”์ผ: 60~120์ดˆ (๋ฐฐํ„ฐ๋ฆฌ ์ ˆ์•ฝ)
โ€ข ์„œ๋ฒ„: 30~60์ดˆ (๋น ๋ฅธ ์—ฐ๊ฒฐ ๊ฐ์ง€)
โ€ข IoT ๋””๋ฐ”์ด์Šค: 120~300์ดˆ (๋„คํŠธ์›Œํฌ ์ ˆ์•ฝ)

3. Clean Session
โ€ข true: ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ, ์ผ์‹œ์  ์—ฐ๊ฒฐ
โ€ข false: ์˜คํ”„๋ผ์ธ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ , ์ง€์† ์—ฐ๊ฒฐ

4. ๋ฉ”์‹œ์ง€ ํฌ๊ธฐ
โ€ข ์ตœ๋Œ€ํ•œ ์ž‘๊ฒŒ ์œ ์ง€ (JSON ์••์ถ•, ๋ถˆํ•„์š”ํ•œ ํ•„๋“œ ์ œ๊ฑฐ)
โ€ข ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”„๋กœํ† ์ฝœ ๊ณ ๋ ค (Protocol Buffers, MessagePack)

5. ์—ฐ๊ฒฐ ํ’€๋ง
โ€ข ํด๋ผ์ด์–ธํŠธ ์žฌ์‚ฌ์šฉ
โ€ข ๋ถˆํ•„์š”ํ•œ ์žฌ์—ฐ๊ฒฐ ๋ฐฉ์ง€

6. ํ† ํ”ฝ ๊ตฌ๋… ์ตœ์ ํ™”
โ€ข ํ•„์š”ํ•œ ํ† ํ”ฝ๋งŒ ๊ตฌ๋…
โ€ข ์™€์ผ๋“œ์นด๋“œ ๋‚จ์šฉ ์ž์ œ

๐Ÿ“Š ๋ฉ”์‹œ์ง€ ์••์ถ• ์˜ˆ์ œ

import paho.mqtt.client as mqtt
import json
import gzip

def compress_payload(data):
    """JSON ๋ฐ์ดํ„ฐ๋ฅผ ์••์ถ•"""
    json_str = json.dumps(data)
    compressed = gzip.compress(json_str.encode('utf-8'))
    return compressed

def decompress_payload(compressed_data):
    """์••์ถ•๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด์ œ"""
    decompressed = gzip.decompress(compressed_data)
    json_str = decompressed.decode('utf-8')
    return json.loads(json_str)

# Publisher
client = mqtt.Client("CompressedPublisher")
client.connect("localhost", 1883)

data = {
    "sensor_id": "sensor_001",
    "timestamp": 1234567890,
    "readings": [
        {"temp": 25.5, "humidity": 60.2},
        {"temp": 25.6, "humidity": 60.1},
        # ... ๋งŽ์€ ๋ฐ์ดํ„ฐ
    ]
}

# ์••์ถ• ์ „ํ›„ ํฌ๊ธฐ ๋น„๊ต
original = json.dumps(data).encode('utf-8')
compressed = compress_payload(data)

print(f"์›๋ณธ ํฌ๊ธฐ: {len(original)} bytes")
print(f"์••์ถ• ํฌ๊ธฐ: {len(compressed)} bytes")
print(f"์••์ถ•๋ฅ : {(1 - len(compressed)/len(original)) * 100:.1f}%")

# ์••์ถ•๋œ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰
client.publish("sensors/compressed", compressed)

# Subscriber
def on_message(client, userdata, message):
    compressed_data = message.payload
    data = decompress_payload(compressed_data)
    print(f"์ˆ˜์‹  ๋ฐ์ดํ„ฐ: {data}")

๐Ÿ› ๋””๋ฒ„๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง

๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๊ธฐ ๋งˆ๋ จ์ด์•ผ. MQTT ํ†ต์‹ ์„ ํšจ๊ณผ์ ์œผ๋กœ ๋””๋ฒ„๊น…ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๐Ÿ”

๐Ÿ› ๏ธ Mosquitto ํด๋ผ์ด์–ธํŠธ ๋„๊ตฌ

# ๋ฉ”์‹œ์ง€ ๊ตฌ๋… (๋ชจ๋“  ํ† ํ”ฝ)
mosquitto_sub -h localhost -t '#' -v

# ํŠน์ • ํ† ํ”ฝ ๊ตฌ๋…
mosquitto_sub -h localhost -t 'sensors/temperature' -v

# ์‚ฌ์šฉ์ž ์ธ์ฆ๊ณผ ํ•จ๊ป˜ ๊ตฌ๋…
mosquitto_sub -h localhost -t '#' -u username -P password -v

# ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
mosquitto_pub -h localhost -t 'test/topic' -m 'Hello MQTT!'

# JSON ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
mosquitto_pub -h localhost -t 'sensors/temp' \
    -m '{"temperature": 25.5, "unit": "celsius"}'

# QoS ์„ค์ •๊ณผ ํ•จ๊ป˜ ๋ฐœํ–‰
mosquitto_pub -h localhost -t 'important/data' -m 'Critical' -q 2

# Retain ํ”Œ๋ž˜๊ทธ์™€ ํ•จ๊ป˜ ๋ฐœํ–‰
mosquitto_pub -h localhost -t 'status/online' -m 'true' -r

๐Ÿ“ฑ MQTT ํด๋ผ์ด์–ธํŠธ ์•ฑ

GUI ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋” ํŽธํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด:

โ€ข MQTT Explorer: Windows/Mac/Linux์šฉ ๋ฌด๋ฃŒ ๋„๊ตฌ, ํ† ํ”ฝ ํŠธ๋ฆฌ ์‹œ๊ฐํ™”
โ€ข MQTT.fx: Java ๊ธฐ๋ฐ˜ ํฌ๋กœ์Šค ํ”Œ๋žซํผ ํด๋ผ์ด์–ธํŠธ
โ€ข MyMQTT (Android): ๋ชจ๋ฐ”์ผ์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์ข‹์Œ
โ€ข MQTTBox: Chrome ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ

๐Ÿ“Š ๋กœ๊น… ์„ค์ •

# mosquitto.conf ๋กœ๊น… ์„ค์ •

# ๋กœ๊ทธ ์ถœ๋ ฅ ์œ„์น˜
log_dest file /var/log/mosquitto/mosquitto.log
log_dest stdout

# ๋กœ๊ทธ ํƒ€์ž… (์—ฌ๋Ÿฌ ๊ฐœ ์ง€์ • ๊ฐ€๋Šฅ)
log_type error
log_type warning
log_type notice
log_type information
log_type all

# ์—ฐ๊ฒฐ ๋ฉ”์‹œ์ง€ ๋กœ๊น…
connection_messages true

# ํƒ€์ž„์Šคํƒฌํ”„ ํฌํ•จ
log_timestamp true

# ๋กœ๊ทธ ํŒŒ์ผ ํฌ๊ธฐ ์ œํ•œ (bytes)
# log_file_size 10485760  # 10MB

๐ŸŒ ์‹ค์ „ ํ™œ์šฉ ์‚ฌ๋ก€

์ด๋ก ๋งŒ ์•Œ์•„์„œ๋Š” ์žฌ๋ฏธ์—†์ง€! ์‹ค์ œ๋กœ MQTT๊ฐ€ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๋ช‡ ๊ฐ€์ง€ ์‚ฌ๋ก€๋ฅผ ์‚ดํŽด๋ณด์ž. ๐ŸŽช

MQTT ์‹ค์ „ ํ™œ์šฉ ๋ถ„์•ผ ์Šค๋งˆํŠธํ™ˆ ์กฐ๋ช… ์ œ์–ด ์˜จ๋„ ๊ด€๋ฆฌ ๋ณด์•ˆ ์‹œ์Šคํ…œ ์‚ฐ์—… IoT ์„ค๋น„ ๋ชจ๋‹ˆํ„ฐ๋ง ์˜ˆ์ง€ ์ •๋น„ ์ƒ์‚ฐ ์ตœ์ ํ™” ํ—ฌ์Šค์ผ€์–ด ํ™˜์ž ๋ชจ๋‹ˆํ„ฐ๋ง ์›๊ฒฉ ์ง„๋ฃŒ ์›จ์–ด๋Ÿฌ๋ธ” ๊ธฐ๊ธฐ ์ž๋™์ฐจ ํ…”๋ ˆ๋งคํ‹ฑ์Šค V2X ํ†ต์‹  OTA ์—…๋ฐ์ดํŠธ ๋†์—… ์Šค๋งˆํŠธํŒœ ํ† ์–‘ ์„ผ์„œ ์ž๋™ ๊ด€๊ฐœ ๋ฌผ๋ฅ˜ ์œ„์น˜ ์ถ”์  ์žฌ๊ณ  ๊ด€๋ฆฌ ๋ฐฐ์†ก ์ตœ์ ํ™” ์—๋„ˆ์ง€ ์Šค๋งˆํŠธ๊ทธ๋ฆฌ๋“œ ์ „๋ ฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์ˆ˜์š” ์˜ˆ์ธก

๐Ÿ  ์Šค๋งˆํŠธํ™ˆ ์‹œ์Šคํ…œ ์˜ˆ์ œ

์‹ค์ œ ์Šค๋งˆํŠธํ™ˆ ์‹œ์Šคํ…œ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ด๋ณด์ž. ์กฐ๋ช…, ์˜จ๋„, ๋ณด์•ˆ ์„ผ์„œ๋ฅผ ํ†ตํ•ฉ ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ์ด์•ผ:

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

class SmartHomeController:
    def __init__(self, broker_address, username, password):
        self.broker = broker_address
        self.client = mqtt.Client("SmartHomeController")
        self.client.username_pw_set(username, password)
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        
        # ๋””๋ฐ”์ด์Šค ์ƒํƒœ ์ €์žฅ
        self.devices = {
            "lights": {},
            "temperature": {},
            "security": {}
        }
    
    def on_connect(self, client, userdata, flags, rc):
        if rc == 0:
            print("โœ… ์Šค๋งˆํŠธํ™ˆ ์ปจํŠธ๋กค๋Ÿฌ ์—ฐ๊ฒฐ ์„ฑ๊ณต!")
            # ๋ชจ๋“  ๋””๋ฐ”์ด์Šค ํ† ํ”ฝ ๊ตฌ๋…
            self.client.subscribe("home/#", qos=1)
        else:
            print(f"โŒ ์—ฐ๊ฒฐ ์‹คํŒจ: {rc}")
    
    def on_message(self, client, userdata, message):
        topic = message.topic
        payload = json.loads(message.payload.decode())
        
        # ํ† ํ”ฝ ํŒŒ์‹ฑ
        parts = topic.split('/')
        
        if len(parts) >= 3:
            category = parts[1]  # lights, temperature, security
            room = parts[2]      # living-room, bedroom, etc.
            
            # ์ƒํƒœ ์—…๋ฐ์ดํŠธ
            if category in self.devices:
                self.devices[category][room] = payload
                self.process_device_update(category, room, payload)
    
    def process_device_update(self, category, room, data):
        """๋””๋ฐ”์ด์Šค ์—…๋ฐ์ดํŠธ ์ฒ˜๋ฆฌ"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        
        if category == "temperature":
            temp = data.get("temperature")
            print(f"[{timestamp}] ๐ŸŒก๏ธ  {room}: {temp}ยฐC")
            
            # ์ž๋™ ์˜จ๋„ ์กฐ์ ˆ
            if temp > 28:
                self.control_ac(room, "on", 24)
            elif temp < 18:
                self.control_heater(room, "on", 22)
        
        elif category == "lights":
            status = data.get("status")
            brightness = data.get("brightness", 0)
            print(f"[{timestamp}] ๐Ÿ’ก {room}: {status} ({brightness}%)")
        
        elif category == "security":
            motion = data.get("motion_detected")
            if motion:
                print(f"[{timestamp}] ๐Ÿšจ {room}: ์›€์ง์ž„ ๊ฐ์ง€!")
                self.send_alert(room)
    
    def control_light(self, room, status, brightness=100):
        """์กฐ๋ช… ์ œ์–ด"""
        payload = {
            "command": "set",
            "status": status,
            "brightness": brightness,
            "timestamp": time.time()
        }
        topic = f"home/lights/{room}/control"
        self.client.publish(topic, json.dumps(payload), qos=1)
        print(f"๐Ÿ’ก {room} ์กฐ๋ช… {status} (๋ฐ๊ธฐ: {brightness}%)")
    
    def control_ac(self, room, status, temperature):
        """์—์–ด์ปจ ์ œ์–ด"""
        payload = {
            "command": "set",
            "status": status,
            "temperature": temperature,
            "mode": "cool",
            "timestamp": time.time()
        }
        topic = f"home/climate/{room}/control"
        self.client.publish(topic, json.dumps(payload), qos=1)
        print(f"โ„๏ธ  {room} ์—์–ด์ปจ {status} (์„ค์ •: {temperature}ยฐC)")
    
    def control_heater(self, room, status, temperature):
        """ํžˆํ„ฐ ์ œ์–ด"""
        payload = {
            "command": "set",
            "status": status,
            "temperature": temperature,
            "mode": "heat",
            "timestamp": time.time()
        }
        topic = f"home/climate/{room}/control"
        self.client.publish(topic, json.dumps(payload), qos=1)
        print(f"๐Ÿ”ฅ {room} ํžˆํ„ฐ {status} (์„ค์ •: {temperature}ยฐC)")
    
    def send_alert(self, room):
        """๋ณด์•ˆ ์•Œ๋ฆผ ์ „์†ก"""
        payload = {
            "type": "security_alert",
            "room": room,
            "message": f"{room}์—์„œ ์›€์ง์ž„์ด ๊ฐ์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
            "timestamp": time.time()
        }
        topic = "home/alerts"
        self.client.publish(topic, json.dumps(payload), qos=2)
        print(f"๐Ÿ“ฑ ๋ณด์•ˆ ์•Œ๋ฆผ ์ „์†ก: {room}")
    
    def get_status_report(self):
        """์ „์ฒด ์ƒํƒœ ๋ฆฌํฌํŠธ"""
        print("\n" + "="*60)
        print("๐Ÿ  ์Šค๋งˆํŠธํ™ˆ ์ƒํƒœ ๋ฆฌํฌํŠธ")
        print("="*60)
        
        for category, rooms in self.devices.items():
            if rooms:
                print(f"\n๐Ÿ“Š {category.upper()}")
                for room, data in rooms.items():
                    print(f"  โ€ข {room}: {data}")
        
        print("="*60 + "\n")
    
    def start(self):
        """์ปจํŠธ๋กค๋Ÿฌ ์‹œ์ž‘"""
        self.client.connect(self.broker, 1883, keepalive=60)
        self.client.loop_start()
    
    def stop(self):
        """์ปจํŠธ๋กค๋Ÿฌ ์ข…๋ฃŒ"""
        self.client.loop_stop()
        self.client.disconnect()

# ์‚ฌ์šฉ ์˜ˆ์ œ
if __name__ == "__main__":
    controller = SmartHomeController(
        broker_address="localhost",
        username="smarthome",
        password="password123"
    )
    
    controller.start()
    
    try:
        # ์ดˆ๊ธฐ ์„ค์ •
        time.sleep(2)
        
        # ๊ฑฐ์‹ค ์กฐ๋ช… ์ผœ๊ธฐ
        controller.control_light("living-room", "on", 80)
        
        # ์นจ์‹ค ์กฐ๋ช… ๋„๊ธฐ
        controller.control_light("bedroom", "off", 0)
        
        # ์ƒํƒœ ๋ฆฌํฌํŠธ ์ฃผ๊ธฐ์  ์ถœ๋ ฅ
        while True:
            time.sleep(30)
            controller.get_status_report()
    
    except KeyboardInterrupt:
        print("\nโน๏ธ  ์ปจํŠธ๋กค๋Ÿฌ ์ข…๋ฃŒ ์ค‘...")
        controller.stop()

๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ

MQTT๋ฅผ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•ด๋ด! ๐Ÿ“š ๊ทธ๋ฆฌ๊ณ  ์žฌ๋Šฅ๋„ท์—์„œ๋Š” MQTT ์ „๋ฌธ๊ฐ€๋“ค์ด ์‹ค์‹œ๊ฐ„ ๋ฉ˜ํ† ๋ง๋„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์œผ๋‹ˆ, ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด!

๐Ÿ“– ๊ณต์‹ ๋ฌธ์„œ ๋ฐ ์ŠคํŽ™ โ€ข MQTT 3.1.1 ์ŠคํŽ™: docs.oasis-open.org/mqtt
โ€ข MQTT 5.0 ์ŠคํŽ™: ์ตœ์‹  ๊ธฐ๋Šฅ ํฌํ•จ
โ€ข Eclipse Paho: eclipse.org/paho
โ€ข Mosquitto ๋ฌธ์„œ: mosquitto.org/documentation

๐ŸŽฅ ์˜จ๋ผ์ธ ๊ฐ•์˜
โ€ข Udemy: "MQTT Essentials" ์ฝ”์Šค
โ€ข YouTube: HiveMQ ์ฑ„๋„
โ€ข Coursera: IoT ์ „๋ฌธํ™” ๊ณผ์ •

๐Ÿ’ฌ ์ปค๋ฎค๋‹ˆํ‹ฐ
โ€ข Reddit: r/mqtt
โ€ข Stack Overflow: mqtt ํƒœ๊ทธ
โ€ข HiveMQ Community Forum
โ€ข Eclipse Paho ๋ฉ”์ผ๋ง ๋ฆฌ์ŠคํŠธ

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ

์—ฌ๊ธฐ๊นŒ์ง€ MQTT ๋ธŒ๋กœ์ปค์™€ ํด๋ผ์ด์–ธํŠธ์˜ ๊ธฐ๋ณธ๋ถ€ํ„ฐ ์‹ค์ „ ํ™œ์šฉ๊นŒ์ง€ ๋ชจ๋‘ ๋‹ค๋ค„๋ดค์–ด! ์ด์ œ ์ง์ ‘ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ๊ฒฝํ—˜์„ ์Œ“์•„๊ฐ€๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๐Ÿ’ช

๐Ÿ’ก ์‹ค์Šต ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด 1. ๋‚ ์”จ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ: ์—ฌ๋Ÿฌ ์„ผ์„œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•ด์„œ ์›น ๋Œ€์‹œ๋ณด๋“œ์— ํ‘œ์‹œ
2. ์ฑ„ํŒ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜: MQTT๋ฅผ ๋ฉ”์‹œ์ง• ๋ฐฑ์—”๋“œ๋กœ ํ™œ์šฉ
3. ์›๊ฒฉ ์ œ์–ด ๋กœ๋ด‡: ๋ผ์ฆˆ๋ฒ ๋ฆฌํŒŒ์ด์™€ MQTT๋กœ IoT ๋กœ๋ด‡ ์ œ์–ด
4. ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ: ๋ชจ๋ฐ”์ผ ์•ฑ์— ํ‘ธ์‹œ ์•Œ๋ฆผ ์ „์†ก
5. ์—๋„ˆ์ง€ ๋ชจ๋‹ˆํ„ฐ๋ง: ๊ฐ€์ •์˜ ์ „๋ ฅ ์‚ฌ์šฉ๋Ÿ‰ ์‹ค์‹œ๊ฐ„ ์ถ”์ 

MQTT๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ ํ”„๋กœํ† ์ฝœ์ด์•ผ. ํ•œ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด ๋‹ค์–‘ํ•œ IoT ํ”„๋กœ์ ํŠธ์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ๊ฐœ๋ฐœ์ž๋กœ์„œ ํฐ ๋ฌด๊ธฐ๊ฐ€ ๋  ๊ฑฐ์•ผ! ๐ŸŽฏ

ํ˜น์‹œ ๊ตฌํ˜„ํ•˜๋‹ค๊ฐ€ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๊ฑฐ๋‚˜, ๋” ๊นŠ์ด ์žˆ๋Š” ๋‚ด์šฉ์ด ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์งˆ๋ฌธํ•ด์ค˜. ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด! ๐Ÿ˜Š

๐ŸŽ‰ Happy Coding with MQTT! ๐ŸŽ‰

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.