Ver3.5 ๐ IoT ์ผ์ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ฐ๋์ ์ ๋ก๋ํ๋ ์์ ์ ๋ณต ์์ค ๊ฐ์ด๋

๐ IoT ์ผ์ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ฐ๋์ ์ ๋ก๋ํ๋ ์์ ์ ๋ณต ์์ค ๊ฐ์ด๋
์ผ์์์ ํด๋ผ์ฐ๋๊น์ง, ์ค์ ์ฝ๋๋ก ๋ฐฐ์ฐ๋ IoT ๋ฐ์ดํฐ ํ์ดํ๋ผ์ธ ๐
์์งํ ๋งํ๋ฉด IoT ์ผ์ ๋ฐ์ดํฐ๋ฅผ ํด๋ผ์ฐ๋์ ์ฌ๋ฆฌ๋ ๊ฒ ์ฒ์์ ๋ณ๊ฑฐ ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์์์?
"๊ทธ๋ฅ HTTP๋ก POST ๋ ๋ฆฌ๋ฉด ๋๋ ๊ฑฐ ์๋?" ์ด๋ ๊ฒ ์๊ฐํ๋ ๋ถ๋ค ๋ง์ ํ
๋ฐใ
ใ
ใ
๊ทผ๋ฐ ์ค์ ๋ก ํด๋ณด๋ฉด ์๊ฐ๋ณด๋ค ๊ณ ๋ คํ ๊ฒ ์์ฒญ ๋ง์์. ์ง์ง๋ก์ ๐
IoT ํ๊ฒฝ์ ์ผ๋ฐ ์น ๊ฐ๋ฐ์ด๋ ๋ค๋ฅด๊ฒ ๋คํธ์ํฌ๊ฐ ๋ถ์์ ํ๊ณ , ๋๋ฐ์ด์ค ๋ฆฌ์์ค๊ฐ ์ ํ์ ์ด๊ณ ,
๋ฐฐํฐ๋ฆฌ ์๋ช
๋ ์ ๊ฒฝ ์จ์ผ ํ๊ณ , ๋ณด์๋ ๋นก์ธ๊ฒ ์ฑ๊ฒจ์ผ ํด์.
๊ฑฐ๊ธฐ๋ค ์์ญ~์์ฒ ๊ฐ์ ์ผ์๊ฐ ๋์์ ๋ฐ์ดํฐ๋ฅผ ์์๋ด๋ ์ํฉ๋ ์ฒ๋ฆฌํด์ผ ํ์ฃ .
์ด ๊ธ์์๋ ์ค์ ๋ก ํ์
์์ ์ฐ์ด๋ IoT ๋ฐ์ดํฐ ์
๋ก๋ ์์ค ์ฝ๋๋ฅผ ๋จ๊ณ๋ณ๋ก ๋ฏ์ด๋ณผ ๊ฑฐ์์.
Python, Arduino(C++), Node.js ๊ธฐ๋ฐ์ผ๋ก ๋ค์ํ ํด๋ผ์ฐ๋ ํ๋ซํผ(AWS IoT, Firebase, Azure IoT Hub)์
๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆฌ๋ ๋ฐฉ๋ฒ์ ์ค์ ์ฝ๋์ ํจ๊ป ์ค๋ช
ํ ๊ฒ์. ๋ ์ธ ๊ณ ๐
ํด๋ผ์ฐ๋ ์ฐ๋์ด ์ฒ์์ธ ๋ถ๋ค, ์ค์ ์์ค ์ฝ๋๊ฐ ํ์ํ ๋ชจ๋ ๋ถ๋ค!
ํด๋ผ์ฐ๋์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆฌ๊ธฐ ์ ์ ์ด๋ค ํ๋กํ ์ฝ๋ก ๋ณด๋ผ์ง๋ถํฐ ๊ฒฐ์ ํด์ผ ํด์.
ํ๋กํ ์ฝ ์ ํ์ด ์๋ชป๋๋ฉด ๋ฐฐํฐ๋ฆฌ ๋นจ๋ฆฌ ๋ณ๊ณ , ๋ฐ์ดํฐ ์ ์ค๋๊ณ , ์ง์ฐ ๋ฐ์ํ๊ณ ... ๋๋ฆฌ ๋ฉ๋๋ค ๐
| ํ๋กํ ์ฝ | ํน์ง | ์ ํฉํ ์ํฉ | ์ค๋ฒํค๋ |
|---|---|---|---|
| MQTT | ๊ฒฝ๋ pub/sub ๋ฉ์์ง ๋ธ๋ก์ปค | ๋ฐฐํฐ๋ฆฌ ๊ธฐ๊ธฐ, ๋ถ์์ ๋คํธ์ํฌ | ๋งค์ฐ ๋ฎ์ |
| HTTP/REST | ๋ฒ์ฉ ์์ฒญ-์๋ต ๋ฐฉ์ | ๋จ์ ๋ฐ์ดํฐ ์ ์ก, ์น ์ฐ๋ | ์ค๊ฐ |
| WebSocket | ์๋ฐฉํฅ ์ค์๊ฐ ํต์ | ์ค์๊ฐ ๋ชจ๋ํฐ๋ง, ์ ์ด | ์ค๊ฐ |
| CoAP | UDP ๊ธฐ๋ฐ ๊ฒฝ๋ ํ๋กํ ์ฝ | ์ด์ ์ ๋ ฅ ์ผ์, ํ๋์ญ ํต์ | ๋งค์ฐ ๋ฎ์ |
| AMQP | ์ํฐํ๋ผ์ด์ฆ ๋ฉ์์ง ํ | ๋๊ท๋ชจ ์ฐ์ IoT | ๋์ |
HTTP๋ ๋งค๋ฒ TCP ์ฐ๊ฒฐ์ ์๋ก ๋งบ์ด์ ์ค๋ฒํค๋๊ฐ ํฌ๊ฑฐ๋ ์.
MQTT๋ ํ ๋ฒ ์ฐ๊ฒฐํ๊ณ ๊ณ์ ์ ์งํ๋ฉด์ ๋ฐ์ดํฐ๋ง ์๋ ๋ฐฉ์์ด๋ผ ํจ์ฌ ํจ์จ์ ์ด์์.
MQTT(Message Queuing Telemetry Transport)๋ 1999๋
์ IBM์ด ๋ง๋ ํ๋กํ ์ฝ์ธ๋ฐ,
์ง๊ธ๋ IoT ์ธ๊ณ์์ ์ฌ์ค์ ํ์ค์ผ๋ก ์ฐ์ด๊ณ ์์ด์.
ํต์ฌ ๊ฐ๋
์ ์ธ ๊ฐ์ง์์:
Publisher(๋ฐํ์) - ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ์ผ์/๋๋ฐ์ด์ค
Broker(๋ธ๋ก์ปค) - ๋ฉ์์ง๋ฅผ ์ค๊ณํ๋ ์๋ฒ (Mosquitto, AWS IoT Core ๋ฑ)
Subscriber(๊ตฌ๋
์) - ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ๋ ํด๋ผ์ฐ๋/์ฑ
ํ ํฝ(Topic)์ด๋ผ๋ ๊ฐ๋
์ผ๋ก ๋ฉ์์ง๋ฅผ ๋ถ๋ฅํด์.
์๋ฅผ ๋ค์ด home/livingroom/temperature ์ด๋ฐ ์์ผ๋ก ๊ณ์ธต ๊ตฌ์กฐ๋ก ๋ง๋ค ์ ์์ด์.
์, ์ด์ ์ง์ง ์ฝ๋ ๋ค์ด๊ฐ๋๋ค! ์ฒซ ๋ฒ์งธ๋ Python + MQTT ์กฐํฉ์ด์์.
๋ผ์ฆ๋ฒ ๋ฆฌํ์ด๋ ์ผ๋ฐ ๋ฆฌ๋
์ค ์๋ฒ์์ ๊ฐ์ฅ ๋ง์ด ์ฐ๋ ์กฐํฉ์ด์ฃ ๐
paho-mqtt ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊ฑฐ์์. ์ค์น๋ถํฐ ์์!
# ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น pip install paho-mqtt pip install requests # HTTP ์ ๋ก๋์ฉ pip install python-dotenv # ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ์ฉ
import paho.mqtt.client as mqtt
import json
import time
import random
import ssl
from datetime import datetime
# ===== ์ค์ ๊ฐ =====
BROKER_HOST = "your-broker.example.com" # MQTT ๋ธ๋ก์ปค ์ฃผ์
BROKER_PORT = 8883 # TLS ํฌํธ (์ผ๋ฐ์ 1883)
CLIENT_ID = "sensor_device_001"
TOPIC = "home/livingroom/sensors"
USERNAME = "your_username"
PASSWORD = "your_password"
# TLS ์ธ์ฆ์ ๊ฒฝ๋ก (๋ณด์ ์ฐ๊ฒฐ ์ ํ์)
CA_CERT = "./certs/ca.crt"
CLIENT_CERT = "./certs/client.crt"
CLIENT_KEY = "./certs/client.key"
# ===== ์ฝ๋ฐฑ ํจ์๋ค =====
def on_connect(client, userdata, flags, rc):
"""๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ฑ๊ณต/์คํจ ์ฝ๋ฐฑ"""
if rc == 0:
print(f"โ
๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ฑ๊ณต! ์ฝ๋: {rc}")
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ. ์ฝ๋: {rc}")
# rc ์ฝ๋ ์๋ฏธ:
# 1: ์๋ชป๋ ํ๋กํ ์ฝ ๋ฒ์
# 2: ํด๋ผ์ด์ธํธ ID ๊ฑฐ๋ถ
# 3: ์๋ฒ ์ฌ์ฉ ๋ถ๊ฐ
# 4: ์๋ชป๋ ์ฌ์ฉ์๋ช
/๋น๋ฐ๋ฒํธ
# 5: ๊ถํ ์์
def on_publish(client, userdata, mid):
"""๋ฉ์์ง ๋ฐํ ์๋ฃ ์ฝ๋ฐฑ"""
print(f"๐ค ๋ฉ์์ง ๋ฐํ ์๋ฃ. MID: {mid}")
def on_disconnect(client, userdata, rc):
"""์ฐ๊ฒฐ ํด์ ์ฝ๋ฐฑ"""
if rc != 0:
print(f"โ ๏ธ ์์์น ๋ชปํ ์ฐ๊ฒฐ ํด์ . ์ฌ์ฐ๊ฒฐ ์๋...")
# ===== ์ผ์ ๋ฐ์ดํฐ ์ฝ๊ธฐ ํจ์ =====
def read_sensor_data():
"""
์ค์ ํ๊ฒฝ์์๋ GPIO, I2C, SPI ๋ฑ์ผ๋ก ์ผ์ ์ฝ๊ธฐ
์ฌ๊ธฐ์๋ ์๋ฎฌ๋ ์ด์
๋ฐ์ดํฐ ์ฌ์ฉ
"""
return {
"device_id": CLIENT_ID,
"timestamp": datetime.utcnow().isoformat() + "Z",
"temperature": round(random.uniform(18.0, 35.0), 2), # ์ญ์จ
"humidity": round(random.uniform(30.0, 80.0), 2), # %
"pressure": round(random.uniform(1000.0, 1020.0), 2), # hPa
"battery": round(random.uniform(3.0, 4.2), 2), # V
"rssi": random.randint(-90, -40) # dBm (์ ํธ ๊ฐ๋)
}
# ===== MQTT ํด๋ผ์ด์ธํธ ์ค์ =====
def setup_mqtt_client():
client = mqtt.Client(
client_id=CLIENT_ID,
clean_session=True,
protocol=mqtt.MQTTv311 # MQTT 3.1.1 ๋ฒ์ ์ฌ์ฉ
)
# ์ธ์ฆ ์ค์
client.username_pw_set(USERNAME, PASSWORD)
# TLS/SSL ์ค์ (๋ณด์ ์ฐ๊ฒฐ)
client.tls_set(
ca_certs=CA_CERT,
certfile=CLIENT_CERT,
keyfile=CLIENT_KEY,
tls_version=ssl.PROTOCOL_TLSv1_2
)
# ์ฝ๋ฐฑ ๋ฑ๋ก
client.on_connect = on_connect
client.on_publish = on_publish
client.on_disconnect = on_disconnect
# Last Will Testament (LWT) ์ค์
# ๋๋ฐ์ด์ค๊ฐ ๊ฐ์๊ธฐ ๋๊ฒผ์ ๋ ๋ธ๋ก์ปค๊ฐ ์๋์ผ๋ก ๋ฐํํ๋ ๋ฉ์์ง
lwt_payload = json.dumps({
"device_id": CLIENT_ID,
"status": "offline",
"timestamp": datetime.utcnow().isoformat() + "Z"
})
client.will_set(
topic=f"home/devices/{CLIENT_ID}/status",
payload=lwt_payload,
qos=1,
retain=True
)
return client
# ===== ๋ฉ์ธ ์คํ =====
def main():
client = setup_mqtt_client()
# ๋ธ๋ก์ปค ์ฐ๊ฒฐ (๋น๋๊ธฐ)
client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)
# ๋ฐฑ๊ทธ๋ผ์ด๋ ๋คํธ์ํฌ ๋ฃจํ ์์
client.loop_start()
# ์จ๋ผ์ธ ์ํ ๋ฐํ
online_payload = json.dumps({
"device_id": CLIENT_ID,
"status": "online",
"timestamp": datetime.utcnow().isoformat() + "Z"
})
client.publish(
topic=f"home/devices/{CLIENT_ID}/status",
payload=online_payload,
qos=1,
retain=True
)
print("๐ ์ผ์ ๋ฐ์ดํฐ ์ ์ก ์์!")
try:
while True:
# ์ผ์ ๋ฐ์ดํฐ ์ฝ๊ธฐ
sensor_data = read_sensor_data()
# JSON ์ง๋ ฌํ
payload = json.dumps(sensor_data, ensure_ascii=False)
# MQTT ๋ฐํ
# QoS ๋ ๋ฒจ:
# 0: ์ต๋ 1ํ ์ ๋ฌ (๋น ๋ฅด์ง๋ง ์ ์ค ๊ฐ๋ฅ)
# 1: ์ต์ 1ํ ์ ๋ฌ (์ค๋ณต ๊ฐ๋ฅํ์ง๋ง ์ ์ค ์์)
# 2: ์ ํํ 1ํ ์ ๋ฌ (๊ฐ์ฅ ์์ ํ์ง๋ง ๋๋ฆผ)
result = client.publish(
topic=TOPIC,
payload=payload,
qos=1,
retain=False
)
if result.rc == mqtt.MQTT_ERR_SUCCESS:
print(f"โ
์ ์ก ์ฑ๊ณต: {sensor_data['temperature']}ยฐC, {sensor_data['humidity']}%")
else:
print(f"โ ์ ์ก ์คํจ. ์๋ฌ ์ฝ๋: {result.rc}")
time.sleep(5) # 5์ด๋ง๋ค ์ ์ก
except KeyboardInterrupt:
print("\nโ ์ข
๋ฃ ์ ํธ ๊ฐ์ง. ์ ๋ฆฌ ์ค...")
finally:
client.loop_stop()
client.disconnect()
print("๐ ์ฐ๊ฒฐ ์ข
๋ฃ ์๋ฃ")
if __name__ == "__main__":
main()
์๋/๊ฒฝ๊ณ ๋ฐ์ดํฐ โ QoS 1 (๋ฐ๋์ ๋๋ฌํด์ผ ํจ)
๊ธ์ต/์๋ฃ ๋ฐ์ดํฐ โ QoS 2 (์ ํํ ํ ๋ฒ๋ง ์ ๋ฌ ๋ณด์ฅ)
์ค์ IoT ํ๊ฒฝ์์๋ ๋คํธ์ํฌ๊ฐ ๋๋ ๋๊ฒจ์ใ
ใ
๊ทธ๋์ ์ฌ์ฐ๊ฒฐ ๋ก์ง์ ์ง์ง ํ์์
๋๋ค. ์์ผ๋ฉด ๋์ค์ ์์ฒญ ํํํด์ ๐
import paho.mqtt.client as mqtt
import json
import time
import threading
from datetime import datetime
class RobustMQTTPublisher:
"""
์ฌ์ฐ๊ฒฐ ๊ธฐ๋ฅ์ด ํฌํจ๋ ์์ ์ ์ธ MQTT ํผ๋ธ๋ฆฌ์
์ง์ ๋ฐฑ์คํ(Exponential Backoff) ๋ฐฉ์์ผ๋ก ์ฌ์ฐ๊ฒฐ ์๋
"""
def __init__(self, broker_host, broker_port, client_id, topic):
self.broker_host = broker_host
self.broker_port = broker_port
self.client_id = client_id
self.topic = topic
self.is_connected = False
self.reconnect_delay = 1 # ์ด๊ธฐ ์ฌ์ฐ๊ฒฐ ๋๊ธฐ ์๊ฐ (์ด)
self.max_reconnect_delay = 60 # ์ต๋ ์ฌ์ฐ๊ฒฐ ๋๊ธฐ ์๊ฐ (์ด)
self.message_queue = [] # ์คํ๋ผ์ธ ์ค ์์ธ ๋ฉ์์ง ํ
self.max_queue_size = 100 # ์ต๋ ํ ํฌ๊ธฐ
self.client = mqtt.Client(client_id=client_id)
self.client.on_connect = self._on_connect
self.client.on_disconnect = self._on_disconnect
self.client.on_publish = self._on_publish
def _on_connect(self, client, userdata, flags, rc):
if rc == 0:
self.is_connected = True
self.reconnect_delay = 1 # ์ฑ๊ณต ์ ๋๋ ์ด ๋ฆฌ์
print(f"โ
[{datetime.now().strftime('%H:%M:%S')}] ์ฐ๊ฒฐ ์ฑ๊ณต!")
# ์คํ๋ผ์ธ ์ค ์์ธ ๋ฉ์์ง ์ฒ๋ฆฌ
self._flush_queue()
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ. RC: {rc}")
def _on_disconnect(self, client, userdata, rc):
self.is_connected = False
if rc != 0:
print(f"โ ๏ธ ์์์น ๋ชปํ ์ฐ๊ฒฐ ํด์ . {self.reconnect_delay}์ด ํ ์ฌ์ฐ๊ฒฐ...")
threading.Thread(target=self._reconnect, daemon=True).start()
def _on_publish(self, client, userdata, mid):
pass # ๋ฐํ ์๋ฃ ์ฒ๋ฆฌ
def _reconnect(self):
"""์ง์ ๋ฐฑ์คํ ๋ฐฉ์ ์ฌ์ฐ๊ฒฐ"""
while not self.is_connected:
try:
self.client.reconnect()
break
except Exception as e:
print(f"๐ ์ฌ์ฐ๊ฒฐ ์คํจ: {e}. {self.reconnect_delay}์ด ํ ์ฌ์๋...")
time.sleep(self.reconnect_delay)
# ์ง์ ๋ฐฑ์คํ: 1 โ 2 โ 4 โ 8 โ ... โ 60์ด
self.reconnect_delay = min(
self.reconnect_delay * 2,
self.max_reconnect_delay
)
def _flush_queue(self):
"""์คํ๋ผ์ธ ์ค ์์ธ ๋ฉ์์ง ์ผ๊ด ์ ์ก"""
if self.message_queue:
print(f"๐ฆ ํ์ ์์ธ {len(self.message_queue)}๊ฐ ๋ฉ์์ง ์ ์ก ์ค...")
while self.message_queue and self.is_connected:
msg = self.message_queue.pop(0)
self.client.publish(self.topic, msg, qos=1)
time.sleep(0.1) # ๋ธ๋ก์ปค ๊ณผ๋ถํ ๋ฐฉ์ง
def publish(self, data: dict):
"""๋ฐ์ดํฐ ๋ฐํ (์คํ๋ผ์ธ ์ ํ์ ์ ์ฅ)"""
payload = json.dumps(data, ensure_ascii=False)
if self.is_connected:
result = self.client.publish(self.topic, payload, qos=1)
return result.rc == mqtt.MQTT_ERR_SUCCESS
else:
# ์คํ๋ผ์ธ ์ํ: ํ์ ์ ์ฅ
if len(self.message_queue) < self.max_queue_size:
self.message_queue.append(payload)
print(f"๐ฅ ์คํ๋ผ์ธ ํ ์ ์ฅ. ํ์ฌ ํ ํฌ๊ธฐ: {len(self.message_queue)}")
else:
print("โ ๏ธ ํ ๊ฐ๋ ์ฐธ! ๊ฐ์ฅ ์ค๋๋ ๋ฉ์์ง ์ญ์ ")
self.message_queue.pop(0)
self.message_queue.append(payload)
return False
def connect(self):
self.client.connect(self.broker_host, self.broker_port, keepalive=60)
self.client.loop_start()
def disconnect(self):
self.client.loop_stop()
self.client.disconnect()
# ์ฌ์ฉ ์์
publisher = RobustMQTTPublisher(
broker_host="broker.example.com",
broker_port=1883,
client_id="sensor_001",
topic="sensors/data"
)
publisher.connect()
# ๋ฐ์ดํฐ ์ ์ก
publisher.publish({
"temperature": 25.3,
"humidity": 60.5,
"timestamp": datetime.utcnow().isoformat() + "Z"
})
AWS IoT Core๋ ํ์ฌ ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ IoT ํด๋ผ์ฐ๋ ํ๋ซํผ์ด์์.
์์ญ์ต ๊ฐ์ ๋๋ฐ์ด์ค๋ฅผ ์ฐ๊ฒฐํ ์ ์๊ณ , ๋ค๋ฅธ AWS ์๋น์ค(DynamoDB, S3, Lambda ๋ฑ)์
์ฐ๋์ด ์ฌ์์ ์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ์์ ํนํ ์ธ๊ธฐ ๋ง์์ ๐
AWS IoT Core๋ X.509 ์ธ์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ๋๋ฐ์ด์ค๋ฅผ ์ธ์ฆํด์.
๋น๋ฐ๋ฒํธ ๋ฐฉ์์ด ์๋๋ผ ์ธ์ฆ์ ๋ฐฉ์์ด๋ผ ํจ์ฌ ์์ ํ์ฃ !
AWS ์ฝ์์์ Thing์ ์์ฑํ๊ณ ์ธ์ฆ์๋ฅผ ๋ค์ด๋ก๋ํด์ผ ํด์.
# AWS CLI๋ก IoT Thing ์์ฑ ๋ฐ ์ธ์ฆ์ ๋ฐ๊ธ
# (AWS CLI ์ค์น ๋ฐ ์๊ฒฉ์ฆ๋ช
์ค์ ํ์)
# 1. Thing ์์ฑ
aws iot create-thing --thing-name "MySensor001"
# 2. ์ธ์ฆ์ ์์ฑ ๋ฐ ํ์ฑํ
aws iot create-keys-and-certificate \
--set-as-active \
--certificate-pem-outfile "device.cert.pem" \
--public-key-outfile "device.public.key" \
--private-key-outfile "device.private.key"
# 3. ์ ์ฑ
์์ฑ (JSON ํ์ผ๋ก ์ ์ฅ ํ ์คํ)
# iot-policy.json ๋ด์ฉ:
# {
# "Version": "2012-10-17",
# "Statement": [{
# "Effect": "Allow",
# "Action": ["iot:Connect", "iot:Publish", "iot:Subscribe", "iot:Receive"],
# "Resource": ["arn:aws:iot:ap-northeast-2:*:*"]
# }]
# }
aws iot create-policy \
--policy-name "SensorPolicy" \
--policy-document file://iot-policy.json
# 4. ์ ์ฑ
์ ์ธ์ฆ์์ ์ฐ๊ฒฐ
aws iot attach-policy \
--policy-name "SensorPolicy" \
--target "arn:aws:iot:ap-northeast-2:123456789:cert/your-cert-id"
# 5. ์ธ์ฆ์๋ฅผ Thing์ ์ฐ๊ฒฐ
aws iot attach-thing-principal \
--thing-name "MySensor001" \
--principal "arn:aws:iot:ap-northeast-2:123456789:cert/your-cert-id"
# 6. AWS IoT ์๋ํฌ์ธํธ ํ์ธ
aws iot describe-endpoint --endpoint-type iot:Data-ATS
# ๊ฒฐ๊ณผ: "xxxxx-ats.iot.ap-northeast-2.amazonaws.com"
"""
AWS IoT Core MQTT ์ฐ๊ฒฐ ๋ฐ ๋ฐ์ดํฐ ์
๋ก๋
ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: pip install awsiotsdk
"""
from awscrt import mqtt
from awsiot import mqtt_connection_builder
import json
import time
import threading
from datetime import datetime
# ===== AWS IoT ์ค์ =====
ENDPOINT = "xxxxx-ats.iot.ap-northeast-2.amazonaws.com"
CLIENT_ID = "MySensor001"
TOPIC = "sensors/MySensor001/data"
CERT_FILEPATH = "./certs/device.cert.pem"
PRI_KEY_FILEPATH = "./certs/device.private.key"
CA_FILEPATH = "./certs/AmazonRootCA1.pem" # AWS ๋ฃจํธ CA ์ธ์ฆ์
# ์ฐ๊ฒฐ ์๋ฃ ์ด๋ฒคํธ
connected_future = threading.Event()
disconnect_future = threading.Event()
def on_connection_interrupted(connection, error, **kwargs):
"""์ฐ๊ฒฐ ์ค๋จ ์ฝ๋ฐฑ"""
print(f"โ ๏ธ ์ฐ๊ฒฐ ์ค๋จ: {error}")
def on_connection_resumed(connection, return_code, session_present, **kwargs):
"""์ฐ๊ฒฐ ์ฌ๊ฐ ์ฝ๋ฐฑ"""
print(f"โ
์ฐ๊ฒฐ ์ฌ๊ฐ. ์ธ์
์ ์ง: {session_present}")
def on_connection_success(connection, callback_data):
"""์ฐ๊ฒฐ ์ฑ๊ณต ์ฝ๋ฐฑ"""
print(f"๐ AWS IoT Core ์ฐ๊ฒฐ ์ฑ๊ณต!")
connected_future.set()
def on_connection_failure(connection, callback_data):
"""์ฐ๊ฒฐ ์คํจ ์ฝ๋ฐฑ"""
print(f"โ ์ฐ๊ฒฐ ์คํจ: {callback_data.error}")
def on_connection_closed(connection, callback_data):
"""์ฐ๊ฒฐ ์ข
๋ฃ ์ฝ๋ฐฑ"""
print("๐ ์ฐ๊ฒฐ ์ข
๋ฃ")
disconnect_future.set()
# ===== MQTT ์ฐ๊ฒฐ ๋น๋ =====
mqtt_connection = mqtt_connection_builder.mtls_from_path(
endpoint=ENDPOINT,
port=8883,
cert_filepath=CERT_FILEPATH,
pri_key_filepath=PRI_KEY_FILEPATH,
ca_filepath=CA_FILEPATH,
client_id=CLIENT_ID,
clean_session=False, # ์ธ์
์ ์ง (QoS 1 ๋ฉ์์ง ๋ณด์ฅ)
keep_alive_secs=30,
on_connection_interrupted=on_connection_interrupted,
on_connection_resumed=on_connection_resumed,
on_connection_success=on_connection_success,
on_connection_failure=on_connection_failure,
on_connection_closed=on_connection_closed
)
print(f"๐ AWS IoT Core ์ฐ๊ฒฐ ์ค... ({ENDPOINT})")
connect_future = mqtt_connection.connect()
connect_future.result() # ์ฐ๊ฒฐ ์๋ฃ๊น์ง ๋๊ธฐ
print("โ
์ฐ๊ฒฐ ์๋ฃ! ๋ฐ์ดํฐ ์ ์ก ์์")
# ===== ์ผ์ ๋ฐ์ดํฐ ์ ์ก =====
def send_sensor_data(temperature, humidity, pressure):
"""์ผ์ ๋ฐ์ดํฐ๋ฅผ AWS IoT Core๋ก ์ ์ก"""
payload = {
"device_id": CLIENT_ID,
"timestamp": datetime.utcnow().isoformat() + "Z",
"location": {
"building": "A๋",
"floor": 3,
"room": "์๋ฒ์ค"
},
"sensors": {
"temperature": {
"value": temperature,
"unit": "celsius"
},
"humidity": {
"value": humidity,
"unit": "percent"
},
"pressure": {
"value": pressure,
"unit": "hPa"
}
},
"metadata": {
"firmware_version": "1.2.3",
"battery_level": 85,
"signal_strength": -65
}
}
message_json = json.dumps(payload, ensure_ascii=False)
# MQTT ๋ฐํ
publish_future, packet_id = mqtt_connection.publish(
topic=TOPIC,
payload=message_json,
qos=mqtt.QoS.AT_LEAST_ONCE # QoS 1
)
publish_future.result() # ๋ฐํ ์๋ฃ ๋๊ธฐ
print(f"๐ค ์ ์ก ์๋ฃ (ํจํท ID: {packet_id}): {temperature}ยฐC, {humidity}%")
# ===== ๋๋ฐ์ด์ค ์๋์ฐ ์
๋ฐ์ดํธ =====
def update_device_shadow(state: dict):
"""
AWS IoT ๋๋ฐ์ด์ค ์๋์ฐ ์
๋ฐ์ดํธ
์๋์ฐ: ๋๋ฐ์ด์ค์ ํ์ฌ ์ํ๋ฅผ ํด๋ผ์ฐ๋์ ์ ์ฅํ๋ ๊ธฐ๋ฅ
์คํ๋ผ์ธ ๋๋ฐ์ด์ค์ ์ํ๋ ํด๋ผ์ฐ๋์์ ํ์ธ ๊ฐ๋ฅ
"""
shadow_topic = f"$aws/things/{CLIENT_ID}/shadow/update"
shadow_payload = {
"state": {
"reported": state # ๋๋ฐ์ด์ค๊ฐ ์ค์ ๋ก ๋ณด๊ณ ํ๋ ์ํ
}
}
mqtt_connection.publish(
topic=shadow_topic,
payload=json.dumps(shadow_payload),
qos=mqtt.QoS.AT_LEAST_ONCE
)
print(f"๐ ๋๋ฐ์ด์ค ์๋์ฐ ์
๋ฐ์ดํธ ์๋ฃ")
# ๋ฉ์ธ ๋ฃจํ
try:
import random
while True:
temp = round(random.uniform(18.0, 35.0), 2)
hum = round(random.uniform(30.0, 80.0), 2)
pres = round(random.uniform(1000.0, 1020.0), 2)
send_sensor_data(temp, hum, pres)
# ์๋์ฐ๋ ์ฃผ๊ธฐ์ ์ผ๋ก ์
๋ฐ์ดํธ
update_device_shadow({
"temperature": temp,
"humidity": hum,
"status": "active"
})
time.sleep(10)
except KeyboardInterrupt:
print("\nโ ์ข
๋ฃ ์ค...")
disconnect_future_result = mqtt_connection.disconnect()
disconnect_future_result.result()
print("โ
์ ์ ์ข
๋ฃ")
Firebase๋ ๊ตฌ๊ธ์ด ๋ง๋ BaaS(Backend as a Service)์ธ๋ฐ,
์๊ท๋ชจ IoT ํ๋ก์ ํธ๋ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์ง์ง ์ต๊ณ ์์!
์ค์ ์ด ๊ฐ๋จํ๊ณ , ์ค์๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ๊ฐ ๊ธฐ๋ณธ์ผ๋ก ์ง์๋ผ์
๋์๋ณด๋ ๋ง๋ค๊ธฐ๋ ์์ฒญ ํธํด์ ใ
ใ
๐
ํนํ Firebase Realtime Database๋ WebSocket ๊ธฐ๋ฐ์ผ๋ก ์ค์๊ฐ ์
๋ฐ์ดํธ๊ฐ ๋๊ณ ,
Firestore๋ ๋ ๊ฐ๋ ฅํ ์ฟผ๋ฆฌ์ ํ์ฅ์ฑ์ ์ ๊ณตํด์.
"""
Firebase Realtime Database / Firestore IoT ๋ฐ์ดํฐ ์
๋ก๋
pip install firebase-admin
"""
import firebase_admin
from firebase_admin import credentials, db, firestore
import json
import time
from datetime import datetime
import random
# ===== Firebase ์ด๊ธฐํ =====
# Firebase ์ฝ์์์ ์๋น์ค ๊ณ์ ํค ๋ค์ด๋ก๋ ํ์
cred = credentials.Certificate("./firebase-service-account.json")
firebase_admin.initialize_app(cred, {
'databaseURL': 'https://your-project-id-default-rtdb.firebaseio.com/'
})
# Firestore ํด๋ผ์ด์ธํธ
firestore_db = firestore.client()
# ===== Realtime Database ์
๋ก๋ =====
def upload_to_realtime_db(device_id: str, sensor_data: dict):
"""
Firebase Realtime Database์ ์ผ์ ๋ฐ์ดํฐ ์
๋ก๋
๊ตฌ์กฐ: /devices/{device_id}/latest ์ ์ต์ ๋ฐ์ดํฐ ์ ์ฅ
/devices/{device_id}/history/{timestamp} ์ ์ด๋ ฅ ์ ์ฅ
"""
ref = db.reference(f'/devices/{device_id}')
# ์ต์ ๋ฐ์ดํฐ ์
๋ฐ์ดํธ (๋ฎ์ด์ฐ๊ธฐ)
ref.child('latest').set(sensor_data)
# ์ด๋ ฅ ๋ฐ์ดํฐ ์ถ๊ฐ (ํ์์คํฌํ๋ฅผ ํค๋ก ์ฌ์ฉ)
timestamp_key = datetime.utcnow().strftime('%Y%m%d_%H%M%S_%f')
ref.child('history').child(timestamp_key).set(sensor_data)
print(f"โ
Realtime DB ์
๋ก๋ ์๋ฃ: {device_id}")
# ===== Firestore ์
๋ก๋ =====
def upload_to_firestore(device_id: str, sensor_data: dict):
"""
Firestore์ ์ผ์ ๋ฐ์ดํฐ ์
๋ก๋
์ปฌ๋ ์
: sensor_readings
๋ฌธ์: ์๋ ID ์์ฑ
"""
# ํ์์คํฌํ ์ถ๊ฐ
sensor_data['created_at'] = firestore.SERVER_TIMESTAMP
sensor_data['device_id'] = device_id
# ์ปฌ๋ ์
์ ๋ฌธ์ ์ถ๊ฐ
doc_ref = firestore_db.collection('sensor_readings').add(sensor_data)
print(f"โ
Firestore ์
๋ก๋ ์๋ฃ. ๋ฌธ์ ID: {doc_ref[1].id}")
# ๋๋ฐ์ด์ค ์ํ ๋ฌธ์ ์
๋ฐ์ดํธ (upsert)
device_ref = firestore_db.collection('devices').document(device_id)
device_ref.set({
'last_seen': firestore.SERVER_TIMESTAMP,
'latest_data': sensor_data,
'status': 'online'
}, merge=True) # merge=True: ๊ธฐ์กด ํ๋ ์ ์งํ๋ฉด์ ์
๋ฐ์ดํธ
# ===== ๋ฐฐ์น ์
๋ก๋ (์ฌ๋ฌ ์ผ์ ๋์ ์ฒ๋ฆฌ) =====
def batch_upload_to_firestore(readings: list):
"""
์ฌ๋ฌ ์ผ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์น๋ก ํ ๋ฒ์ ์
๋ก๋
Firestore ๋ฐฐ์น ์ฐ๊ธฐ: ์ต๋ 500๊ฐ ๋ฌธ์ ๋์ ์ฒ๋ฆฌ
"""
batch = firestore_db.batch()
collection_ref = firestore_db.collection('sensor_readings')
for reading in readings[:500]: # ์ต๋ 500๊ฐ ์ ํ
doc_ref = collection_ref.document() # ์๋ ID
reading['created_at'] = firestore.SERVER_TIMESTAMP
batch.set(doc_ref, reading)
batch.commit()
print(f"โ
๋ฐฐ์น ์
๋ก๋ ์๋ฃ: {len(readings)}๊ฐ ๋ฌธ์")
# ===== ์ค์๊ฐ ๋ฆฌ์ค๋ (ํด๋ผ์ฐ๋ โ ๋๋ฐ์ด์ค ๋ช
๋ น ์์ ) =====
def setup_command_listener(device_id: str):
"""
Firestore์์ ๋๋ฐ์ด์ค ๋ช
๋ น์ ์ค์๊ฐ์ผ๋ก ์์
ํด๋ผ์ฐ๋์์ ๋๋ฐ์ด์ค๋ฅผ ์๊ฒฉ ์ ์ดํ ๋ ์ฌ์ฉ
"""
def on_snapshot(doc_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED' or change.type.name == 'MODIFIED':
command = change.document.to_dict()
print(f"๐ฉ ๋ช
๋ น ์์ : {command}")
# ๋ช
๋ น ์ฒ๋ฆฌ
if command.get('action') == 'reboot':
print("๐ ์ฌ๋ถํ
๋ช
๋ น ์คํ!")
elif command.get('action') == 'update_interval':
new_interval = command.get('interval', 10)
print(f"โฑ๏ธ ์ ์ก ์ฃผ๊ธฐ ๋ณ๊ฒฝ: {new_interval}์ด")
# ๋ช
๋ น ์ฒ๋ฆฌ ์๋ฃ ํ์
change.document.reference.update({
'status': 'executed',
'executed_at': firestore.SERVER_TIMESTAMP
})
# ๋ช
๋ น ์ปฌ๋ ์
๊ตฌ๋
commands_ref = firestore_db.collection('devices').document(device_id)\
.collection('commands')\
.where('status', '==', 'pending')
watch = commands_ref.on_snapshot(on_snapshot)
return watch
# ===== ๋ฉ์ธ ์คํ =====
DEVICE_ID = "sensor_001"
# ๋ช
๋ น ๋ฆฌ์ค๋ ์์
command_watcher = setup_command_listener(DEVICE_ID)
print("๐ Firebase ๋ฐ์ดํฐ ์
๋ก๋ ์์!")
try:
while True:
sensor_data = {
"temperature": round(random.uniform(18.0, 35.0), 2),
"humidity": round(random.uniform(30.0, 80.0), 2),
"co2": round(random.uniform(400, 1200), 0),
"lux": round(random.uniform(0, 1000), 1),
"timestamp": datetime.utcnow().isoformat() + "Z"
}
# Realtime DB์ Firestore ๋์ ์
๋ก๋
upload_to_realtime_db(DEVICE_ID, sensor_data)
upload_to_firestore(DEVICE_ID, sensor_data)
time.sleep(10)
except KeyboardInterrupt:
command_watcher.unsubscribe()
print("โ
์ข
๋ฃ ์๋ฃ")
IoT ๋ฐ์ดํฐ๋ ๋น ๋ฅด๊ฒ ์์ด๋๊น ๋ฐ์ดํฐ ๋ณด์กด ์ ์ฑ (TTL)์ ๊ผญ ์ค์ ํ์ธ์!
์ค๋๋ ๋ฐ์ดํฐ๋ Cloud Functions๋ก ์๋ ์ญ์ ํ๊ฑฐ๋ BigQuery๋ก ์ด๊ดํ๋ ๊ฒ ์ข์์.
๋ง์ดํฌ๋ก์ํํธ์ Azure IoT Hub๋ ์ํฐํ๋ผ์ด์ฆ ํ๊ฒฝ์์ ๊ฐ์ธ์์.
ํนํ Windows ๊ธฐ๋ฐ ์ธํ๋ผ๋ฅผ ์ฐ๋ ๊ธฐ์
๋ค์ด ๋ง์ด ์ ํํ์ฃ .
Azure IoT Hub๋ ๋๋ฐ์ด์ค ๊ด๋ฆฌ, ๋ณด์, ์ค์ผ์ผ๋ง ๋ชจ๋ ๊ฐ๋ ฅํด์ ๐ช
"""
Azure IoT Hub ๋ฐ์ดํฐ ์
๋ก๋
pip install azure-iot-device
"""
import asyncio
import json
import random
from datetime import datetime
from azure.iot.device.aio import IoTHubDeviceClient
from azure.iot.device import Message
# ===== Azure IoT Hub ์ฐ๊ฒฐ ๋ฌธ์์ด =====
# Azure ํฌํธ โ IoT Hub โ ๋๋ฐ์ด์ค โ ์ฐ๊ฒฐ ๋ฌธ์์ด ๋ณต์ฌ
CONNECTION_STRING = "HostName=your-hub.azure-devices.net;DeviceId=MySensor001;SharedAccessKey=your-key=="
# ===== ์ง์ ๋ฉ์๋ ํธ๋ค๋ฌ (ํด๋ผ์ฐ๋ โ ๋๋ฐ์ด์ค ๋ช
๋ น) =====
async def method_request_handler(method_request):
"""
Azure IoT Hub ์ง์ ๋ฉ์๋ ์์ ํธ๋ค๋ฌ
ํด๋ผ์ฐ๋์์ ๋๋ฐ์ด์ค๋ก ์ง์ ํจ์๋ฅผ ํธ์ถํ๋ ๊ธฐ๋ฅ
"""
print(f"๐ฉ ์ง์ ๋ฉ์๋ ์์ : {method_request.name}")
print(f" ํ์ด๋ก๋: {method_request.payload}")
if method_request.name == "reboot":
print("๐ ์ฌ๋ถํ
๋ช
๋ น ์คํ!")
response_payload = {"result": "reboot_initiated", "status": "success"}
status_code = 200
elif method_request.name == "get_status":
response_payload = {
"device_id": "MySensor001",
"uptime": 3600,
"firmware": "v1.2.3",
"status": "healthy"
}
status_code = 200
elif method_request.name == "set_interval":
new_interval = method_request.payload.get("interval", 10)
print(f"โฑ๏ธ ์ ์ก ์ฃผ๊ธฐ ๋ณ๊ฒฝ: {new_interval}์ด")
response_payload = {"result": f"interval_set_to_{new_interval}s"}
status_code = 200
else:
response_payload = {"error": "unknown_method"}
status_code = 404
# ์๋ต ๋ฐํ
from azure.iot.device import MethodResponse
method_response = MethodResponse.create_from_method_request(
method_request, status_code, response_payload
)
await client.send_method_response(method_response)
# ===== ํธ์ ํจ์น ํธ๋ค๋ฌ (์ํ๋ ์์ฑ ์์ ) =====
async def twin_patch_handler(patch):
"""
Azure IoT ๋๋ฐ์ด์ค ํธ์์ desired ์์ฑ ๋ณ๊ฒฝ ์์
ํด๋ผ์ฐ๋์์ ๋๋ฐ์ด์ค ์ค์ ์ ์๊ฒฉ์ผ๋ก ๋ณ๊ฒฝํ ๋ ์ฌ์ฉ
"""
print(f"๐ง ํธ์ ํจ์น ์์ : {patch}")
if "telemetry_interval" in patch:
print(f"โฑ๏ธ ํ
๋ ๋ฉํธ๋ฆฌ ์ฃผ๊ธฐ ๋ณ๊ฒฝ: {patch['telemetry_interval']}์ด")
if "threshold" in patch:
print(f"๐ก๏ธ ์๊ณ๊ฐ ๋ณ๊ฒฝ: {patch['threshold']}")
# reported ์์ฑ ์
๋ฐ์ดํธ (์ค์ ์ ์ฉ ํ์ธ)
reported_patch = {
"telemetry_interval": patch.get("telemetry_interval", 10),
"last_config_update": datetime.utcnow().isoformat() + "Z"
}
await client.patch_twin_reported_properties(reported_patch)
# ===== ๋ฉ์ธ ๋น๋๊ธฐ ํจ์ =====
async def main():
global client
# ํด๋ผ์ด์ธํธ ์์ฑ ๋ฐ ์ฐ๊ฒฐ
client = IoTHubDeviceClient.create_from_connection_string(CONNECTION_STRING)
# ํธ๋ค๋ฌ ๋ฑ๋ก
client.on_method_request_received = method_request_handler
client.on_twin_desired_properties_patch_received = twin_patch_handler
await client.connect()
print("โ
Azure IoT Hub ์ฐ๊ฒฐ ์๋ฃ!")
# ์ด๊ธฐ ํธ์ reported ์์ฑ ์ค์
await client.patch_twin_reported_properties({
"firmware_version": "1.2.3",
"hardware_version": "rev_B",
"startup_time": datetime.utcnow().isoformat() + "Z",
"capabilities": ["temperature", "humidity", "pressure"]
})
try:
while True:
# ์ผ์ ๋ฐ์ดํฐ ์์ฑ
temperature = round(random.uniform(18.0, 35.0), 2)
humidity = round(random.uniform(30.0, 80.0), 2)
# Azure IoT Hub ๋ฉ์์ง ์์ฑ
telemetry = {
"temperature": temperature,
"humidity": humidity,
"timestamp": datetime.utcnow().isoformat() + "Z"
}
message = Message(json.dumps(telemetry))
# ๋ฉ์์ง ์์ฑ ์ค์ (๋ผ์ฐํ
์ ํ์ฉ)
message.content_encoding = "utf-8"
message.content_type = "application/json"
# ์ปค์คํ
์์ฑ ์ถ๊ฐ (IoT Hub ๋ฉ์์ง ๋ผ์ฐํ
์กฐ๊ฑด์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅ)
message.custom_properties["device_type"] = "temperature_sensor"
message.custom_properties["location"] = "building_A"
# ์จ๋ ์๊ณ๊ฐ ์ด๊ณผ ์ ์๋ ์์ฑ ์ถ๊ฐ
if temperature > 30.0:
message.custom_properties["alert"] = "high_temperature"
print(f"๐จ ๊ณ ์จ ๊ฒฝ๋ณด! {temperature}ยฐC")
# ๋ฉ์์ง ์ ์ก
await client.send_message(message)
print(f"๐ค Azure IoT Hub ์ ์ก: {temperature}ยฐC, {humidity}%")
await asyncio.sleep(10)
except KeyboardInterrupt:
print("\nโ ์ข
๋ฃ ์ค...")
finally:
await client.disconnect()
print("โ
์ฐ๊ฒฐ ์ข
๋ฃ")
# ์คํ
asyncio.run(main())
์๋์ด๋
ธ๋ ESP32 ๊ฐ์ ๋ง์ดํฌ๋ก์ปจํธ๋กค๋ฌ์์ ์ง์ ํด๋ผ์ฐ๋๋ก ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฆฌ๋ ๊ฒฝ์ฐ๋ ๋ง์์.
์ด๋๋ C++ ๊ธฐ๋ฐ์ Arduino ์ฝ๋๋ฅผ ์จ์ผ ํ์ฃ .
ESP32๋ WiFi๊ฐ ๋ด์ฅ๋์ด ์์ด์ IoT ํ๋ก์ ํธ์์ ์์ฒญ ์ธ๊ธฐ ์์ด์ ๐
/*
* ESP32 IoT ์ผ์ ๋ฐ์ดํฐ HTTP ์
๋ก๋
* ํ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
* - WiFi (๋ด์ฅ)
* - HTTPClient (๋ด์ฅ)
* - ArduinoJson (๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋งค๋์ ์์ ์ค์น)
* - DHT sensor library (์จ์ต๋ ์ผ์์ฉ)
*/
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <time.h>
// ===== WiFi ์ค์ =====
const char* WIFI_SSID = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";
// ===== ์๋ฒ ์ค์ =====
const char* SERVER_URL = "https://api.yourserver.com/v1/sensors/data";
const char* API_KEY = "your_api_key_here";
const char* DEVICE_ID = "esp32_sensor_001";
// ===== ์ผ์ ์ค์ =====
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
// ===== ์ ์ก ์ฃผ๊ธฐ =====
const unsigned long SEND_INTERVAL = 30000; // 30์ด
unsigned long lastSendTime = 0;
// ===== WiFi ์ฐ๊ฒฐ ํจ์ =====
void connectWiFi() {
Serial.print("WiFi ์ฐ๊ฒฐ ์ค: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(1000);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nโ
WiFi ์ฐ๊ฒฐ ์ฑ๊ณต!");
Serial.print("IP ์ฃผ์: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nโ WiFi ์ฐ๊ฒฐ ์คํจ. ์ฌ์์...");
ESP.restart();
}
}
// ===== NTP ์๊ฐ ๋๊ธฐํ =====
void syncTime() {
configTime(9 * 3600, 0, "pool.ntp.org", "time.nist.gov");
Serial.print("์๊ฐ ๋๊ธฐํ ์ค");
struct tm timeinfo;
while (!getLocalTime(&timeinfo)) {
Serial.print(".");
delay(500);
}
Serial.println("\nโ
์๊ฐ ๋๊ธฐํ ์๋ฃ");
}
// ===== ISO 8601 ํ์์คํฌํ ์์ฑ =====
String getTimestamp() {
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return "1970-01-01T00:00:00Z";
}
char timestamp[30];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &timeinfo);
return String(timestamp);
}
// ===== HTTP POST ๋ฐ์ดํฐ ์ ์ก =====
bool sendSensorData(float temperature, float humidity) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("โ ๏ธ WiFi ์ฐ๊ฒฐ ๋๊น. ์ฌ์ฐ๊ฒฐ...");
connectWiFi();
return false;
}
HTTPClient http;
// HTTPS ์ฐ๊ฒฐ ์ค์
http.begin(SERVER_URL);
http.addHeader("Content-Type", "application/json");
http.addHeader("X-API-Key", API_KEY);
http.addHeader("X-Device-ID", DEVICE_ID);
http.setTimeout(10000); // 10์ด ํ์์์
// JSON ํ์ด๋ก๋ ์์ฑ
StaticJsonDocument<512> doc;
doc["device_id"] = DEVICE_ID;
doc["timestamp"] = getTimestamp();
JsonObject sensors = doc.createNestedObject("sensors");
sensors["temperature"] = round(temperature * 100) / 100.0;
sensors["humidity"] = round(humidity * 100) / 100.0;
JsonObject meta = doc.createNestedObject("metadata");
meta["wifi_rssi"] = WiFi.RSSI();
meta["free_heap"] = ESP.getFreeHeap();
meta["uptime"] = millis() / 1000;
String payload;
serializeJson(doc, payload);
Serial.println("๐ค ์ ์ก ์ค: " + payload);
// POST ์์ฒญ
int httpCode = http.POST(payload);
if (httpCode > 0) {
String response = http.getString();
Serial.printf("โ
HTTP ์๋ต: %d\n", httpCode);
Serial.println("์๋ต ๋ณธ๋ฌธ: " + response);
http.end();
return httpCode == 200 || httpCode == 201;
} else {
Serial.printf("โ HTTP ์ค๋ฅ: %s\n", http.errorToString(httpCode).c_str());
http.end();
return false;
}
}
// ===== ์ฌ์๋ ๋ก์ง ํฌํจ ์ ์ก =====
void sendWithRetry(float temperature, float humidity, int maxRetries = 3) {
for (int i = 0; i < maxRetries; i++) {
if (sendSensorData(temperature, humidity)) {
return; // ์ฑ๊ณต
}
Serial.printf("โ ๏ธ ์ ์ก ์คํจ. %d/%d ์ฌ์๋...\n", i + 1, maxRetries);
delay(2000 * (i + 1)); // ์ ์ง์ ๋๊ธฐ
}
Serial.println("โ ์ต๋ ์ฌ์๋ ํ์ ์ด๊ณผ. ๋ฐ์ดํฐ ์ ์ค");
// ์ค์ ํ๊ฒฝ์์๋ SPIFFS/LittleFS์ ๋ก์ปฌ ์ ์ฅ ํ ๋์ค์ ์ฌ์ ์ก
}
// ===== ๋ฅ์ฌ๋ฆฝ ๋ชจ๋ (๋ฐฐํฐ๋ฆฌ ์ ์ฝ) =====
void enterDeepSleep(int seconds) {
Serial.printf("๐ค ๋ฅ์ฌ๋ฆฝ ์ง์
: %d์ด\n", seconds);
esp_sleep_enable_timer_wakeup(seconds * 1000000ULL); // ๋ง์ดํฌ๋ก์ด ๋จ์
esp_deep_sleep_start();
}
// ===== Setup =====
void setup() {
Serial.begin(115200);
Serial.println("\n๐ ESP32 IoT ์ผ์ ์์!");
dht.begin();
connectWiFi();
syncTime();
Serial.println("โ
์ด๊ธฐํ ์๋ฃ");
}
// ===== Loop =====
void loop() {
unsigned long currentTime = millis();
if (currentTime - lastSendTime >= SEND_INTERVAL) {
// ์ผ์ ์ฝ๊ธฐ
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
// ์ผ์ ์ฝ๊ธฐ ์คํจ ์ฒดํฌ
if (isnan(humidity) || isnan(temperature)) {
Serial.println("โ ์ผ์ ์ฝ๊ธฐ ์คํจ!");
} else {
Serial.printf("๐ก๏ธ ์จ๋: %.2fยฐC, ๐ง ์ต๋: %.2f%%\n", temperature, humidity);
sendWithRetry(temperature, humidity);
}
lastSendTime = currentTime;
}
// ๋ฐฐํฐ๋ฆฌ ๊ตฌ๋ ์ ๋ฅ์ฌ๋ฆฝ ์ฌ์ฉ (์๋ ์ฃผ์ ํด์ )
// sendWithRetry(temperature, humidity);
// enterDeepSleep(30); // 30์ด ์ฌ๋ฆฝ ํ ๊นจ์ด๋จ
}
/*
* ESP32 MQTT ๋ฐ์ดํฐ ์
๋ก๋ (PubSubClient ๋ผ์ด๋ธ๋ฌ๋ฆฌ)
* ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋งค๋์ ์์ "PubSubClient" ์ค์น ํ์
*/
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
const char* WIFI_SSID = "your_ssid";
const char* WIFI_PASSWORD = "your_password";
// MQTT ๋ธ๋ก์ปค ์ค์ (HiveMQ Cloud ์์)
const char* MQTT_BROKER = "your-cluster.hivemq.cloud";
const int MQTT_PORT = 8883; // TLS ํฌํธ
const char* MQTT_USER = "your_username";
const char* MQTT_PASSWORD = "your_password";
const char* MQTT_CLIENT_ID = "esp32_001";
const char* MQTT_TOPIC = "sensors/esp32_001/data";
// HiveMQ Cloud ๋ฃจํธ CA ์ธ์ฆ์
const char* ROOT_CA = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
"... (์ค์ ์ธ์ฆ์ ๋ด์ฉ) ...\n" \
"-----END CERTIFICATE-----\n";
WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);
// MQTT ๋ฉ์์ง ์์ ์ฝ๋ฐฑ (๊ตฌ๋
์ ์ฌ์ฉ)
void mqttCallback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println("๐ฉ ์์ ํ ํฝ: " + String(topic));
Serial.println(" ๋ฉ์์ง: " + message);
// JSON ํ์ฑ
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, message);
if (!error) {
if (doc.containsKey("command")) {
String cmd = doc["command"].as<String>();
if (cmd == "reboot") {
Serial.println("๐ ์ฌ๋ถํ
๋ช
๋ น!");
delay(1000);
ESP.restart();
}
}
}
}
void reconnectMQTT() {
int retries = 0;
while (!mqttClient.connected() && retries < 5) {
Serial.print("MQTT ์ฐ๊ฒฐ ์๋...");
if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD)) {
Serial.println("โ
์ฐ๊ฒฐ ์ฑ๊ณต!");
// ๋ช
๋ น ์์ ์ ์ํ ๊ตฌ๋
String cmdTopic = "sensors/" + String(MQTT_CLIENT_ID) + "/commands";
mqttClient.subscribe(cmdTopic.c_str());
} else {
Serial.printf("โ ์คํจ. ์ํ: %d. 5์ด ํ ์ฌ์๋...\n", mqttClient.state());
delay(5000);
retries++;
}
}
}
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nโ
WiFi ์ฐ๊ฒฐ!");
// TLS ์ค์
espClient.setCACert(ROOT_CA);
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
mqttClient.setBufferSize(1024); // ๋ฒํผ ํฌ๊ธฐ ์ฆ๊ฐ
reconnectMQTT();
}
void loop() {
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
static unsigned long lastMsg = 0;
if (millis() - lastMsg > 10000) {
lastMsg = millis();
// JSON ํ์ด๋ก๋
StaticJsonDocument<256> doc;
doc["device_id"] = MQTT_CLIENT_ID;
doc["temperature"] = 25.3;
doc["humidity"] = 60.5;
doc["uptime"] = millis() / 1000;
char payload[256];
serializeJson(doc, payload);
if (mqttClient.publish(MQTT_TOPIC, payload, true)) {
Serial.println("๐ค MQTT ์ ์ก ์๋ฃ: " + String(payload));
} else {
Serial.println("โ MQTT ์ ์ก ์คํจ");
}
}
}
์ฌ๋ฌ ์ผ์์์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ์์ ํด๋ผ์ฐ๋๋ก ์ฌ๋ฆฌ๋ ๊ฒ์ดํธ์จ์ด ์๋ฒ๋ฅผ Node.js๋ก ๋ง๋ค์ด๋ณผ๊ฒ์.
Node.js๋ ๋น๋๊ธฐ I/O๊ฐ ๊ฐ์ ์ด๋ผ ์ฌ๋ฌ ์ผ์ ์ฐ๊ฒฐ์ ๋์์ ์ฒ๋ฆฌํ๊ธฐ ๋ฑ์ด์์!
์ค์ ํ์
์์๋ ์ฃ์ง ๊ฒ์ดํธ์จ์ด๋ก Node.js ๋ง์ด ์จ์ ๐
/**
* Node.js IoT ๊ฒ์ดํธ์จ์ด ์๋ฒ
* ์ฌ๋ฌ ์ผ์์์ ๋ฐ์ดํฐ ์์ง โ ์ฒ๋ฆฌ โ ํด๋ผ์ฐ๋ ์
๋ก๋
*
* npm install mqtt axios winston node-cron
*/
const mqtt = require('mqtt');
const axios = require('axios');
const winston = require('winston');
const cron = require('node-cron');
// ===== ๋ก๊ฑฐ ์ค์ =====
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.colorize(),
winston.format.printf(({ timestamp, level, message }) => {
return `[${timestamp}] ${level}: ${message}`;
})
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'gateway.log' })
]
});
// ===== ์ค์ =====
const CONFIG = {
mqtt: {
broker: 'mqtt://localhost:1883',
topics: ['sensors/#'], // ๋ชจ๋ ์ผ์ ํ ํฝ ๊ตฌ๋
clientId: 'gateway_server_001'
},
cloud: {
endpoint: 'https://api.yourcloud.com/v1/telemetry',
apiKey: process.env.CLOUD_API_KEY,
batchSize: 50, // ๋ฐฐ์น ํฌ๊ธฐ
flushInterval: 5000 // 5์ด๋ง๋ค ๋ฐฐ์น ์ ์ก
}
};
// ===== ๋ฐ์ดํฐ ๋ฒํผ (๋ฐฐ์น ์ฒ๋ฆฌ์ฉ) =====
class DataBuffer {
constructor(maxSize, flushInterval, flushCallback) {
this.buffer = [];
this.maxSize = maxSize;
this.flushCallback = flushCallback;
// ์ฃผ๊ธฐ์ ํ๋ฌ์
setInterval(() => {
if (this.buffer.length > 0) {
this.flush();
}
}, flushInterval);
}
add(data) {
this.buffer.push(data);
// ๋ฒํผ ๊ฐ๋ ์ฐจ๋ฉด ์ฆ์ ํ๋ฌ์
if (this.buffer.length >= this.maxSize) {
this.flush();
}
}
flush() {
if (this.buffer.length === 0) return;
const batch = [...this.buffer];
this.buffer = [];
this.flushCallback(batch).catch(err => {
logger.error(`๋ฐฐ์น ์ ์ก ์คํจ: ${err.message}`);
// ์คํจํ ๋ฐ์ดํฐ ๋ค์ ๋ฒํผ์ ์ถ๊ฐ (์ฌ์๋)
this.buffer.unshift(...batch);
});
}
}
// ===== ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ฐ ์ ์ =====
function validateAndClean(rawData) {
const required = ['device_id', 'timestamp'];
for (const field of required) {
if (!rawData[field]) {
throw new Error(`ํ์ ํ๋ ๋๋ฝ: ${field}`);
}
}
// ์จ๋ ๋ฒ์ ๊ฒ์ฆ (-50ยฐC ~ 100ยฐC)
if (rawData.temperature !== undefined) {
if (rawData.temperature < -50 || rawData.temperature > 100) {
throw new Error(`์จ๋ ๋ฒ์ ์ด๊ณผ: ${rawData.temperature}`);
}
}
// ์ต๋ ๋ฒ์ ๊ฒ์ฆ (0% ~ 100%)
if (rawData.humidity !== undefined) {
if (rawData.humidity < 0 || rawData.humidity > 100) {
throw new Error(`์ต๋ ๋ฒ์ ์ด๊ณผ: ${rawData.humidity}`);
}
}
// ํ์์คํฌํ ์ ๊ทํ
const ts = new Date(rawData.timestamp);
if (isNaN(ts.getTime())) {
rawData.timestamp = new Date().toISOString();
}
// ๊ฒ์ดํธ์จ์ด ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ
rawData.gateway_id = CONFIG.mqtt.clientId;
rawData.received_at = new Date().toISOString();
return rawData;
}
// ===== ํด๋ผ์ฐ๋ ๋ฐฐ์น ์
๋ก๋ =====
async function uploadBatch(batch) {
const payload = {
batch_id: `batch_${Date.now()}`,
count: batch.length,
data: batch
};
const response = await axios.post(CONFIG.cloud.endpoint, payload, {
headers: {
'Content-Type': 'application/json',
'X-API-Key': CONFIG.cloud.apiKey,
'X-Gateway-ID': CONFIG.mqtt.clientId
},
timeout: 30000 // 30์ด ํ์์์
});
logger.info(`โ
๋ฐฐ์น ์
๋ก๋ ์๋ฃ: ${batch.length}๊ฐ โ HTTP ${response.status}`);
return response.data;
}
// ===== ์ฌ์๋ ๋ํผ =====
async function withRetry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
logger.warn(`์ฌ์๋ ${i + 1}/${maxRetries}: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
}
// ===== ํต๊ณ ์์ง =====
const stats = {
received: 0,
processed: 0,
failed: 0,
uploaded: 0
};
// 1๋ถ๋ง๋ค ํต๊ณ ์ถ๋ ฅ
cron.schedule('* * * * *', () => {
logger.info(`๐ ํต๊ณ | ์์ : ${stats.received} | ์ฒ๋ฆฌ: ${stats.processed} | ์คํจ: ${stats.failed} | ์
๋ก๋: ${stats.uploaded}`);
});
// ===== ๋ฉ์ธ ๊ฒ์ดํธ์จ์ด =====
const dataBuffer = new DataBuffer(
CONFIG.cloud.batchSize,
CONFIG.cloud.flushInterval,
(batch) => withRetry(() => uploadBatch(batch))
);
// MQTT ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ
const mqttClient = mqtt.connect(CONFIG.mqtt.broker, {
clientId: CONFIG.mqtt.clientId,
clean: false,
reconnectPeriod: 5000,
connectTimeout: 30000
});
mqttClient.on('connect', () => {
logger.info('โ
MQTT ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์๋ฃ');
// ๋ชจ๋ ์ผ์ ํ ํฝ ๊ตฌ๋
CONFIG.mqtt.topics.forEach(topic => {
mqttClient.subscribe(topic, { qos: 1 }, (err) => {
if (!err) logger.info(`๐ก ๊ตฌ๋
์์: ${topic}`);
});
});
});
mqttClient.on('message', (topic, message) => {
stats.received++;
try {
const rawData = JSON.parse(message.toString());
const cleanData = validateAndClean(rawData);
dataBuffer.add(cleanData);
stats.processed++;
logger.debug(`๐ฅ ์์ : ${topic} โ ${cleanData.device_id}`);
} catch (error) {
stats.failed++;
logger.error(`โ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์คํจ [${topic}]: ${error.message}`);
}
});
mqttClient.on('error', (error) => {
logger.error(`MQTT ์ค๋ฅ: ${error.message}`);
});
mqttClient.on('reconnect', () => {
logger.warn('๐ MQTT ์ฌ์ฐ๊ฒฐ ์ค...');
});
logger.info('๐ IoT ๊ฒ์ดํธ์จ์ด ์๋ฒ ์์!');
IoT ๋ณด์์ ์ง์ง ์ค์ํ๋ฐ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ํํ ํ๋ ๋ถ๋ถ์ด์์.
2016๋
๋ฏธ๋ผ์ด ๋ด๋ท ์ฌ๊ฑด ๊ธฐ์ตํ์ธ์? ์์ญ๋ง ๊ฐ์ IoT ๊ธฐ๊ธฐ๊ฐ ํดํน๋นํด์
๋๊ท๋ชจ DDoS ๊ณต๊ฒฉ์ ์ฌ์ฉ๋์์์. ๊ทธ ํผํด๊ฐ ์์ฒญ๋ฌ์ฃ ๐ฑ
๋ณด์์ ๋์ค์ ์ถ๊ฐํ๋ ๊ฒ ์๋๋ผ ์ฒ์๋ถํฐ ์ค๊ณ์ ํฌํจํด์ผ ํด์!
"""
๋ฏผ๊ฐ ์ ๋ณด๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉํ๋ฉด ์ ๋ ์ ๋ฉ๋๋ค!
.env ํ์ผ๊ณผ python-dotenv๋ฅผ ์ฌ์ฉํ์ธ์.
"""
# .env ํ์ผ (์ ๋ Git์ ์ปค๋ฐํ์ง ๋ง์ธ์!)
# .gitignore์ .env ์ถ๊ฐ ํ์!
"""
AWS_IOT_ENDPOINT=xxxxx-ats.iot.ap-northeast-2.amazonaws.com
AWS_IOT_CLIENT_ID=MySensor001
MQTT_USERNAME=your_username
MQTT_PASSWORD=your_secure_password
FIREBASE_PROJECT_ID=your-project-id
AZURE_CONNECTION_STRING=HostName=...
"""
# Python ์ฝ๋์์ ํ๊ฒฝ๋ณ์ ์ฌ์ฉ
from dotenv import load_dotenv
import os
load_dotenv() # .env ํ์ผ ๋ก๋
AWS_ENDPOINT = os.getenv('AWS_IOT_ENDPOINT')
CLIENT_ID = os.getenv('AWS_IOT_CLIENT_ID')
MQTT_USER = os.getenv('MQTT_USERNAME')
MQTT_PASS = os.getenv('MQTT_PASSWORD')
# ํ๊ฒฝ๋ณ์ ๋๋ฝ ์ฒดํฌ
required_vars = ['AWS_IOT_ENDPOINT', 'AWS_IOT_CLIENT_ID', 'MQTT_USERNAME', 'MQTT_PASSWORD']
missing = [var for var in required_vars if not os.getenv(var)]
if missing:
raise EnvironmentError(f"ํ์ ํ๊ฒฝ๋ณ์ ๋๋ฝ: {', '.join(missing)}")
print("โ
ํ๊ฒฝ๋ณ์ ๋ก๋ ์๋ฃ")
# ===== HMAC ์๋ช
์ผ๋ก ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ =====
import hmac
import hashlib
import base64
SECRET_KEY = os.getenv('HMAC_SECRET_KEY', 'default_secret').encode()
def sign_payload(payload: str) -> str:
"""ํ์ด๋ก๋์ HMAC-SHA256 ์๋ช
์ถ๊ฐ"""
signature = hmac.new(SECRET_KEY, payload.encode(), hashlib.sha256).digest()
return base64.b64encode(signature).decode()
def verify_payload(payload: str, signature: str) -> bool:
"""์๋ช
๊ฒ์ฆ"""
expected = sign_payload(payload)
return hmac.compare_digest(expected, signature)
# ์ฌ์ฉ ์์
import json
data = {"temperature": 25.3, "timestamp": "2024-01-01T00:00:00Z"}
payload_str = json.dumps(data, sort_keys=True)
sig = sign_payload(payload_str)
# ์ ์ก ์ ์๋ช
ํฌํจ
message = {
"payload": data,
"signature": sig
}
# ์์ ์ธก์์ ๊ฒ์ฆ
is_valid = verify_payload(payload_str, sig)
print(f"์๋ช
๊ฒ์ฆ: {'โ
์ ํจ' if is_valid else 'โ ์์กฐ ๊ฐ์ง!'}")
IoT ํ๊ฒฝ์์๋ ๋ฐ์ดํฐ ํฌ๊ธฐ๋ฅผ ์ค์ด๋ ๊ฒ์ด ๋ฐฐํฐ๋ฆฌ ์๋ช
๊ณผ ํต์ ๋น์ฉ์ ์ง๊ฒฐ๋ผ์.
JSON์ ์ฌ๋์ด ์ฝ๊ธฐ ์ข์ง๋ง ์ฉ๋์ด ํฌ๊ฑฐ๋ ์. ์ค์ ํ์
์์๋ ๋ค์ํ ์ต์ ํ ๊ธฐ๋ฒ์ ์จ์.
| ํฌ๋งท | ํน์ง | ํฌ๊ธฐ ๋น๊ต | ์ ํฉํ ์ํฉ |
|---|---|---|---|
| JSON | ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์, ๋ฒ์ฉ | 100% (๊ธฐ์ค) | ๋๋ฒ๊น , ์๊ท๋ชจ ํ๋ก์ ํธ |
| MessagePack | JSON์ ๋ฐ์ด๋๋ฆฌ ๋ฒ์ | ~50~70% | ์ค๊ฐ ๊ท๋ชจ, ๋น ๋ฅธ ์ง๋ ฌํ |
| Protocol Buffers | ๊ตฌ๊ธ ๊ฐ๋ฐ, ์คํค๋ง ๊ธฐ๋ฐ | ~30~50% | ๋๊ท๋ชจ, ์๊ฒฉํ ์คํค๋ง |
| CBOR | RFC 7049, IoT ํ์ค | ~40~60% | CoAP๊ณผ ํจ๊ป ์ฌ์ฉ |
| gzip ์์ถ | ํ ์คํธ ๋ฐ์ดํฐ์ ํจ๊ณผ์ | ~20~40% | ๋ฐฐ์น ์ ์ก, HTTP |
"""
MessagePack์ผ๋ก IoT ๋ฐ์ดํฐ ์์ถ ์ ์ก
pip install msgpack
"""
import msgpack
import json
import gzip
import time
# ์ํ ์ผ์ ๋ฐ์ดํฐ
sensor_data = {
"device_id": "sensor_001",
"timestamp": "2024-01-15T09:30:00Z",
"temperature": 25.34,
"humidity": 60.12,
"pressure": 1013.25,
"co2": 450,
"lux": 320.5,
"battery": 3.85,
"rssi": -65
}
# ===== ํฌ๊ธฐ ๋น๊ต =====
# 1. JSON (์ผ๋ฐ)
json_bytes = json.dumps(sensor_data).encode('utf-8')
print(f"JSON: {len(json_bytes)} bytes")
# 2. JSON + gzip
json_gzip = gzip.compress(json_bytes, compresslevel=6)
print(f"JSON + gzip: {len(json_gzip)} bytes ({len(json_gzip)/len(json_bytes)*100:.1f}%)")
# 3. MessagePack
msgpack_bytes = msgpack.packb(sensor_data, use_bin_type=True)
print(f"MessagePack: {len(msgpack_bytes)} bytes ({len(msgpack_bytes)/len(json_bytes)*100:.1f}%)")
# 4. MessagePack + gzip
msgpack_gzip = gzip.compress(msgpack_bytes, compresslevel=6)
print(f"MessagePack + gzip: {len(msgpack_gzip)} bytes ({len(msgpack_gzip)/len(json_bytes)*100:.1f}%)")
# ===== ๋ฐฐ์น ๋ฐ์ดํฐ ์์ถ ์ ์ก =====
def compress_batch(data_list: list) -> bytes:
"""๋ฐฐ์น ๋ฐ์ดํฐ๋ฅผ MessagePack + gzip์ผ๋ก ์์ถ"""
packed = msgpack.packb(data_list, use_bin_type=True)
compressed = gzip.compress(packed, compresslevel=6)
return compressed
def decompress_batch(compressed: bytes) -> list:
"""์์ถ ํด์ """
decompressed = gzip.decompress(compressed)
return msgpack.unpackb(decompressed, raw=False)
# 100๊ฐ ๋ฐ์ดํฐ ๋ฐฐ์น ์์ถ ํ
์คํธ
batch = [sensor_data.copy() for _ in range(100)]
compressed = compress_batch(batch)
original_size = len(json.dumps(batch).encode())
print(f"\n๋ฐฐ์น 100๊ฐ:")
print(f" ์๋ณธ JSON: {original_size:,} bytes")
print(f" ์์ถ ํ: {len(compressed):,} bytes ({len(compressed)/original_size*100:.1f}%)")
# ===== ๋ธํ ์ธ์ฝ๋ฉ (๋ณํ๋๋ง ์ ์ก) =====
class DeltaEncoder:
"""
์ด์ ๊ฐ๊ณผ์ ์ฐจ์ด(๋ธํ)๋ง ์ ์กํ๋ ์ธ์ฝ๋
์ผ์ ๊ฐ์ด ์ฒ์ฒํ ๋ณํ ๋ ๋งค์ฐ ํจ๊ณผ์
"""
def __init__(self, threshold=0.1):
self.last_values = {}
self.threshold = threshold # ๋ณํ ์๊ณ๊ฐ
def encode(self, device_id: str, data: dict) -> dict:
"""๋ณํ๊ฐ ์๋ ํ๋๋ง ๋ฐํ"""
if device_id not in self.last_values:
self.last_values[device_id] = data.copy()
return data # ์ฒซ ๋ฒ์งธ๋ ์ ์ฒด ์ ์ก
delta = {
"device_id": device_id,
"timestamp": data.get("timestamp"),
"type": "delta"
}
changed = False
for key, value in data.items():
if key in ("device_id", "timestamp"):
continue
last_val = self.last_values[device_id].get(key)
if last_val is None or abs(value - last_val) > self.threshold:
delta[key] = value
self.last_values[device_id][key] = value
changed = True
if not changed:
return None # ๋ณํ ์์ผ๋ฉด ์ ์ก ์ ํจ
return delta
# ์ฌ์ฉ ์์
encoder = DeltaEncoder(threshold=0.5)
readings = [
{"device_id": "s001", "timestamp": "T1", "temperature": 25.0, "humidity": 60.0},
{"device_id": "s001", "timestamp": "T2", "temperature": 25.1, "humidity": 60.0}, # ๋ณํ ์์
{"device_id": "s001", "timestamp": "T3", "temperature": 25.8, "humidity": 61.2}, # ์จ๋ ๋ณํ
{"device_id": "s001", "timestamp": "T4", "temperature": 25.9, "humidity": 61.3}, # ๋ฏธ์ธ ๋ณํ
]
for reading in readings:
delta = encoder.encode("s001", reading)
if delta:
print(f"๐ค ์ ์ก: {delta}")
else:
print(f"โญ๏ธ ์คํต (๋ณํ ์์): {reading['timestamp']}")
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ข
ํฉํด์ ์ค์ ๋ก ์ธ ์ ์๋ ์์ฑ๋ ์์ค๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ์!
๋ผ์ฆ๋ฒ ๋ฆฌํ์ด + ๋ค์ค ์ผ์ + AWS IoT Core + DynamoDB ์กฐํฉ์ด์์.
์ด ์ ๋๋ฉด ์ค์ ์ค๋งํธํ ํ๋ก์ ํธ์ ๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅํ ์์ค์ด์์ ๐
์ฐธ๊ณ ๋ก ์ด๋ฐ IoT ํ๋ก์ ํธ ๊ฐ๋ฐ ์์ค๋ ์ปค์คํ
๊ตฌํ์ด ํ์ํ๋ค๋ฉด
์ฌ๋ฅ๋ท(https://www.jaenung.net)์์ IoT/์๋ฒ ๋๋ ์ ๋ฌธ ๊ฐ๋ฐ์๋ฅผ ์ฐพ์๋ณผ ์ ์์ด์!
"""
์ค๋งํธํ IoT ์ผ์ ์์คํ
- ์์ฑ ์์ค
ํ์ผ ๊ตฌ์กฐ:
smart_home/
โโโ config.py # ์ค์ ๊ด๋ฆฌ
โโโ sensors.py # ์ผ์ ์ถ์ํ ๋ ์ด์ด
โโโ uploader.py # ํด๋ผ์ฐ๋ ์
๋ก๋ ๋ชจ๋
โโโ local_storage.py # ๋ก์ปฌ ์์ ์ ์ฅ
โโโ main.py # ๋ฉ์ธ ์คํ ํ์ผ
"""
# ===== config.py =====
import os
from dataclasses import dataclass
from dotenv import load_dotenv
load_dotenv()
@dataclass
class Config:
# AWS IoT
aws_endpoint: str = os.getenv('AWS_IOT_ENDPOINT', '')
aws_client_id: str = os.getenv('AWS_IOT_CLIENT_ID', 'smart_home_001')
aws_cert_path: str = os.getenv('AWS_CERT_PATH', './certs/device.cert.pem')
aws_key_path: str = os.getenv('AWS_KEY_PATH', './certs/device.private.key')
aws_ca_path: str = os.getenv('AWS_CA_PATH', './certs/AmazonRootCA1.pem')
# ์ผ์ ์ค์
send_interval: int = int(os.getenv('SEND_INTERVAL', '30')) # ์ด
# ๋ก์ปฌ ์ ์ฅ์
local_db_path: str = os.getenv('LOCAL_DB_PATH', './data/sensor_cache.db')
max_local_records: int = int(os.getenv('MAX_LOCAL_RECORDS', '10000'))
config = Config()
# ===== sensors.py =====
import json
from datetime import datetime
from abc import ABC, abstractmethod
class BaseSensor(ABC):
"""์ผ์ ์ถ์ ๊ธฐ๋ณธ ํด๋์ค"""
def __init__(self, sensor_id: str, location: str):
self.sensor_id = sensor_id
self.location = location
@abstractmethod
def read(self) -> dict:
"""์ผ์ ๊ฐ ์ฝ๊ธฐ (์๋ธํด๋์ค์์ ๊ตฌํ)"""
pass
def get_reading(self) -> dict:
"""ํ์คํ๋ ์ผ์ ๋ฐ์ดํฐ ๋ฐํ"""
raw = self.read()
return {
"sensor_id": self.sensor_id,
"location": self.location,
"timestamp": datetime.utcnow().isoformat() + "Z",
"data": raw,
"metadata": {
"sensor_type": self.__class__.__name__,
"firmware": "1.0.0"
}
}
class DHT22Sensor(BaseSensor):
"""DHT22 ์จ์ต๋ ์ผ์"""
def __init__(self, sensor_id: str, location: str, pin: int):
super().__init__(sensor_id, location)
self.pin = pin
# ์ค์ ํ๊ฒฝ: import Adafruit_DHT
# self.dht = Adafruit_DHT.DHT22
def read(self) -> dict:
# ์ค์ ํ๊ฒฝ:
# humidity, temperature = Adafruit_DHT.read_retry(self.dht, self.pin)
import random
return {
"temperature": round(random.uniform(18.0, 35.0), 2),
"humidity": round(random.uniform(30.0, 80.0), 2),
"unit": {"temperature": "celsius", "humidity": "percent"}
}
class MQ135Sensor(BaseSensor):
"""MQ-135 ๊ณต๊ธฐ์ง ์ผ์ (CO2, VOC)"""
def __init__(self, sensor_id: str, location: str, adc_channel: int):
super().__init__(sensor_id, location)
self.adc_channel = adc_channel
def read(self) -> dict:
import random
return {
"co2_ppm": round(random.uniform(400, 1500), 0),
"air_quality_index": random.randint(0, 500),
"status": "good" if random.random() > 0.3 else "moderate"
}
class SensorManager:
"""๋ค์ค ์ผ์ ๊ด๋ฆฌ์"""
def __init__(self):
self.sensors = []
def add_sensor(self, sensor: BaseSensor):
self.sensors.append(sensor)
return self
def read_all(self) -> list:
"""๋ชจ๋ ์ผ์ ๋์ ์ฝ๊ธฐ"""
import concurrent.futures
readings = []
with concurrent.futures.ThreadPoolExecutor(max_workers=len(self.sensors)) as executor:
futures = {executor.submit(sensor.get_reading): sensor for sensor in self.sensors}
for future in concurrent.futures.as_completed(futures):
try:
reading = future.result(timeout=5)
readings.append(reading)
except Exception as e:
sensor = futures[future]
print(f"โ ์ผ์ ์ฝ๊ธฐ ์คํจ [{sensor.sensor_id}]: {e}")
return readings
# ===== local_storage.py =====
import sqlite3
import json
from datetime import datetime
class LocalStorage:
"""์คํ๋ผ์ธ ์ ๋ก์ปฌ SQLite ์์ ์ ์ฅ์"""
def __init__(self, db_path: str):
self.db_path = db_path
self._init_db()
def _init_db(self):
with sqlite3.connect(self.db_path) as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS sensor_cache (
id INTEGER PRIMARY KEY AUTOINCREMENT,
data TEXT NOT NULL,
created_at TEXT NOT NULL,
uploaded INTEGER DEFAULT 0
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_uploaded ON sensor_cache(uploaded)")
def save(self, readings: list):
"""์ผ์ ๋ฐ์ดํฐ ๋ก์ปฌ ์ ์ฅ"""
with sqlite3.connect(self.db_path) as conn:
for reading in readings:
conn.execute(
"INSERT INTO sensor_cache (data, created_at) VALUES (?, ?)",
(json.dumps(reading), datetime.utcnow().isoformat())
)
print(f"๐พ ๋ก์ปฌ ์ ์ฅ: {len(readings)}๊ฐ")
def get_pending(self, limit: int = 100) -> list:
"""์
๋ก๋ ๋๊ธฐ ์ค์ธ ๋ฐ์ดํฐ ์กฐํ"""
with sqlite3.connect(self.db_path) as conn:
rows = conn.execute(
"SELECT id, data FROM sensor_cache WHERE uploaded = 0 ORDER BY id LIMIT ?",
(limit,)
).fetchall()
return [(row[0], json.loads(row[1])) for row in rows]
def mark_uploaded(self, ids: list):
"""์
๋ก๋ ์๋ฃ ํ์"""
with sqlite3.connect(self.db_path) as conn:
conn.execute(
f"UPDATE sensor_cache SET uploaded = 1 WHERE id IN ({','.join('?' * len(ids))})",
ids
)
def cleanup(self, keep_days: int = 7):
"""์ค๋๋ ์
๋ก๋ ์๋ฃ ๋ฐ์ดํฐ ์ญ์ """
with sqlite3.connect(self.db_path) as conn:
result = conn.execute(
"""DELETE FROM sensor_cache
WHERE uploaded = 1
AND created_at < datetime('now', ?)""",
(f'-{keep_days} days',)
)
print(f"๐๏ธ ์ ๋ฆฌ ์๋ฃ: {result.rowcount}๊ฐ ์ญ์ ")
# ===== main.py =====
import time
import signal
import threading
from datetime import datetime
class SmartHomeIoT:
"""์ค๋งํธํ IoT ๋ฉ์ธ ์์คํ
"""
def __init__(self):
self.running = True
self.sensor_manager = SensorManager()
self.local_storage = LocalStorage('./data/cache.db')
# ์ผ์ ๋ฑ๋ก
self.sensor_manager\
.add_sensor(DHT22Sensor("dht22_living", "๊ฑฐ์ค", pin=4))\
.add_sensor(DHT22Sensor("dht22_bedroom", "์นจ์ค", pin=17))\
.add_sensor(MQ135Sensor("mq135_kitchen", "์ฃผ๋ฐฉ", adc_channel=0))
# ์๊ทธ๋ ํธ๋ค๋ฌ (Ctrl+C ์ฒ๋ฆฌ)
signal.signal(signal.SIGINT, self._shutdown)
signal.signal(signal.SIGTERM, self._shutdown)
print("๐ ์ค๋งํธํ IoT ์์คํ
์์!")
def _shutdown(self, signum, frame):
print("\nโ ์ข
๋ฃ ์ ํธ ์์ . ์์ ํ๊ฒ ์ข
๋ฃ ์ค...")
self.running = False
def _upload_pending(self):
"""๋ก์ปฌ์ ์์ธ ๋ฐ์ดํฐ ์ฌ์ ์ก (๋ฐฑ๊ทธ๋ผ์ด๋)"""
while self.running:
pending = self.local_storage.get_pending(limit=50)
if pending:
print(f"๐ค ๋๊ธฐ ์ค์ธ {len(pending)}๊ฐ ๋ฐ์ดํฐ ์ฌ์ ์ก ์๋...")
# ์ค์ ์
๋ก๋ ๋ก์ง (AWS IoT ๋ฑ)
ids = [item[0] for item in pending]
self.local_storage.mark_uploaded(ids)
time.sleep(60) # 1๋ถ๋ง๋ค ์ฒดํฌ
def run(self):
"""๋ฉ์ธ ๋ฃจํ"""
# ๋ฐฑ๊ทธ๋ผ์ด๋ ์ฌ์ ์ก ์ค๋ ๋
retry_thread = threading.Thread(target=self._upload_pending, daemon=True)
retry_thread.start()
# ์ ๊ธฐ ์ ๋ฆฌ ์์
cleanup_counter = 0
while self.running:
try:
# ๋ชจ๋ ์ผ์ ์ฝ๊ธฐ
readings = self.sensor_manager.read_all()
print(f"๐ {len(readings)}๊ฐ ์ผ์ ๋ฐ์ดํฐ ์์ง ์๋ฃ")
# ๋ก์ปฌ ์ ์ฅ (๋ฐฑ์
)
self.local_storage.save(readings)
# ํด๋ผ์ฐ๋ ์
๋ก๋ (์ฌ๊ธฐ์ AWS IoT, Firebase ๋ฑ ์ฐ๋)
# publisher.publish_batch(readings)
# 7์ผ๋ง๋ค ์ ๋ฆฌ
cleanup_counter += 1
if cleanup_counter >= 20160: # 30์ด * 20160 = 7์ผ
self.local_storage.cleanup(keep_days=7)
cleanup_counter = 0
time.sleep(30) # 30์ด ๋๊ธฐ
except Exception as e:
print(f"โ ์ค๋ฅ ๋ฐ์: {e}")
time.sleep(5)
print("โ
์์คํ
์ข
๋ฃ ์๋ฃ")
if __name__ == "__main__":
system = SmartHomeIoT()
system.run()
์ฝ๋ ์ง๋ ๊ฒ๋ ์ค์ํ์ง๋ง, ์ค์ ์ด์ ํ๊ฒฝ์์๋ ๋ชจ๋ํฐ๋ง๊ณผ ์ต์ ํ๊ฐ ์ง์ง ์ค์ํด์.
์ผ์๊ฐ ์์ญ ๊ฐ๋ง ๋์ด๊ฐ๋ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด์ง๊ฑฐ๋ ์ ๐
๊ทธ๋์ ์ฒ์๋ถํฐ ๋ชจ๋ํฐ๋ง ์ฒด๊ณ๋ฅผ ๊ฐ์ถ๋ ๊ฒ ๋์ค์ ์์ฒญ ํธํด์!
"""
Prometheus ๋ฉํธ๋ฆญ ์์ง์ผ๋ก IoT ์์คํ
๋ชจ๋ํฐ๋ง
pip install prometheus_client
"""
from prometheus_client import Counter, Gauge, Histogram, start_http_server
import time
# ===== ๋ฉํธ๋ฆญ ์ ์ =====
# ์ ์ก ์ฑ๊ณต/์คํจ ์นด์ดํฐ
messages_sent_total = Counter(
'iot_messages_sent_total',
'Total messages sent to cloud',
['device_id', 'status'] # ๋ ์ด๋ธ
)
# ํ์ฌ ์ผ์ ๊ฐ ๊ฒ์ด์ง
sensor_temperature = Gauge(
'iot_sensor_temperature_celsius',
'Current temperature reading',
['device_id', 'location']
)
sensor_humidity = Gauge(
'iot_sensor_humidity_percent',
'Current humidity reading',
['device_id', 'location']
)
# ์ ์ก ์ง์ฐ ํ์คํ ๊ทธ๋จ
upload_duration = Histogram(
'iot_upload_duration_seconds',
'Time spent uploading data to cloud',
['cloud_provider'],
buckets=[0.1, 0.5, 1.0, 2.0, 5.0, 10.0]
)
# ์ฐ๊ฒฐ ์ํ ๊ฒ์ด์ง
connection_status = Gauge(
'iot_connection_status',
'MQTT connection status (1=connected, 0=disconnected)',
['broker']
)
# ===== ๋ฉํธ๋ฆญ ์
๋ฐ์ดํธ ์์ =====
def record_upload(device_id: str, success: bool, duration: float, provider: str):
"""์
๋ก๋ ๊ฒฐ๊ณผ ๊ธฐ๋ก"""
status = 'success' if success else 'failure'
messages_sent_total.labels(device_id=device_id, status=status).inc()
upload_duration.labels(cloud_provider=provider).observe(duration)
def update_sensor_metrics(device_id: str, location: str, temp: float, hum: float):
"""์ผ์ ๊ฐ ๋ฉํธ๋ฆญ ์
๋ฐ์ดํธ"""
sensor_temperature.labels(device_id=device_id, location=location).set(temp)
sensor_humidity.labels(device_id=device_id, location=location).set(hum)
# Prometheus ๋ฉํธ๋ฆญ ์๋ฒ ์์ (ํฌํธ 8000)
start_http_server(8000)
print("๐ Prometheus ๋ฉํธ๋ฆญ ์๋ฒ ์์: http://localhost:8000/metrics")
# ์๋ฎฌ๋ ์ด์
import random
while True:
temp = random.uniform(20, 30)
hum = random.uniform(40, 70)
start = time.time()
# ์ค์ ์
๋ก๋ ๋ก์ง...
time.sleep(random.uniform(0.1, 0.5)) # ์
๋ก๋ ์๋ฎฌ๋ ์ด์
duration = time.time() - start
update_sensor_metrics("sensor_001", "๊ฑฐ์ค", temp, hum)
record_upload("sensor_001", True, duration, "aws_iot")
connection_status.labels(broker="aws_iot_core").set(1)
print(f"๐ ๋ฉํธ๋ฆญ ์
๋ฐ์ดํธ: {temp:.1f}ยฐC, {hum:.1f}%")
time.sleep(10)
Docker Compose๋ก Prometheus + Grafana๋ฅผ ํ ๋ฒ์ ์ฌ๋ฆฌ๋ฉด ์ค์ ์ด ํจ์ฌ ํธํด์!
AWS CloudWatch, Azure Monitor, Google Cloud Monitoring๋ ๊ฐ ํ๋ซํผ ๋ด์ฅ ๋ชจ๋ํฐ๋ง์ผ๋ก ํ์ฉ ๊ฐ๋ฅํด์.
์ด๋ค ํด๋ผ์ฐ๋๋ฅผ ์ ํํ ์ง ๊ณ ๋ฏผ๋๋ ๋ถ๋ค์ ์ํด ์ ๋ฆฌํด๋ดค์ด์!
ํ๋ก์ ํธ ๊ท๋ชจ์ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ์ต์ ์ ์ ํ์ด ๋ฌ๋ผ์ ธ์ ๐
์ฐธ๊ณ ๋ก ์ด๋ฐ IoT ์ํคํ
์ฒ ์ค๊ณ๋ ๊ตฌํ์ด ํ์ํ๋ค๋ฉด ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์์!
| ํ๋ซํผ | ์ฅ์ | ๋จ์ | ์ถ์ฒ ๊ท๋ชจ | ๋ฌด๋ฃ ํฐ์ด |
|---|---|---|---|---|
| AWS IoT Core | ๊ฐ์ฅ ํ๋ถํ ์ํ๊ณ, ๋์ ์์ ์ฑ, ๋ค์ํ ์ฐ๋ ์๋น์ค | ๋ณต์กํ ์ค์ , ๋น์ฉ ์์ธก ์ด๋ ค์ | ์ค~๋๊ท๋ชจ | ์ 250๋ง ๋ฉ์์ง |
| Firebase | ๋น ๋ฅธ ์ค์ , ์ค์๊ฐ ๋๊ธฐํ, ๋ชจ๋ฐ์ผ ์ฑ ์ฐ๋ ์ฌ์ | ๋๊ท๋ชจ ์ฟผ๋ฆฌ ์ ํ, ๋น์ฉ ๊ธ์ฆ ๊ฐ๋ฅ | ์~์ค๊ท๋ชจ | 1GB ์ ์ฅ, 10GB/์ |
| Azure IoT Hub | ์ํฐํ๋ผ์ด์ฆ ์นํ์ , MS ์ํ๊ณ ํตํฉ | ๋ณต์กํ ์ค์ , ๋์ ํ์ต ๊ณก์ | ์ค~๋๊ท๋ชจ | 8,000 ๋ฉ์์ง/์ผ |
| Google Cloud IoT | BigQuery ์ฐ๋, ML ํ์ดํ๋ผ์ธ ๊ฐ์ | 2023๋ ์๋น์ค ์ข ๋ฃ (์ฃผ์!) | - | - |
| HiveMQ Cloud | ์์ MQTT ๋ธ๋ก์ปค, ๊ฐ๋จํ ์ค์ | ์ ์ฅ/๋ถ์ ๊ธฐ๋ฅ ์์ | ์~์ค๊ท๋ชจ | 100 ์ฐ๊ฒฐ, 10GB/์ |
| ThingsBoard | ์คํ์์ค, ์์ฒด ํธ์คํ ๊ฐ๋ฅ | ์ด์ ๋ถ๋ด, ์ปค๋ฎค๋ํฐ ์ง์ | ์~์ค๊ท๋ชจ | ์คํ์์ค ๋ฌด๋ฃ |
๊ธฐ์กด์ ์ฌ์ฉํ๋ ๋ถ๋ค์ AWS IoT Core๋ HiveMQ๋ก ๋ง์ด๊ทธ๋ ์ด์ ์ด ํ์ํด์!
โ ๋น ๋ฅธ ํ๋กํ ํ์ดํ, ์ค์๊ฐ ๋์๋ณด๋ ๊ตฌํ ์ฌ์
โ ๋์ ์์ ์ฑ, ๋๊ท๋ชจ ๋๋ฐ์ด์ค ๊ด๋ฆฌ, ๋ถ์ ํ์ดํ๋ผ์ธ
โ ๊ธฐ์กด MS ์ธํ๋ผ์ ํตํฉ, ์ํฐํ๋ผ์ด์ฆ ๋ณด์ ์ ์ฑ ์ค์
โ ์ด๊ธฐ ๋น์ฉ ์์, ์์ ํ ๋ฐ์ดํฐ ํต์ ๊ถ, ์ด์ ๋ถ๋ด ์์
IoT ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ์ง์ง ๋ณ๋ณ ๋ฌธ์ ๊ฐ ๋ค ์๊ฒจ์ใ
ใ
์์ฃผ ๋ง๋๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด์!
| ๋ฌธ์ ์ฆ์ | ์์ธ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|---|
| MQTT ์ฐ๊ฒฐ ์คํจ (RC=5) | ์ธ์ฆ ์ ๋ณด ์ค๋ฅ | username/password ๋๋ ์ธ์ฆ์ ํ์ธ |
| TLS ํธ๋์ ฐ์ดํฌ ์คํจ | ์ธ์ฆ์ ๋ง๋ฃ ๋๋ CA ๋ถ์ผ์น | ์ธ์ฆ์ ๊ฐฑ์ , CA ์ธ์ฆ์ ์ ๋ฐ์ดํธ |
| ๋ฉ์์ง ์ ์ค (QoS 0) | ๋คํธ์ํฌ ๋ถ์์ | QoS 1 ๋๋ 2๋ก ๋ณ๊ฒฝ |
| ๋ฐ์ดํฐ ์ค๋ณต ์์ | QoS 1 ์ฌ์ ์ก | ๋ฉ์์ง ID๋ก ์ค๋ณต ์ ๊ฑฐ ๋ก์ง ์ถ๊ฐ |
| ESP32 ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ | JSON ํ์ด๋ก๋ ๊ณผ๋ค | MessagePack ์ฌ์ฉ, ๋ถํ์ ํ๋ ์ ๊ฑฐ |
| ๋ฐฐํฐ๋ฆฌ ๋นจ๋ฆฌ ์๋ชจ | WiFi ํญ์ ์ผ์ง | ๋ฅ์ฌ๋ฆฝ ๋ชจ๋ ํ์ฉ, ์ ์ก ์ฃผ๊ธฐ ๋๋ฆฌ๊ธฐ |
| ํด๋ผ์ฐ๋ ๋น์ฉ ๊ธ์ฆ | ๋ฐ์ดํฐ ์ ์ก๋ ๊ณผ๋ค | ๋ฐฐ์น ์ ์ก, ๋ธํ ์ธ์ฝ๋ฉ, ์์ถ ์ ์ฉ |
| ํ์์คํฌํ ๋ถ์ผ์น | ๋๋ฐ์ด์ค ์๊ฐ ๋๊ธฐํ ์ ๋จ | NTP ์๋ฒ ๋๊ธฐํ ํ์ |
"""
MQTT ์ฐ๊ฒฐ ๋ฌธ์ ๋๋ฒ๊น
๋๊ตฌ
"""
import paho.mqtt.client as mqtt
import logging
# ์์ธ ๋ก๊น
ํ์ฑํ
logging.basicConfig(level=logging.DEBUG)
mqtt_logger = logging.getLogger('mqtt')
mqtt_logger.setLevel(logging.DEBUG)
def debug_mqtt_connection(broker, port, username=None, password=None):
"""MQTT ์ฐ๊ฒฐ ๋ฌธ์ ์ง๋จ"""
client = mqtt.Client()
if username:
client.username_pw_set(username, password)
# ๋ชจ๋ ์ด๋ฒคํธ ๋ก๊น
def on_log(client, userdata, level, buf):
print(f"[MQTT LOG] {buf}")
def on_connect(client, userdata, flags, rc):
rc_messages = {
0: "โ
์ฐ๊ฒฐ ์ฑ๊ณต",
1: "โ ์๋ชป๋ ํ๋กํ ์ฝ ๋ฒ์ ",
2: "โ ํด๋ผ์ด์ธํธ ID ๊ฑฐ๋ถ๋จ",
3: "โ ์๋ฒ ์ฌ์ฉ ๋ถ๊ฐ",
4: "โ ์๋ชป๋ ์ฌ์ฉ์๋ช
๋๋ ๋น๋ฐ๋ฒํธ",
5: "โ ๊ถํ ์์ (์ธ์ฆ ์คํจ)"
}
print(f"์ฐ๊ฒฐ ๊ฒฐ๊ณผ: {rc_messages.get(rc, f'์ ์ ์๋ ์ค๋ฅ: {rc}')}")
client.on_log = on_log
client.on_connect = on_connect
print(f"๐ ์ฐ๊ฒฐ ์๋: {broker}:{port}")
try:
client.connect(broker, port, keepalive=10)
client.loop_start()
import time
time.sleep(5) # 5์ด ๋๊ธฐ
client.loop_stop()
client.disconnect()
except ConnectionRefusedError:
print(f"โ ์ฐ๊ฒฐ ๊ฑฐ๋ถ๋จ. ๋ธ๋ก์ปค ์ฃผ์/ํฌํธ ํ์ธ: {broker}:{port}")
except TimeoutError:
print(f"โ ์ฐ๊ฒฐ ํ์์์. ๋ฐฉํ๋ฒฝ ๋๋ ๋คํธ์ํฌ ํ์ธ")
except Exception as e:
print(f"โ ์์์น ๋ชปํ ์ค๋ฅ: {e}")
# ์ฌ์ฉ
debug_mqtt_connection("broker.example.com", 1883, "user", "pass")
์ ์ง์ง ๊ธธ์์ฃ ?ใ
ใ
๊ทผ๋ฐ ์ด๊ฒ ๋ค ์ค์ ๋ก ํ์ํ ๋ด์ฉ๋ค์ด์์!
๋ง์ง๋ง์ผ๋ก ํต์ฌ ํฌ์ธํธ๋ง ์ ๋ฆฌํ ๊ฒ์ ๐
- โ ํ๋กํ ์ฝ ์ ํ ๋ฐฐํฐ๋ฆฌ ๊ธฐ๊ธฐ โ MQTT, ๋จ์ ์ฐ๋ โ HTTP REST
- โ ๋ณด์ ํ์ TLS ์ํธํ, X.509 ์ธ์ฆ์, ํ๊ฒฝ๋ณ์๋ก ํค ๊ด๋ฆฌ
- โ ์ฌ์ฐ๊ฒฐ ๋ก์ง ์ง์ ๋ฐฑ์คํ ๋ฐฉ์์ผ๋ก ์์ ์ ์ธ ์ฌ์ฐ๊ฒฐ ๊ตฌํ
- โ ์คํ๋ผ์ธ ๋์ ๋ก์ปฌ SQLite ์ ์ฅ โ ์จ๋ผ์ธ ๋ณต๊ตฌ ์ ์ฌ์ ์ก
- โ ๋ฐ์ดํฐ ์ต์ ํ MessagePack/gzip ์์ถ, ๋ธํ ์ธ์ฝ๋ฉ์ผ๋ก ์ ์ก๋ ์ ๊ฐ
- โ QoS ์ค์ ๋ฐ์ดํฐ ์ค์๋์ ๋ฐ๋ผ QoS 0/1/2 ์ ์ ํ ์ ํ
- โ ๋ชจ๋ํฐ๋ง Prometheus + Grafana๋ก ์ค์๊ฐ ์์คํ ์ํ ํ์
- โ ๋ฐฐ์น ์ฒ๋ฆฌ ๊ฐ๋ณ ์ ์ก๋ณด๋ค ๋ฐฐ์น ์ ์ก์ผ๋ก ํจ์จ์ฑ ํฅ์
- โ ํ์์คํฌํ NTP ๋๊ธฐํ ํ์, UTC ๊ธฐ์ค ISO 8601 ํ์ ์ฌ์ฉ
- โ ์๋ฌ ํธ๋ค๋ง ๋ชจ๋ ์์ธ ์ํฉ์ ๋ํ ์ฒ๋ฆฌ ๋ก์ง ๊ตฌํ
IoT ๊ฐ๋ฐ์ ์ฒ์์ ๋ณต์กํด ๋ณด์ด์ง๋ง, ๊ธฐ๋ณธ ์๋ฆฌ๋ฅผ ์ดํดํ๊ณ ๋๋ฉด
์๊ฐ๋ณด๋ค ์ฌ๋ฏธ์๋ ๋ถ์ผ์์! ์ผ์์์ ๋ฐ์ดํฐ๊ฐ ํด๋ผ์ฐ๋๋ก ์ฌ๋ผ๊ฐ๊ณ ,
๊ทธ๊ฒ ๋์๋ณด๋์ ์ค์๊ฐ์ผ๋ก ํ์๋๋ ๊ฑธ ๋ณด๋ฉด ์ง์ง ๋ฟ๋ฏํ๊ฑฐ๋ ์ ๐
์ด ๊ธ์์ ๋ค๋ฃฌ ์์ค ์ฝ๋๋ค์ ์ค์ ํ๋ก์ ํธ์ ๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅํ ์์ค์ผ๋ก ์์ฑํ์ด์.
๋ฌผ๋ก ์ค์ ํ๊ฒฝ์ ๋ง๊ฒ ์ค์ ๊ฐ๊ณผ ์ผ์ ์ฝ๊ธฐ ๋ถ๋ถ์ ์์ ์ด ํ์ํ๊ฒ ์ง๋ง,
์ ์ฒด์ ์ธ ๊ตฌ์กฐ์ ํจํด์ ๊ทธ๋๋ก ํ์ฉํ ์ ์์ด์!
๊ถ๊ธํ ์ ์ด๋ ๋ ์๊ณ ์ถ์ ๋ด์ฉ์ด ์์ผ๋ฉด ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์ ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.