cloud_iot.py

Download
python 833 lines 25.5 KB
  1#!/usr/bin/env python3
  2"""
  3ํด๋ผ์šฐ๋“œ IoT ํ†ตํ•ฉ - ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ชจ๋“œ
  4AWS IoT Core ๋ฐ GCP Pub/Sub ์—ฐ๋™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  5
  6์‹ค์ œ ํด๋ผ์šฐ๋“œ ๊ณ„์ • ๋ฐ ์ž๊ฒฉ ์ฆ๋ช… ์—†์ด ๋™์ž‘
  7"""
  8
  9import json
 10import time
 11import threading
 12import uuid
 13from datetime import datetime
 14from typing import Dict, List, Optional, Callable
 15from dataclasses import dataclass, asdict
 16from enum import Enum
 17import queue
 18import random
 19
 20
 21# ==============================================================================
 22# ๋ฐ์ดํ„ฐ ๋ชจ๋ธ
 23# ==============================================================================
 24
 25class CloudProvider(Enum):
 26    """ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž"""
 27    AWS_IOT = "aws_iot"
 28    GCP_PUBSUB = "gcp_pubsub"
 29    SIMULATION = "simulation"
 30
 31
 32class MessageQoS(Enum):
 33    """MQTT QoS ๋ ˆ๋ฒจ"""
 34    AT_MOST_ONCE = 0
 35    AT_LEAST_ONCE = 1
 36    EXACTLY_ONCE = 2
 37
 38
 39@dataclass
 40class IoTMessage:
 41    """IoT ๋ฉ”์‹œ์ง€"""
 42    topic: str
 43    payload: Dict
 44    timestamp: datetime
 45    message_id: str
 46    qos: MessageQoS = MessageQoS.AT_LEAST_ONCE
 47
 48    def to_json(self) -> str:
 49        """JSON ์ง๋ ฌํ™”"""
 50        data = {
 51            "topic": self.topic,
 52            "payload": self.payload,
 53            "timestamp": self.timestamp.isoformat(),
 54            "message_id": self.message_id,
 55            "qos": self.qos.value
 56        }
 57        return json.dumps(data)
 58
 59
 60@dataclass
 61class DeviceInfo:
 62    """๋””๋ฐ”์ด์Šค ์ •๋ณด"""
 63    device_id: str
 64    device_type: str
 65    location: str
 66    firmware_version: str
 67    registered_at: datetime
 68
 69
 70@dataclass
 71class TelemetryData:
 72    """ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐ์ดํ„ฐ"""
 73    device_id: str
 74    temperature: float
 75    humidity: float
 76    pressure: float
 77    battery_level: float
 78    timestamp: datetime
 79
 80    def to_dict(self) -> Dict:
 81        return {
 82            "device_id": self.device_id,
 83            "temperature": self.temperature,
 84            "humidity": self.humidity,
 85            "pressure": self.pressure,
 86            "battery_level": self.battery_level,
 87            "timestamp": self.timestamp.isoformat()
 88        }
 89
 90
 91# ==============================================================================
 92# AWS IoT Core ์‹œ๋ฎฌ๋ ˆ์ด์…˜
 93# ==============================================================================
 94
 95class SimulatedAWSIoTClient:
 96    """AWS IoT Core ํด๋ผ์ด์–ธํŠธ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
 97
 98    def __init__(self, endpoint: str, cert_path: str, key_path: str,
 99                 ca_path: str, client_id: str):
100        self.endpoint = endpoint
101        self.cert_path = cert_path
102        self.key_path = key_path
103        self.ca_path = ca_path
104        self.client_id = client_id
105        self.connected = False
106        self.subscriptions: Dict[str, Callable] = {}
107        self.message_queue = queue.Queue()
108
109        print(f"[AWS IoT ์‹œ๋ฎฌ๋ ˆ์ด์…˜] ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ")
110        print(f"  - ์—”๋“œํฌ์ธํŠธ: {endpoint}")
111        print(f"  - ํด๋ผ์ด์–ธํŠธ ID: {client_id}")
112        print(f"  - ์ธ์ฆ์„œ: {cert_path}")
113
114    def connect(self):
115        """์—ฐ๊ฒฐ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
116        print(f"\n[AWS IoT ์‹œ๋ฎฌ๋ ˆ์ด์…˜] ์—ฐ๊ฒฐ ์ค‘...")
117        time.sleep(0.5)  # ์—ฐ๊ฒฐ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
118
119        # ์ธ์ฆ์„œ ๊ฒ€์ฆ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
120        print("  - TLS ํ•ธ๋“œ์…ฐ์ดํฌ...")
121        time.sleep(0.2)
122        print("  - ์ธ์ฆ์„œ ๊ฒ€์ฆ...")
123        time.sleep(0.2)
124        print("  - MQTT ์—ฐ๊ฒฐ...")
125        time.sleep(0.2)
126
127        self.connected = True
128        print("โœ“ ์—ฐ๊ฒฐ ์„ฑ๊ณต!\n")
129
130    def disconnect(self):
131        """์—ฐ๊ฒฐ ํ•ด์ œ"""
132        if self.connected:
133            print("[AWS IoT ์‹œ๋ฎฌ๋ ˆ์ด์…˜] ์—ฐ๊ฒฐ ํ•ด์ œ")
134            self.connected = False
135
136    def publish(self, topic: str, payload: Dict, qos: MessageQoS = MessageQoS.AT_LEAST_ONCE):
137        """๋ฉ”์‹œ์ง€ ๋ฐœํ–‰"""
138        if not self.connected:
139            raise RuntimeError("์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Œ")
140
141        message = IoTMessage(
142            topic=topic,
143            payload=payload,
144            timestamp=datetime.now(),
145            message_id=str(uuid.uuid4()),
146            qos=qos
147        )
148
149        # ๋ฐœํ–‰ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
150        print(f"[AWS IoT ๋ฐœํ–‰] {topic}")
151        print(f"  ๋ฉ”์‹œ์ง€ ID: {message.message_id[:8]}...")
152        print(f"  QoS: {qos.name}")
153        print(f"  ํŽ˜์ด๋กœ๋“œ: {json.dumps(payload, indent=2)}")
154
155        # ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
156        time.sleep(random.uniform(0.01, 0.05))
157
158        return message.message_id
159
160    def subscribe(self, topic: str, callback: Callable):
161        """ํ† ํ”ฝ ๊ตฌ๋…"""
162        if not self.connected:
163            raise RuntimeError("์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Œ")
164
165        self.subscriptions[topic] = callback
166        print(f"[AWS IoT ๊ตฌ๋…] {topic}")
167
168    def _simulate_incoming_message(self, topic: str, payload: Dict):
169        """์ˆ˜์‹  ๋ฉ”์‹œ์ง€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (๋‚ด๋ถ€ ํ…Œ์ŠคํŠธ์šฉ)"""
170        if topic in self.subscriptions:
171            callback = self.subscriptions[topic]
172            callback(topic, payload)
173
174
175class AWSIoTDeviceManager:
176    """AWS IoT ๋””๋ฐ”์ด์Šค ๊ด€๋ฆฌ์ž"""
177
178    def __init__(self, client: SimulatedAWSIoTClient):
179        self.client = client
180        self.device_info: Optional[DeviceInfo] = None
181
182    def register_device(self, device_info: DeviceInfo):
183        """๋””๋ฐ”์ด์Šค ๋“ฑ๋ก"""
184        self.device_info = device_info
185
186        print("\n[AWS IoT] ๋””๋ฐ”์ด์Šค ๋“ฑ๋ก")
187        print(f"  - ID: {device_info.device_id}")
188        print(f"  - ํƒ€์ž…: {device_info.device_type}")
189        print(f"  - ์œ„์น˜: {device_info.location}")
190        print(f"  - ํŽŒ์›จ์–ด: {device_info.firmware_version}")
191
192        # Thing ์ƒ์„ฑ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
193        print("  - Thing ์ƒ์„ฑ ์ค‘...")
194        time.sleep(0.3)
195
196        # ์ธ์ฆ์„œ ์—ฐ๊ฒฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
197        print("  - ์ธ์ฆ์„œ ์—ฐ๊ฒฐ ์ค‘...")
198        time.sleep(0.3)
199
200        # ์ •์ฑ… ์—ฐ๊ฒฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
201        print("  - IoT ์ •์ฑ… ์—ฐ๊ฒฐ ์ค‘...")
202        time.sleep(0.3)
203
204        print("โœ“ ๋””๋ฐ”์ด์Šค ๋“ฑ๋ก ์™„๋ฃŒ\n")
205
206    def publish_telemetry(self, data: TelemetryData):
207        """ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰"""
208        topic = f"device/{data.device_id}/telemetry"
209        payload = data.to_dict()
210
211        self.client.publish(topic, payload)
212
213    def update_device_shadow(self, state: Dict):
214        """Device Shadow ์—…๋ฐ์ดํŠธ"""
215        if not self.device_info:
216            raise ValueError("๋””๋ฐ”์ด์Šค ์ •๋ณด ์—†์Œ")
217
218        topic = f"$aws/things/{self.device_info.device_id}/shadow/update"
219
220        shadow_payload = {
221            "state": {
222                "reported": state
223            },
224            "metadata": {
225                "reported": {
226                    k: {"timestamp": int(time.time())}
227                    for k in state.keys()
228                }
229            }
230        }
231
232        print(f"\n[AWS IoT] Device Shadow ์—…๋ฐ์ดํŠธ")
233        print(f"  ์ƒํƒœ: {json.dumps(state, indent=2)}")
234
235        self.client.publish(topic, shadow_payload)
236
237
238# ==============================================================================
239# GCP Pub/Sub ์‹œ๋ฎฌ๋ ˆ์ด์…˜
240# ==============================================================================
241
242class SimulatedGCPPubSubPublisher:
243    """GCP Pub/Sub Publisher (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
244
245    def __init__(self, project_id: str, topic_id: str):
246        self.project_id = project_id
247        self.topic_id = topic_id
248        self.topic_path = f"projects/{project_id}/topics/{topic_id}"
249
250        print(f"[GCP Pub/Sub ์‹œ๋ฎฌ๋ ˆ์ด์…˜] Publisher ์ƒ์„ฑ")
251        print(f"  - ํ”„๋กœ์ ํŠธ: {project_id}")
252        print(f"  - ํ† ํ”ฝ: {topic_id}")
253        print(f"  - ๊ฒฝ๋กœ: {self.topic_path}")
254
255    def publish(self, data: Dict, **attributes) -> str:
256        """๋ฉ”์‹œ์ง€ ๋ฐœํ–‰"""
257        message_id = str(uuid.uuid4())
258
259        print(f"\n[GCP Pub/Sub ๋ฐœํ–‰]")
260        print(f"  ํ† ํ”ฝ: {self.topic_id}")
261        print(f"  ๋ฉ”์‹œ์ง€ ID: {message_id[:8]}...")
262
263        if attributes:
264            print(f"  ์†์„ฑ: {attributes}")
265
266        print(f"  ๋ฐ์ดํ„ฐ: {json.dumps(data, indent=2)}")
267
268        # ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
269        time.sleep(random.uniform(0.01, 0.05))
270
271        print(f"โœ“ ๋ฐœํ–‰ ์™„๋ฃŒ\n")
272        return message_id
273
274    def publish_batch(self, messages: List[Dict]) -> List[str]:
275        """๋ฐฐ์น˜ ๋ฐœํ–‰"""
276        print(f"\n[GCP Pub/Sub ๋ฐฐ์น˜ ๋ฐœํ–‰] {len(messages)}๊ฐœ ๋ฉ”์‹œ์ง€")
277
278        message_ids = []
279        for i, data in enumerate(messages):
280            message_id = str(uuid.uuid4())
281            message_ids.append(message_id)
282            print(f"  [{i+1}] {message_id[:8]}...")
283
284        time.sleep(random.uniform(0.05, 0.1))
285        print(f"โœ“ ๋ฐฐ์น˜ ๋ฐœํ–‰ ์™„๋ฃŒ\n")
286
287        return message_ids
288
289
290class SimulatedGCPPubSubSubscriber:
291    """GCP Pub/Sub Subscriber (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
292
293    def __init__(self, project_id: str, subscription_id: str):
294        self.project_id = project_id
295        self.subscription_id = subscription_id
296        self.subscription_path = f"projects/{project_id}/subscriptions/{subscription_id}"
297        self.message_queue = queue.Queue()
298
299        print(f"[GCP Pub/Sub ์‹œ๋ฎฌ๋ ˆ์ด์…˜] Subscriber ์ƒ์„ฑ")
300        print(f"  - ๊ตฌ๋…: {subscription_id}")
301        print(f"  - ๊ฒฝ๋กœ: {self.subscription_path}")
302
303    def pull(self, max_messages: int = 10) -> List[Dict]:
304        """๋ฉ”์‹œ์ง€ ํ’€ (๋™๊ธฐ)"""
305        print(f"\n[GCP Pub/Sub Pull] ์ตœ๋Œ€ {max_messages}๊ฐœ ๋ฉ”์‹œ์ง€")
306
307        messages = []
308
309        # ์‹œ๋ฎฌ๋ ˆ์ด์…˜: ํ์—์„œ ๋ฉ”์‹œ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
310        for _ in range(min(max_messages, self.message_queue.qsize())):
311            try:
312                msg = self.message_queue.get_nowait()
313                messages.append(msg)
314            except queue.Empty:
315                break
316
317        print(f"  ์ˆ˜์‹ : {len(messages)}๊ฐœ ๋ฉ”์‹œ์ง€")
318
319        # ACK ์‹œ๋ฎฌ๋ ˆ์ด์…˜
320        if messages:
321            print(f"  ACK ์ „์†ก ์ค‘...")
322            time.sleep(0.1)
323
324        return messages
325
326    def subscribe(self, callback: Callable):
327        """์ŠคํŠธ๋ฆฌ๋ฐ ๊ตฌ๋… (๋น„๋™๊ธฐ)"""
328        print(f"\n[GCP Pub/Sub] ์ŠคํŠธ๋ฆฌ๋ฐ ๊ตฌ๋… ์‹œ์ž‘")
329
330        def streaming_loop():
331            while True:
332                try:
333                    msg = self.message_queue.get(timeout=1)
334                    callback(msg, {})
335                except queue.Empty:
336                    continue
337                except Exception as e:
338                    print(f"๊ตฌ๋… ์˜ค๋ฅ˜: {e}")
339                    break
340
341        thread = threading.Thread(target=streaming_loop, daemon=True)
342        thread.start()
343
344        return thread
345
346
347# ==============================================================================
348# MQTT ๋ฉ”์‹œ์ง€ ํฌ๋งท
349# ==============================================================================
350
351class MQTTMessageFormat:
352    """MQTT ๋ฉ”์‹œ์ง€ ํฌ๋งท ํ‘œ์ค€"""
353
354    @staticmethod
355    def create_telemetry(device_id: str, sensor_data: Dict) -> Dict:
356        """ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ"""
357        return {
358            "device_id": device_id,
359            "message_type": "telemetry",
360            "data": sensor_data,
361            "timestamp": datetime.now().isoformat(),
362            "version": "1.0"
363        }
364
365    @staticmethod
366    def create_event(device_id: str, event_type: str, event_data: Dict) -> Dict:
367        """์ด๋ฒคํŠธ ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ"""
368        return {
369            "device_id": device_id,
370            "message_type": "event",
371            "event_type": event_type,
372            "data": event_data,
373            "timestamp": datetime.now().isoformat(),
374            "version": "1.0"
375        }
376
377    @staticmethod
378    def create_command(device_id: str, command: str, parameters: Dict) -> Dict:
379        """๋ช…๋ น ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ"""
380        return {
381            "device_id": device_id,
382            "message_type": "command",
383            "command": command,
384            "parameters": parameters,
385            "timestamp": datetime.now().isoformat(),
386            "command_id": str(uuid.uuid4()),
387            "version": "1.0"
388        }
389
390    @staticmethod
391    def create_response(device_id: str, command_id: str, status: str, result: Dict) -> Dict:
392        """๋ช…๋ น ์‘๋‹ต ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ"""
393        return {
394            "device_id": device_id,
395            "message_type": "response",
396            "command_id": command_id,
397            "status": status,  # "success", "error", "timeout"
398            "result": result,
399            "timestamp": datetime.now().isoformat(),
400            "version": "1.0"
401        }
402
403
404# ==============================================================================
405# ๋””๋ฐ”์ด์Šค ํ”„๋กœ๋น„์ €๋‹
406# ==============================================================================
407
408class DeviceProvisioning:
409    """๋””๋ฐ”์ด์Šค ํ”„๋กœ๋น„์ €๋‹ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"""
410
411    def __init__(self, provider: CloudProvider):
412        self.provider = provider
413        self.provisioned_devices: Dict[str, DeviceInfo] = {}
414
415        print(f"\n[ํ”„๋กœ๋น„์ €๋‹] ์ดˆ๊ธฐํ™” ({provider.value})")
416
417    def provision_device(self, device_id: str, device_type: str, location: str) -> DeviceInfo:
418        """๋””๋ฐ”์ด์Šค ํ”„๋กœ๋น„์ €๋‹"""
419        print(f"\n[ํ”„๋กœ๋น„์ €๋‹] ๋””๋ฐ”์ด์Šค ๋“ฑ๋ก ์‹œ์ž‘")
420        print(f"  - ID: {device_id}")
421        print(f"  - ํƒ€์ž…: {device_type}")
422        print(f"  - ์œ„์น˜: {location}")
423
424        # ๋‹จ๊ณ„ 1: ๋””๋ฐ”์ด์Šค ์ƒ์„ฑ
425        print("\n  [1/4] ๋””๋ฐ”์ด์Šค ์ƒ์„ฑ ์ค‘...")
426        time.sleep(0.3)
427
428        # ๋‹จ๊ณ„ 2: ์ธ์ฆ์„œ ์ƒ์„ฑ
429        print("  [2/4] ์ธ์ฆ์„œ ์ƒ์„ฑ ์ค‘...")
430        cert_arn = f"arn:aws:iot:region:account:cert/{uuid.uuid4()}"
431        print(f"    ์ธ์ฆ์„œ ARN: {cert_arn}")
432        time.sleep(0.3)
433
434        # ๋‹จ๊ณ„ 3: ์ •์ฑ… ์—ฐ๊ฒฐ
435        print("  [3/4] ์ •์ฑ… ์—ฐ๊ฒฐ ์ค‘...")
436        print("    ์ •์ฑ…: IoTDevicePolicy")
437        time.sleep(0.3)
438
439        # ๋‹จ๊ณ„ 4: Thing ์ƒ์„ฑ ๋ฐ ์—ฐ๊ฒฐ
440        print("  [4/4] Thing ์ƒ์„ฑ ๋ฐ ์—ฐ๊ฒฐ ์ค‘...")
441        time.sleep(0.3)
442
443        device_info = DeviceInfo(
444            device_id=device_id,
445            device_type=device_type,
446            location=location,
447            firmware_version="1.0.0",
448            registered_at=datetime.now()
449        )
450
451        self.provisioned_devices[device_id] = device_info
452
453        print("\nโœ“ ํ”„๋กœ๋น„์ €๋‹ ์™„๋ฃŒ!")
454        print(f"  ์ธ์ฆ์„œ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: certs/{device_id}.cert.pem")
455        print(f"  ๊ฐœ์ธํ‚ค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: certs/{device_id}.private.key\n")
456
457        return device_info
458
459    def deprovision_device(self, device_id: str):
460        """๋””๋ฐ”์ด์Šค ํ•ด์ œ"""
461        if device_id not in self.provisioned_devices:
462            raise ValueError(f"๋””๋ฐ”์ด์Šค ์—†์Œ: {device_id}")
463
464        print(f"\n[ํ”„๋กœ๋น„์ €๋‹] ๋””๋ฐ”์ด์Šค ํ•ด์ œ: {device_id}")
465
466        # ์ธ์ฆ์„œ ๋น„ํ™œ์„ฑํ™”
467        print("  - ์ธ์ฆ์„œ ๋น„ํ™œ์„ฑํ™” ์ค‘...")
468        time.sleep(0.2)
469
470        # Thing ์‚ญ์ œ
471        print("  - Thing ์‚ญ์ œ ์ค‘...")
472        time.sleep(0.2)
473
474        del self.provisioned_devices[device_id]
475        print("โœ“ ํ•ด์ œ ์™„๋ฃŒ\n")
476
477
478# ==============================================================================
479# ํ†ตํ•ฉ IoT ํด๋ผ์ด์–ธํŠธ
480# ==============================================================================
481
482class CloudIoTClient:
483    """ํ†ตํ•ฉ ํด๋ผ์šฐ๋“œ IoT ํด๋ผ์ด์–ธํŠธ"""
484
485    def __init__(self, provider: CloudProvider, config: Dict):
486        self.provider = provider
487        self.config = config
488
489        print("\n" + "="*60)
490        print(f"ํด๋ผ์šฐ๋“œ IoT ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ({provider.value})")
491        print("="*60)
492
493        if provider == CloudProvider.AWS_IOT:
494            self.client = SimulatedAWSIoTClient(
495                endpoint=config.get('endpoint', 'simulated-endpoint.iot.region.amazonaws.com'),
496                cert_path=config.get('cert_path', 'certs/device.cert.pem'),
497                key_path=config.get('key_path', 'certs/device.private.key'),
498                ca_path=config.get('ca_path', 'certs/root-CA.crt'),
499                client_id=config.get('client_id', 'device-001')
500            )
501
502        elif provider == CloudProvider.GCP_PUBSUB:
503            self.publisher = SimulatedGCPPubSubPublisher(
504                project_id=config.get('project_id', 'my-iot-project'),
505                topic_id=config.get('topic_id', 'iot-telemetry')
506            )
507            self.subscriber = SimulatedGCPPubSubSubscriber(
508                project_id=config.get('project_id', 'my-iot-project'),
509                subscription_id=config.get('subscription_id', 'iot-telemetry-sub')
510            )
511
512        self.message_stats = {
513            "published": 0,
514            "received": 0,
515            "errors": 0
516        }
517
518    def connect(self):
519        """์—ฐ๊ฒฐ"""
520        if self.provider == CloudProvider.AWS_IOT:
521            self.client.connect()
522
523    def disconnect(self):
524        """์—ฐ๊ฒฐ ํ•ด์ œ"""
525        if self.provider == CloudProvider.AWS_IOT:
526            self.client.disconnect()
527
528    def publish_telemetry(self, device_id: str, sensor_data: Dict):
529        """ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰"""
530        message = MQTTMessageFormat.create_telemetry(device_id, sensor_data)
531
532        if self.provider == CloudProvider.AWS_IOT:
533            topic = f"device/{device_id}/telemetry"
534            self.client.publish(topic, message)
535        elif self.provider == CloudProvider.GCP_PUBSUB:
536            self.publisher.publish(message, device_id=device_id, message_type="telemetry")
537
538        self.message_stats["published"] += 1
539
540    def subscribe_commands(self, device_id: str, callback: Callable):
541        """๋ช…๋ น ๊ตฌ๋…"""
542        def command_handler(topic: str, payload: Dict):
543            print(f"\n[๋ช…๋ น ์ˆ˜์‹ ] {topic}")
544            print(f"  ๋ช…๋ น: {payload.get('command')}")
545            print(f"  ํŒŒ๋ผ๋ฏธํ„ฐ: {payload.get('parameters')}")
546
547            # ์ฝœ๋ฐฑ ์‹คํ–‰
548            callback(payload)
549
550            # ์‘๋‹ต ์ „์†ก
551            response = MQTTMessageFormat.create_response(
552                device_id=device_id,
553                command_id=payload.get('command_id'),
554                status="success",
555                result={"executed": True}
556            )
557
558            response_topic = f"device/{device_id}/response"
559            self.client.publish(response_topic, response)
560
561        if self.provider == CloudProvider.AWS_IOT:
562            topic = f"device/{device_id}/command"
563            self.client.subscribe(topic, command_handler)
564
565    def get_statistics(self) -> Dict:
566        """ํ†ต๊ณ„ ์กฐํšŒ"""
567        return self.message_stats.copy()
568
569
570# ==============================================================================
571# ์„ผ์„œ ๋ฐ์ดํ„ฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ
572# ==============================================================================
573
574class SensorSimulator:
575    """์„ผ์„œ ๋ฐ์ดํ„ฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ"""
576
577    def __init__(self, device_id: str):
578        self.device_id = device_id
579        self.base_temp = 25.0
580        self.base_humidity = 60.0
581        self.base_pressure = 1013.25
582        self.battery_level = 100.0
583
584    def generate_reading(self) -> TelemetryData:
585        """์„ผ์„œ ์ฝ๊ธฐ ์ƒ์„ฑ"""
586        # ๋žœ๋ค ๋ณ€๋™ ์ถ”๊ฐ€
587        temp = self.base_temp + random.uniform(-2, 2)
588        humidity = self.base_humidity + random.uniform(-5, 5)
589        pressure = self.base_pressure + random.uniform(-2, 2)
590
591        # ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ
592        self.battery_level = max(0, self.battery_level - random.uniform(0.01, 0.05))
593
594        return TelemetryData(
595            device_id=self.device_id,
596            temperature=round(temp, 2),
597            humidity=round(humidity, 2),
598            pressure=round(pressure, 2),
599            battery_level=round(self.battery_level, 2),
600            timestamp=datetime.now()
601        )
602
603
604# ==============================================================================
605# ๋ฐ๋ชจ ์‹œ๋‚˜๋ฆฌ์˜ค
606# ==============================================================================
607
608def demo_aws_iot():
609    """AWS IoT Core ๋ฐ๋ชจ"""
610    print("\n" + "="*60)
611    print("AWS IoT Core ๋ฐ๋ชจ")
612    print("="*60)
613
614    # ํ”„๋กœ๋น„์ €๋‹
615    provisioning = DeviceProvisioning(CloudProvider.AWS_IOT)
616    device_info = provisioning.provision_device(
617        device_id="raspberry-pi-001",
618        device_type="sensor-hub",
619        location="Seoul, Korea"
620    )
621
622    # ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
623    config = {
624        'endpoint': 'a1b2c3d4e5f6g7.iot.ap-northeast-2.amazonaws.com',
625        'cert_path': f'certs/{device_info.device_id}.cert.pem',
626        'key_path': f'certs/{device_info.device_id}.private.key',
627        'ca_path': 'certs/AmazonRootCA1.pem',
628        'client_id': device_info.device_id
629    }
630
631    client = CloudIoTClient(CloudProvider.AWS_IOT, config)
632    client.connect()
633
634    # ๋””๋ฐ”์ด์Šค ๊ด€๋ฆฌ์ž
635    device_manager = AWSIoTDeviceManager(client.client)
636    device_manager.register_device(device_info)
637
638    # ์„ผ์„œ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ
639    sensor = SensorSimulator(device_info.device_id)
640
641    # ๋ช…๋ น ๊ตฌ๋…
642    def on_command(payload: Dict):
643        command = payload.get('command')
644        print(f"\n๋ช…๋ น ์‹คํ–‰: {command}")
645
646    client.subscribe_commands(device_info.device_id, on_command)
647
648    # ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰
649    print("\n" + "-"*60)
650    print("ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰ ์‹œ์ž‘")
651    print("-"*60)
652
653    for i in range(5):
654        print(f"\n[{i+1}/5] ์„ผ์„œ ๋ฐ์ดํ„ฐ ๋ฐœํ–‰")
655
656        # ์„ผ์„œ ์ฝ๊ธฐ
657        data = sensor.generate_reading()
658        print(f"  ์˜จ๋„: {data.temperature}ยฐC")
659        print(f"  ์Šต๋„: {data.humidity}%")
660        print(f"  ์••๋ ฅ: {data.pressure} hPa")
661        print(f"  ๋ฐฐํ„ฐ๋ฆฌ: {data.battery_level}%")
662
663        # ๋ฐœํ–‰
664        device_manager.publish_telemetry(data)
665
666        # Device Shadow ์—…๋ฐ์ดํŠธ
667        if i % 2 == 0:
668            shadow_state = {
669                "temperature": data.temperature,
670                "humidity": data.humidity,
671                "battery": data.battery_level
672            }
673            device_manager.update_device_shadow(shadow_state)
674
675        time.sleep(2)
676
677    # ํ†ต๊ณ„
678    print("\n" + "="*60)
679    print("AWS IoT ๋ฐ๋ชจ ์™„๋ฃŒ")
680    print("="*60)
681    stats = client.get_statistics()
682    print(f"๋ฐœํ–‰๋œ ๋ฉ”์‹œ์ง€: {stats['published']}๊ฐœ")
683
684    client.disconnect()
685
686
687def demo_gcp_pubsub():
688    """GCP Pub/Sub ๋ฐ๋ชจ"""
689    print("\n" + "="*60)
690    print("GCP Pub/Sub ๋ฐ๋ชจ")
691    print("="*60)
692
693    # ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
694    config = {
695        'project_id': 'my-iot-project-123456',
696        'topic_id': 'iot-telemetry',
697        'subscription_id': 'iot-telemetry-sub'
698    }
699
700    client = CloudIoTClient(CloudProvider.GCP_PUBSUB, config)
701
702    # ์„ผ์„œ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ
703    sensor = SensorSimulator("gcp-device-001")
704
705    # ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰
706    print("\n" + "-"*60)
707    print("ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๋ฐœํ–‰ ์‹œ์ž‘")
708    print("-"*60)
709
710    messages = []
711    for i in range(5):
712        data = sensor.generate_reading()
713        print(f"\n[{i+1}/5] ์„ผ์„œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ")
714        print(f"  ์˜จ๋„: {data.temperature}ยฐC")
715        print(f"  ์Šต๋„: {data.humidity}%")
716
717        # ๊ฐœ๋ณ„ ๋ฐœํ–‰
718        sensor_data = {
719            "temperature": data.temperature,
720            "humidity": data.humidity,
721            "pressure": data.pressure,
722            "battery_level": data.battery_level
723        }
724
725        client.publish_telemetry("gcp-device-001", sensor_data)
726        messages.append(data.to_dict())
727
728        time.sleep(1)
729
730    # ๋ฐฐ์น˜ ๋ฐœํ–‰
731    print("\n" + "-"*60)
732    print("๋ฐฐ์น˜ ๋ฐœํ–‰")
733    print("-"*60)
734    client.publisher.publish_batch(messages)
735
736    # ํ†ต๊ณ„
737    print("\n" + "="*60)
738    print("GCP Pub/Sub ๋ฐ๋ชจ ์™„๋ฃŒ")
739    print("="*60)
740    stats = client.get_statistics()
741    print(f"๋ฐœํ–‰๋œ ๋ฉ”์‹œ์ง€: {stats['published']}๊ฐœ")
742
743
744def demo_command_control():
745    """๋ช…๋ น ๋ฐ ์ œ์–ด ๋ฐ๋ชจ"""
746    print("\n" + "="*60)
747    print("๋ช…๋ น ๋ฐ ์ œ์–ด ๋ฐ๋ชจ")
748    print("="*60)
749
750    device_id = "smart-device-001"
751
752    # ๋ช…๋ น ์ƒ์„ฑ ์˜ˆ์‹œ
753    print("\n[ํด๋ผ์šฐ๋“œ โ†’ ๋””๋ฐ”์ด์Šค] ๋ช…๋ น ์ „์†ก")
754
755    # 1. LED ์ œ์–ด ๋ช…๋ น
756    led_command = MQTTMessageFormat.create_command(
757        device_id=device_id,
758        command="set_led",
759        parameters={"color": "red", "brightness": 80}
760    )
761    print(f"\n1. LED ์ œ์–ด ๋ช…๋ น:")
762    print(json.dumps(led_command, indent=2))
763
764    # 2. ์„ค์ • ๋ณ€๊ฒฝ ๋ช…๋ น
765    config_command = MQTTMessageFormat.create_command(
766        device_id=device_id,
767        command="update_config",
768        parameters={"report_interval": 60, "threshold_temp": 30}
769    )
770    print(f"\n2. ์„ค์ • ๋ณ€๊ฒฝ ๋ช…๋ น:")
771    print(json.dumps(config_command, indent=2))
772
773    # 3. ํŽŒ์›จ์–ด ์—…๋ฐ์ดํŠธ ๋ช…๋ น
774    firmware_command = MQTTMessageFormat.create_command(
775        device_id=device_id,
776        command="update_firmware",
777        parameters={"version": "2.0.0", "url": "https://example.com/firmware.bin"}
778    )
779    print(f"\n3. ํŽŒ์›จ์–ด ์—…๋ฐ์ดํŠธ ๋ช…๋ น:")
780    print(json.dumps(firmware_command, indent=2))
781
782    # ์‘๋‹ต ์˜ˆ์‹œ
783    print("\n[๋””๋ฐ”์ด์Šค โ†’ ํด๋ผ์šฐ๋“œ] ๋ช…๋ น ์‘๋‹ต")
784
785    response = MQTTMessageFormat.create_response(
786        device_id=device_id,
787        command_id=led_command["command_id"],
788        status="success",
789        result={"led_state": "on", "color": "red", "brightness": 80}
790    )
791    print(json.dumps(response, indent=2))
792
793
794# ==============================================================================
795# ๋ฉ”์ธ ์‹คํ–‰
796# ==============================================================================
797
798def main():
799    """๋ฉ”์ธ ํ•จ์ˆ˜"""
800    print("ํด๋ผ์šฐ๋“œ IoT ํ†ตํ•ฉ - ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ชจ๋“œ")
801    print("="*60)
802    print("์ด ํ”„๋กœ๊ทธ๋žจ์€ ์‹ค์ œ ํด๋ผ์šฐ๋“œ ๊ณ„์ • ์—†์ด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
803    print()
804
805    # ๋ฉ”๋‰ด
806    print("๋ฐ๋ชจ ์‹œ๋‚˜๋ฆฌ์˜ค:")
807    print("  1. AWS IoT Core")
808    print("  2. GCP Pub/Sub")
809    print("  3. ๋ช…๋ น ๋ฐ ์ œ์–ด")
810    print("  4. ์ „์ฒด ์‹คํ–‰")
811    print()
812
813    choice = input("์„ ํƒ (1-4, ๊ธฐ๋ณธ๊ฐ’=4): ").strip() or "4"
814
815    if choice == "1":
816        demo_aws_iot()
817    elif choice == "2":
818        demo_gcp_pubsub()
819    elif choice == "3":
820        demo_command_control()
821    elif choice == "4":
822        demo_aws_iot()
823        time.sleep(2)
824        demo_gcp_pubsub()
825        time.sleep(2)
826        demo_command_control()
827    else:
828        print("์ž˜๋ชป๋œ ์„ ํƒ")
829
830
831if __name__ == "__main__":
832    main()