센서 및 IoT 데이터 수집

센서 및 IoT 데이터 수집 #

개요 #

IoT(Internet of Things) 기기와 센서들은 실시간으로 다양한 환경 데이터를 생성한다. 이러한 데이터는 온도, 습도, 압력, 움직임, 위치 등 물리적 세계의 상태를 디지털로 변환하여 새로운 인사이트와 자동화 기회를 제공한다.

IoT 데이터의 특성 #

대용량 (Volume) #

  • 수많은 센서에서 지속적으로 데이터 생성
  • 시간당 수백만 개의 데이터 포인트
  • 장기간 축적되는 시계열 데이터

고속성 (Velocity) #

  • 실시간 또는 준실시간 데이터 스트림
  • 빠른 의사결정이 필요한 상황
  • 지연 시간(Latency) 최소화 필요

다양성 (Variety) #

  • 다양한 센서 타입과 데이터 형식
  • 구조화된 데이터와 비구조화된 데이터 혼재
  • 다양한 통신 프로토콜 사용

주요 센서 유형과 데이터 #

환경 센서 #

# 온도/습도 센서 데이터 예시
import random
import datetime
import json

def generate_environmental_data():
    """
    환경 센서 데이터 생성 시뮬레이션
    """
    data = {
        'sensor_id': 'ENV_001',
        'timestamp': datetime.datetime.now().isoformat(),
        'temperature': round(random.uniform(18.0, 28.0), 2),
        'humidity': round(random.uniform(40.0, 70.0), 2),
        'pressure': round(random.uniform(1000.0, 1020.0), 2),
        'air_quality_index': random.randint(0, 300),
        'location': {
            'building': 'Building A',
            'floor': 3,
            'room': '301'
        }
    }
    return data

# 연속 데이터 생성
import time
def continuous_environmental_monitoring(duration_minutes=60):
    """
    지속적인 환경 모니터링
    """
    end_time = time.time() + (duration_minutes * 60)
    
    while time.time() < end_time:
        sensor_data = generate_environmental_data()
        print(json.dumps(sensor_data, indent=2))
        
        # 데이터 저장 또는 전송 로직
        store_sensor_data(sensor_data)
        
        time.sleep(10)  # 10초마다 데이터 수집

움직임/위치 센서 #

import math

class MovementSensor:
    def __init__(self, sensor_id, location):
        self.sensor_id = sensor_id
        self.location = location
        self.last_position = {'x': 0, 'y': 0, 'z': 0}
    
    def get_accelerometer_data(self):
        """
        가속도계 데이터 생성
        """
        return {
            'sensor_id': self.sensor_id,
            'timestamp': datetime.datetime.now().isoformat(),
            'acceleration': {
                'x': round(random.uniform(-2.0, 2.0), 3),
                'y': round(random.uniform(-2.0, 2.0), 3),
                'z': round(random.uniform(9.5, 10.5), 3)  # 중력 포함
            },
            'gyroscope': {
                'x': round(random.uniform(-180, 180), 2),
                'y': round(random.uniform(-180, 180), 2),
                'z': round(random.uniform(-180, 180), 2)
            }
        }
    
    def get_gps_data(self):
        """
        GPS 위치 데이터 생성
        """
        # 서울 중심 좌표 기준 랜덤 위치
        base_lat = 37.5665
        base_lng = 126.9780
        
        return {
            'sensor_id': self.sensor_id,
            'timestamp': datetime.datetime.now().isoformat(),
            'gps': {
                'latitude': base_lat + random.uniform(-0.01, 0.01),
                'longitude': base_lng + random.uniform(-0.01, 0.01),
                'altitude': round(random.uniform(0, 100), 1),
                'accuracy': round(random.uniform(1, 10), 1)
            },
            'speed': round(random.uniform(0, 50), 1),  # km/h
            'heading': round(random.uniform(0, 360), 1)  # degrees
        }

산업용 센서 #

class IndustrialSensor:
    def __init__(self, machine_id, sensor_type):
        self.machine_id = machine_id
        self.sensor_type = sensor_type
    
    def get_vibration_data(self):
        """
        진동 센서 데이터 (예측 정비용)
        """
        # 정상 범위를 벗어나면 이상 신호
        base_vibration = 2.5
        anomaly_factor = random.choice([1.0, 1.0, 1.0, 1.0, 3.5])  # 20% 확률로 이상
        
        return {
            'machine_id': self.machine_id,
            'sensor_type': 'vibration',
            'timestamp': datetime.datetime.now().isoformat(),
            'vibration_level': round(base_vibration * anomaly_factor, 2),
            'frequency': round(random.uniform(50, 200), 1),
            'amplitude': round(random.uniform(0.1, 5.0), 2),
            'is_anomaly': anomaly_factor > 2.0
        }
    
    def get_temperature_data(self):
        """
        기계 온도 센서 데이터
        """
        normal_temp = 65  # 정상 작동 온도
        current_temp = normal_temp + random.uniform(-5, 15)
        
        return {
            'machine_id': self.machine_id,
            'sensor_type': 'temperature',
            'timestamp': datetime.datetime.now().isoformat(),
            'temperature': round(current_temp, 1),
            'threshold_warning': 80,
            'threshold_critical': 90,
            'status': 'normal' if current_temp < 80 else 'warning' if current_temp < 90 else 'critical'
        }

데이터 수집 프로토콜 #

MQTT (Message Queuing Telemetry Transport) #

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

class MQTTDataCollector:
    def __init__(self, broker_host, broker_port=1883):
        self.broker_host = broker_host
        self.broker_port = broker_port
        self.client = mqtt.Client()
        self.collected_data = []
        
        # 콜백 함수 설정
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
    
    def on_connect(self, client, userdata, flags, rc):
        """
        MQTT 브로커 연결 시 호출
        """
        if rc == 0:
            print("MQTT 브로커 연결 성공")
            # 토픽 구독
            client.subscribe("sensors/+/data")  # 모든 센서 데이터
            client.subscribe("alerts/+")        # 알림 메시지
        else:
            print(f"MQTT 브로커 연결 실패: {rc}")
    
    def on_message(self, client, userdata, msg):
        """
        메시지 수신 시 호출
        """
        try:
            topic = msg.topic
            payload = json.loads(msg.payload.decode())
            
            data_record = {
                'topic': topic,
                'timestamp': datetime.datetime.now().isoformat(),
                'data': payload
            }
            
            self.collected_data.append(data_record)
            print(f"데이터 수신: {topic} - {payload}")
            
            # 실시간 처리 로직
            self.process_real_time_data(data_record)
            
        except Exception as e:
            print(f"메시지 처리 오류: {e}")
    
    def process_real_time_data(self, data_record):
        """
        실시간 데이터 처리
        """
        topic = data_record['topic']
        data = data_record['data']
        
        # 임계값 체크
        if 'temperature' in data and data['temperature'] > 80:
            self.send_alert(f"고온 경고: {data['temperature']}°C")
        
        # 데이터베이스 저장
        self.save_to_database(data_record)
    
    def send_alert(self, message):
        """
        경고 메시지 발송
        """
        alert_topic = "alerts/temperature"
        alert_data = {
            'message': message,
            'timestamp': datetime.datetime.now().isoformat(),
            'severity': 'warning'
        }
        
        self.client.publish(alert_topic, json.dumps(alert_data))
    
    def start_collecting(self):
        """
        데이터 수집 시작
        """
        self.client.connect(self.broker_host, self.broker_port, 60)
        self.client.loop_forever()

# 사용 예시
collector = MQTTDataCollector("localhost")
collector.start_collecting()

HTTP API를 통한 데이터 수집 #

from flask import Flask, request, jsonify
import sqlite3
from datetime import datetime

app = Flask(__name__)

class IoTDataAPI:
    def __init__(self, db_path='iot_data.db'):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """
        데이터베이스 초기화
        """
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS sensor_data (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                device_id TEXT NOT NULL,
                sensor_type TEXT NOT NULL,
                timestamp TEXT NOT NULL,
                value REAL NOT NULL,
                unit TEXT,
                location TEXT,
                metadata TEXT
            )
        ''')
        
        conn.commit()
        conn.close()
    
    @app.route('/api/sensors/data', methods=['POST'])
    def receive_sensor_data():
        """
        센서 데이터 수신 엔드포인트
        """
        try:
            data = request.json
            
            # 데이터 검증
            required_fields = ['device_id', 'sensor_type', 'value']
            if not all(field in data for field in required_fields):
                return jsonify({'error': 'Missing required fields'}), 400
            
            # 데이터베이스 저장
            api = IoTDataAPI()
            api.save_sensor_data(data)
            
            return jsonify({'status': 'success', 'message': 'Data received'}), 200
            
        except Exception as e:
            return jsonify({'error': str(e)}), 500
    
    def save_sensor_data(self, data):
        """
        센서 데이터 저장
        """
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            INSERT INTO sensor_data 
            (device_id, sensor_type, timestamp, value, unit, location, metadata)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        ''', (
            data['device_id'],
            data['sensor_type'],
            data.get('timestamp', datetime.now().isoformat()),
            data['value'],
            data.get('unit', ''),
            data.get('location', ''),
            json.dumps(data.get('metadata', {}))
        ))
        
        conn.commit()
        conn.close()

# API 서버 실행
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

실시간 데이터 처리 #

Apache Kafka를 활용한 스트리밍 #

from kafka import KafkaProducer, KafkaConsumer
import json
import time

class IoTKafkaProducer:
    def __init__(self, bootstrap_servers=['localhost:9092']):
        self.producer = KafkaProducer(
            bootstrap_servers=bootstrap_servers,
            value_serializer=lambda v: json.dumps(v).encode('utf-8')
        )
    
    def send_sensor_data(self, topic, sensor_data):
        """
        센서 데이터를 Kafka 토픽으로 전송
        """
        try:
            future = self.producer.send(topic, sensor_data)
            record_metadata = future.get(timeout=10)
            print(f"메시지 전송 성공: {record_metadata.topic}:{record_metadata.partition}:{record_metadata.offset}")
        except Exception as e:
            print(f"메시지 전송 실패: {e}")

class IoTKafkaConsumer:
    def __init__(self, topics, bootstrap_servers=['localhost:9092']):
        self.consumer = KafkaConsumer(
            *topics,
            bootstrap_servers=bootstrap_servers,
            value_deserializer=lambda m: json.loads(m.decode('utf-8')),
            group_id='iot-data-processors'
        )
    
    def process_messages(self):
        """
        메시지 처리
        """
        for message in self.consumer:
            topic = message.topic
            data = message.value
            
            print(f"토픽: {topic}, 데이터: {data}")
            
            # 데이터 유형별 처리
            if topic == 'temperature-sensors':
                self.process_temperature_data(data)
            elif topic == 'vibration-sensors':
                self.process_vibration_data(data)
    
    def process_temperature_data(self, data):
        """
        온도 데이터 처리
        """
        if data['temperature'] > 80:
            alert = {
                'alert_type': 'high_temperature',
                'device_id': data['device_id'],
                'temperature': data['temperature'],
                'timestamp': datetime.now().isoformat()
            }
            # 알림 시스템으로 전송
            self.send_alert(alert)
    
    def process_vibration_data(self, data):
        """
        진동 데이터 처리 (예측 정비)
        """
        if data['vibration_level'] > 5.0:
            maintenance_alert = {
                'alert_type': 'maintenance_required',
                'machine_id': data['machine_id'],
                'vibration_level': data['vibration_level'],
                'estimated_failure_time': '72 hours'
            }
            self.schedule_maintenance(maintenance_alert)

# 사용 예시
producer = IoTKafkaProducer()
consumer = IoTKafkaConsumer(['temperature-sensors', 'vibration-sensors'])

# 프로듀서로 데이터 전송
sensor_data = {
    'device_id': 'TEMP_001',
    'temperature': 85.5,
    'timestamp': datetime.now().isoformat()
}
producer.send_sensor_data('temperature-sensors', sensor_data)

# 컨슈머로 데이터 처리
consumer.process_messages()

Apache Spark Streaming #

from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import *

def create_spark_streaming_session():
    """
    Spark Streaming 세션 생성
    """
    spark = SparkSession.builder \
        .appName("IoTDataStreaming") \
        .config("spark.sql.streaming.checkpointLocation", "/tmp/checkpoint") \
        .getOrCreate()
    
    return spark

def process_iot_stream(spark):
    """
    IoT 데이터 스트림 처리
    """
    # Kafka에서 스트림 읽기
    df = spark \
        .readStream \
        .format("kafka") \
        .option("kafka.bootstrap.servers", "localhost:9092") \
        .option("subscribe", "iot-sensors") \
        .load()
    
    # JSON 데이터 파싱
    schema = StructType([
        StructField("device_id", StringType(), True),
        StructField("sensor_type", StringType(), True),
        StructField("timestamp", TimestampType(), True),
        StructField("value", DoubleType(), True),
        StructField("location", StringType(), True)
    ])
    
    parsed_df = df.select(
        from_json(col("value").cast("string"), schema).alias("data")
    ).select("data.*")
    
    # 윈도우 기반 집계
    windowed_df = parsed_df \
        .withWatermark("timestamp", "10 minutes") \
        .groupBy(
            window(col("timestamp"), "5 minutes"),
            col("device_id"),
            col("sensor_type")
        ) \
        .agg(
            avg("value").alias("avg_value"),
            max("value").alias("max_value"),
            min("value").alias("min_value"),
            count("*").alias("count")
        )
    
    # 결과 출력
    query = windowed_df \
        .writeStream \
        .outputMode("update") \
        .format("console") \
        .trigger(processingTime='30 seconds') \
        .start()
    
    return query

# Spark Streaming 실행
spark = create_spark_streaming_session()
query = process_iot_stream(spark)
query.awaitTermination()

데이터 저장 및 관리 #

시계열 데이터베이스 (InfluxDB) #

from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
import pandas as pd

class InfluxDBManager:
    def __init__(self, url, token, org, bucket):
        self.client = InfluxDBClient(url=url, token=token, org=org)
        self.write_api = self.client.write_api(write_options=SYNCHRONOUS)
        self.query_api = self.client.query_api()
        self.bucket = bucket
        self.org = org
    
    def write_sensor_data(self, sensor_data):
        """
        센서 데이터를 InfluxDB에 저장
        """
        point = Point("sensor_measurement") \
            .tag("device_id", sensor_data['device_id']) \
            .tag("sensor_type", sensor_data['sensor_type']) \
            .tag("location", sensor_data.get('location', 'unknown')) \
            .field("value", sensor_data['value']) \
            .time(sensor_data['timestamp'], WritePrecision.S)
        
        self.write_api.write(bucket=self.bucket, org=self.org, record=point)
    
    def query_recent_data(self, device_id, hours=24):
        """
        최근 데이터 조회
        """
        query = f'''
        from(bucket: "{self.bucket}")
        |> range(start: -{hours}h)
        |> filter(fn: (r) => r._measurement == "sensor_measurement")
        |> filter(fn: (r) => r.device_id == "{device_id}")
        |> aggregateWindow(every: 1m, fn: mean, createEmpty: false)
        |> yield(name: "mean")
        '''
        
        result = self.query_api.query(org=self.org, query=query)
        
        # DataFrame으로 변환
        data_points = []
        for table in result:
            for record in table.records:
                data_points.append({
                    'time': record.get_time(),
                    'device_id': record.values.get('device_id'),
                    'sensor_type': record.values.get('sensor_type'),
                    'value': record.get_value()
                })
        
        return pd.DataFrame(data_points)
    
    def calculate_anomalies(self, device_id, threshold_std=2):
        """
        이상치 탐지
        """
        query = f'''
        from(bucket: "{self.bucket}")
        |> range(start: -7d)
        |> filter(fn: (r) => r._measurement == "sensor_measurement")
        |> filter(fn: (r) => r.device_id == "{device_id}")
        |> aggregateWindow(every: 1h, fn: mean)
        |> movingAverage(n: 24)
        '''
        
        # 통계적 이상치 탐지 로직
        df = self.query_to_dataframe(query)
        mean_val = df['value'].mean()
        std_val = df['value'].std()
        
        anomalies = df[
            (df['value'] > mean_val + threshold_std * std_val) |
            (df['value'] < mean_val - threshold_std * std_val)
        ]
        
        return anomalies

# 사용 예시
influx = InfluxDBManager(
    url="http://localhost:8086",
    token="your-token",
    org="your-org",
    bucket="iot-data"
)

# 센서 데이터 저장
sensor_data = {
    'device_id': 'TEMP_001',
    'sensor_type': 'temperature',
    'value': 23.5,
    'location': 'Building A',
    'timestamp': datetime.now()
}

influx.write_sensor_data(sensor_data)

Apache Cassandra를 활용한 대용량 저장 #

from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
import uuid
from datetime import datetime

class CassandraIoTStore:
    def __init__(self, hosts, keyspace, username=None, password=None):
        if username and password:
            auth_provider = PlainTextAuthProvider(username=username, password=password)
            self.cluster = Cluster(hosts, auth_provider=auth_provider)
        else:
            self.cluster = Cluster(hosts)
        
        self.session = self.cluster.connect()
        self.keyspace = keyspace
        self.setup_keyspace()
    
    def setup_keyspace(self):
        """
        키스페이스 및 테이블 설정
        """
        # 키스페이스 생성
        self.session.execute(f"""
            CREATE KEYSPACE IF NOT EXISTS {self.keyspace}
            WITH replication = {{
                'class': 'SimpleStrategy',
                'replication_factor': 3
            }}
        """)
        
        self.session.set_keyspace(self.keyspace)
        
        # 테이블 생성 - 시계열 데이터에 최적화
        self.session.execute("""
            CREATE TABLE IF NOT EXISTS sensor_data (
                device_id text,
                year int,
                month int,
                day int,
                timestamp timestamp,
                sensor_type text,
                value double,
                metadata map<text, text>,
                id uuid,
                PRIMARY KEY ((device_id, year, month, day), timestamp, id)
            ) WITH CLUSTERING ORDER BY (timestamp DESC, id ASC)
        """)
    
    def insert_sensor_data(self, sensor_data):
        """
        센서 데이터 삽입
        """
        timestamp = datetime.fromisoformat(sensor_data['timestamp'])
        
        prepared = self.session.prepare("""
            INSERT INTO sensor_data 
            (device_id, year, month, day, timestamp, sensor_type, value, metadata, id)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        """)
        
        self.session.execute(prepared, [
            sensor_data['device_id'],
            timestamp.year,
            timestamp.month,
            timestamp.day,
            timestamp,
            sensor_data['sensor_type'],
            sensor_data['value'],
            sensor_data.get('metadata', {}),
            uuid.uuid4()
        ])
    
    def query_device_data(self, device_id, start_date, end_date):
        """
        특정 디바이스의 날짜 범위 데이터 조회
        """
        results = []
        
        # 날짜별로 쿼리 (파티션 키 때문에)
        current_date = start_date
        while current_date <= end_date:
            rows = self.session.execute("""
                SELECT * FROM sensor_data
                WHERE device_id = ? AND year = ? AND month = ? AND day = ?
                AND timestamp >= ? AND timestamp <= ?
            """, [
                device_id,
                current_date.year,
                current_date.month,
                current_date.day,
                start_date,
                end_date
            ])
            
            results.extend(rows)
            current_date += timedelta(days=1)
        
        return results

엣지 컴퓨팅과 데이터 전처리 #

엣지에서의 데이터 필터링 #

class EdgeDataProcessor:
    def __init__(self, device_id, sampling_rate=1.0):
        self.device_id = device_id
        self.sampling_rate = sampling_rate  # 0.0 ~ 1.0
        self.data_buffer = []
        self.last_values = {}
    
    def should_transmit(self, sensor_data):
        """
        데이터 전송 여부 결정
        """
        sensor_type = sensor_data['sensor_type']
        current_value = sensor_data['value']
        
        # 샘플링 비율 적용
        if random.random() > self.sampling_rate:
            return False
        
        # 변화량 기반 필터링
        if sensor_type in self.last_values:
            last_value = self.last_values[sensor_type]
            change_percentage = abs(current_value - last_value) / last_value
            
            # 5% 이상 변화시에만 전송
            if change_percentage < 0.05:
                return False
        
        self.last_values[sensor_type] = current_value
        return True
    
    def aggregate_data(self, time_window_seconds=60):
        """
        시간 윈도우 기반 데이터 집계
        """
        now = datetime.now()
        window_start = now - timedelta(seconds=time_window_seconds)
        
        # 윈도우 내 데이터 필터링
        window_data = [
            data for data in self.data_buffer
            if datetime.fromisoformat(data['timestamp']) >= window_start
        ]
        
        if not window_data:
            return None
        
        # 센서 타입별 집계
        aggregated = {}
        for data in window_data:
            sensor_type = data['sensor_type']
            if sensor_type not in aggregated:
                aggregated[sensor_type] = []
            aggregated[sensor_type].append(data['value'])
        
        # 통계 계산
        result = {
            'device_id': self.device_id,
            'timestamp': now.isoformat(),
            'window_start': window_start.isoformat(),
            'aggregation': {}
        }
        
        for sensor_type, values in aggregated.items():
            result['aggregation'][sensor_type] = {
                'count': len(values),
                'mean': sum(values) / len(values),
                'min': min(values),
                'max': max(values),
                'std': statistics.stdev(values) if len(values) > 1 else 0
            }
        
        return result

보안 고려사항 #

디바이스 인증 #

import jwt
import hashlib
from cryptography.fernet import Fernet

class IoTSecurityManager:
    def __init__(self, secret_key):
        self.secret_key = secret_key
        self.cipher_suite = Fernet(Fernet.generate_key())
    
    def generate_device_token(self, device_id, valid_hours=24):
        """
        디바이스 인증 토큰 생성
        """
        payload = {
            'device_id': device_id,
            'issued_at': datetime.now().timestamp(),
            'expires_at': (datetime.now() + timedelta(hours=valid_hours)).timestamp()
        }
        
        token = jwt.encode(payload, self.secret_key, algorithm='HS256')
        return token
    
    def verify_device_token(self, token):
        """
        디바이스 토큰 검증
        """
        try:
            payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
            
            # 만료 시간 확인
            if datetime.now().timestamp() > payload['expires_at']:
                return None
            
            return payload['device_id']
        except jwt.InvalidTokenError:
            return None
    
    def encrypt_sensor_data(self, data):
        """
        센서 데이터 암호화
        """
        json_data = json.dumps(data)
        encrypted_data = self.cipher_suite.encrypt(json_data.encode())
        return encrypted_data
    
    def decrypt_sensor_data(self, encrypted_data):
        """
        센서 데이터 복호화
        """
        decrypted_data = self.cipher_suite.decrypt(encrypted_data)
        return json.loads(decrypted_data.decode())

모니터링 및 알림 시스템 #

실시간 대시보드 #

import dash
from dash import dcc, html, Input, Output
import plotly.graph_objs as go
import pandas as pd

class IoTDashboard:
    def __init__(self, data_source):
        self.app = dash.Dash(__name__)
        self.data_source = data_source
        self.setup_layout()
        self.setup_callbacks()
    
    def setup_layout(self):
        """
        대시보드 레이아웃 설정
        """
        self.app.layout = html.Div([
            html.H1("IoT 센서 모니터링 대시보드"),
            
            dcc.Dropdown(
                id='device-dropdown',
                options=[
                    {'label': 'Temperature Sensor 1', 'value': 'TEMP_001'},
                    {'label': 'Humidity Sensor 1', 'value': 'HUM_001'},
                    {'label': 'Vibration Sensor 1', 'value': 'VIB_001'}
                ],
                value='TEMP_001'
            ),
            
            dcc.Graph(id='real-time-graph'),
            
            dcc.Interval(
                id='interval-component',
                interval=5000,  # 5초마다 업데이트
                n_intervals=0
            ),
            
            html.Div(id='alert-panel')
        ])
    
    def setup_callbacks(self):
        """
        콜백 함수 설정
        """
        @self.app.callback(
            [Output('real-time-graph', 'figure'),
             Output('alert-panel', 'children')],
            [Input('interval-component', 'n_intervals'),
             Input('device-dropdown', 'value')]
        )
        def update_dashboard(n_intervals, selected_device):
            # 최근 데이터 조회
            recent_data = self.data_source.get_recent_data(selected_device, hours=1)
            
            # 그래프 생성
            figure = {
                'data': [
                    go.Scatter(
                        x=recent_data['timestamp'],
                        y=recent_data['value'],
                        mode='lines+markers',
                        name='Sensor Value'
                    )
                ],
                'layout': go.Layout(
                    title=f'{selected_device} Real-time Data',
                    xaxis={'title': 'Time'},
                    yaxis={'title': 'Value'}
                )
            }
            
            # 알림 패널 업데이트
            alerts = self.check_alerts(recent_data)
            alert_components = [
                html.Div(f"⚠️ {alert}", className="alert")
                for alert in alerts
            ]
            
            return figure, alert_components
    
    def check_alerts(self, data):
        """
        알림 조건 확인
        """
        alerts = []
        
        if not data.empty:
            latest_value = data['value'].iloc[-1]
            
            # 임계값 체크
            if latest_value > 80:
                alerts.append("높은 온도 감지!")
            elif latest_value < 0:
                alerts.append("비정상적인 저온 감지!")
        
        return alerts
    
    def run(self, debug=True, port=8050):
        """
        대시보드 실행
        """
        self.app.run_server(debug=debug, port=port)

센서 및 IoT 데이터 수집은 실시간성, 확장성, 신뢰성이 핵심 요구사항이다. 적절한 프로토콜 선택, 효율적인 데이터 처리, 보안 강화를 통해 IoT 환경에서 안정적인 데이터 수집 시스템을 구축할 수 있다.