diff --git a/service/simulatelogic/ContinuousSimulatorMixin.py b/service/simulatelogic/ContinuousSimulatorMixin.py index 7432951..0c5617f 100644 --- a/service/simulatelogic/ContinuousSimulatorMixin.py +++ b/service/simulatelogic/ContinuousSimulatorMixin.py @@ -32,7 +32,7 @@ def _generate_continuous_val(self): # 2) 이상치 if random.random() < self.OUTLIER_P: - val = random.gauss(self.MU, self.SIGMA) + val = random.uniform(self.UPPER * 0.8, self.UPPER) # 3) 범위 클램프 & 저장 val = round(max(self.LOWER, min(self.UPPER, val)), 2) diff --git a/service/simulatelogic/PowerContinuousSimulatorMixin.py b/service/simulatelogic/PowerContinuousSimulatorMixin.py new file mode 100644 index 0000000..0a46a48 --- /dev/null +++ b/service/simulatelogic/PowerContinuousSimulatorMixin.py @@ -0,0 +1,61 @@ +import random +from scipy.stats import truncnorm + +class PowerContinuousSimulatorMixin: + MU = None + SIGMA = None + LOWER = 0 + UPPER = None + OUTLIER_P = 0.005 + DRIFT_THETA = 0.2 + SMALL_SIGMA_RATIO = 0.07 + + # ── 내부 상태 초기화 ───────────────────────────────────── + def _reset_state(self): + a, b = (self.LOWER - self.MU) / self.SIGMA, (self.UPPER - self.MU) / self.SIGMA + first = truncnorm.rvs(a, b, loc=self.MU, scale=self.SIGMA/3) + self.prev_val = round(first, 2) + + # ── 핵심 데이터 생성 로직 ───────────────────────────────── + def _generate_continuous_val(self): + if not hasattr(self, "prev_val"): + self._reset_state() + + # 1) 평균 회귀 + 작은 노이즈 + mean_revert = self.MU + (self.prev_val - self.MU) * (1 - self.DRIFT_THETA) + small_sigma = self.SIGMA * self.SMALL_SIGMA_RATIO + val = random.gauss(mean_revert, small_sigma) + + # 2) 이상치 발생 - 이상치는 upper의 80~100%로 튀게 + if random.random() < self.OUTLIER_P: + val = random.uniform(self.UPPER * 0.8, self.UPPER) + + # 3) 범위 클램프 & 저장 + val = round(max(self.LOWER, min(self.UPPER, val)), 2) + self.prev_val = val + return val + # ── 파라미터를 받는 전력값 생성 메서드 추가 ───────────────────────────────── + def _generate_power_val(self, mu, sigma, lower, upper): + """특정 파라미터로 전력값 생성 (독립적인 상태 관리)""" + state_key = f"prev_val_{mu}_{sigma}" + + if not hasattr(self, state_key): + a, b = (lower - mu) / sigma, (upper - mu) / sigma + first = truncnorm.rvs(a, b, loc=mu, scale=sigma/3) + setattr(self, state_key, round(first, 2)) + + prev_val = getattr(self, state_key) + + # 1) 평균 회귀 + 작은 노이즈 + mean_revert = mu + (prev_val - mu) * (1 - self.DRIFT_THETA) + small_sigma = sigma * self.SMALL_SIGMA_RATIO + val = random.gauss(mean_revert, small_sigma) + + # 2) 이상치 발생 - 이상치는 upper의 80~100%로 튀게 + if random.random() < self.OUTLIER_P: + val = random.uniform(upper * 0.8, upper) + + # 3) 범위 클램프 & 저장 + val = round(max(lower, min(upper, val)), 2) + setattr(self, state_key, val) + return val \ No newline at end of file diff --git a/service/simulation/CurrentSimulator.py b/service/simulation/CurrentSimulator.py index d3c63e6..a865351 100644 --- a/service/simulation/CurrentSimulator.py +++ b/service/simulation/CurrentSimulator.py @@ -5,8 +5,15 @@ class CurrentSimulator(ContinuousSimulatorMixin,SimulatorInterface2): # 타입별 시뮬레이터 세팅 SENSOR_TYPE = "current" # 센서 타입 # MU, SIGMA = 62.51, 33.76 - MU, SIGMA = 5, 30 # 평균치 , 표준 편차 - LOWER, UPPER = 1,50 # 측정 범위 + # MU, SIGMA = 5, 30 # 평균치 , 표준 편차 + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 5, 30 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 80, 25 + + ENV_LOWER, ENV_UPPER = 1,50 # 측정 범위 + FAC_LOWER, FAC_UPPER = 20, 200 + OUTLIER_P = 0.10 # 10 % 확률로 경보 값 생성 def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): @@ -22,6 +29,20 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 시뮬레이터 마다 개별적으로 사용하는 속성(토픽, 수집 데이터 초기값) self.sensor_id = f"UA10C-CUR-2406089{idx}" # 센서 ID self.type = "current" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) @@ -36,14 +57,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 데이터 생성 로직 정의 def _generate_data(self) -> dict: + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - # "val": round(random.uniform(0.1 + self.idx, 10.0 + self.idx), 2) - "val": self._generate_continuous_val() - # 0: 7, 1: 7이상, 2: 30 이상 최소값은 0 + "val": sensor_value } ################################################ diff --git a/service/simulation/DustSimulator.py b/service/simulation/DustSimulator.py index 643eac3..29e0342 100644 --- a/service/simulation/DustSimulator.py +++ b/service/simulation/DustSimulator.py @@ -4,7 +4,12 @@ class DustSimulator(ContinuousSimulatorMixin,SimulatorInterface2): # dust_simulator.py (Mixin 상수 부분만) SENSOR_TYPE = "dust" # ㎍/㎥ - MU, SIGMA = 50, 25 # 평균 50 ㎍/㎥, σ = 25 + # MU, SIGMA = 50, 25 # 평균 50 ㎍/㎥, σ = 25 + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 50, 25 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 50, 25 + LOWER, UPPER = 0, 300 # 0 ‒ 300 ㎍/㎥ 범위 # SMALL_SIGMA_RATIO = 0.10 # 정상 변동폭 = σ의 10 %(≈ ±2.5) @@ -21,6 +26,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 시뮬레이터 마다 개별적으로 사용하는 속성(토픽, 수집 데이터 초기값) self.sensor_id = f"UA10D-DST-2406089{idx}" # 센서 ID self.type = "dust" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) @@ -36,12 +51,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 데이터 생성 로직 정의 def _generate_data(self) -> dict: + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - "val": self._generate_continuous_val() + "val": sensor_value } ################################################ diff --git a/service/simulation/HumiditySimulator.py b/service/simulation/HumiditySimulator.py index ac0b3ad..6341ded 100644 --- a/service/simulation/HumiditySimulator.py +++ b/service/simulation/HumiditySimulator.py @@ -5,9 +5,15 @@ class HumiditySimulator(ContinuousSimulatorMixin,SimulatorInterface2): # 타입별 시뮬레이터 세팅 SENSOR_TYPE = "humid" - MU, SIGMA = 55, 15 - LOWER, UPPER = 0, 100 - OUTLIER_P = 0.1 + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 55, 15 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 50.02, 11.84 + + ENV_LOWER, ENV_UPPER = 0, 100 + FAC_LOWER, FAC_UPPER = 10, 90 + + OUTLIER_P = 0.05 def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): ######################################### @@ -27,6 +33,20 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co ######################################### self.sensor_id = f"UA10H-HUM-3406089{idx}" # 센서 ID self.type = "humid" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + # shadow 등록용 토픽 self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" # shadow 제어 명령 구독용 토픽 @@ -41,12 +61,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co ################################################ def _generate_data(self) -> dict: """ 데이터 생성 메서드 """ + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - "val": self._generate_continuous_val() + "val": sensor_value } ################################################ diff --git a/service/simulation/PowerSimulator.py b/service/simulation/PowerSimulator.py new file mode 100644 index 0000000..450a85f --- /dev/null +++ b/service/simulation/PowerSimulator.py @@ -0,0 +1,107 @@ +from service.simulatelogic.PowerContinuousSimulatorMixin import PowerContinuousSimulatorMixin +from .SimulatorInterface2 import SimulatorInterface2 + +class PowerSimulator(PowerContinuousSimulatorMixin,SimulatorInterface2): + # 정규분포 상속로직에 집어넣을 숫자들 + SENSOR_TYPE = "power" # 센서 타입 + # 유효전력 정규분포 데이터 + Active_MU = 53237 # 평균 (실데이터) + Active_SIGMA = 38263 # std + Active_LOWER = 0 + Active_UPPER = 250000 # max + # 무효전력 정규분포 데이터 + Reactive_MU = 29018 # 평균 (실데이터) + Reactive_SIGMA = 19247 # std + Reactive_LOWER = 0 + Reactive_UPPER = 480000 # max + + + def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): + super().__init__( + idx=idx, + zone_id=zone_id, + equip_id=equip_id, + interval=interval, + msg_count=msg_count, + conn=conn + ) + self.sensor_id = f"UA10P-PWR-2406089{idx}" # Sensor ID + self.type = "power" # Sensor type + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + # if self.is_environment_sensor: + # self.MU = self.ENV_MU + # self.SIGMA = self.ENV_SIGMA + # else: + # self.MU = self.FAC_MU + # self.SIGMA = self.FAC_SIGMA + + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" + self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" + # 각각의 토픽 설정 + self.active_topic = self._build_topic(zone_id, equip_id, self.sensor_id, "active_power") + self.reactive_topic = self._build_topic(zone_id, equip_id, self.sensor_id, "reactive_power") + self.target_active_power = None + self.target_reactive_power = None + + ################################################z + # 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현) + # 예) 온도, 습도, 진동, 전류 등등 + ################################################ + def _generate_data(self) -> list: + """ 데이터 생성 메서드 """ + # Active Power 데이터 생성 + active_power_value = self._generate_power_val( + self.Active_MU, + self.Active_SIGMA, + self.Active_LOWER, + self.Active_UPPER + ) + + # Reactive Power 데이터 생성 + reactive_power_value = self._generate_power_val( + self.Reactive_MU, + self.Reactive_SIGMA, + self.Reactive_LOWER, + self.Reactive_UPPER + ) + + # 두 개의 데이터 객체 생성 + active_data = { + "zoneId": self.zone_id, + "equipId": self.equip_id, + "sensorId": self.sensor_id, + "sensorType": "active_power", + "val": active_power_value + } + + reactive_data = { + "zoneId": self.zone_id, + "equipId": self.equip_id, + "sensorId": self.sensor_id, + "sensorType": "reactive_power", + "val": reactive_power_value + } + + return [active_data, reactive_data] + + ################################################ + # 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정) + ################################################ + def _apply_desired_state(self, desired_state): + target_active = desired_state.get("target_active_power") + target_reactive = desired_state.get("target_reactive_power") + + sensor_type = "Environment" if self.is_environment_sensor else "Facility" + + if target_active is not None: + self.target_active_power = target_active + print(f"Desired state applied: {self.sensor_id} ({sensor_type}) - Target Active Power: {self.target_active_power}") + + if target_reactive is not None: + self.target_reactive_power = target_reactive + print(f"Desired state applied: {self.sensor_id} ({sensor_type}) - Target Reactive Power: {self.target_reactive_power}") + + if target_active is None and target_reactive is None: + print(f"No target power values provided for {self.sensor_id}.") \ No newline at end of file diff --git a/service/simulation/PressureSimulator.py b/service/simulation/PressureSimulator.py new file mode 100644 index 0000000..6e2606c --- /dev/null +++ b/service/simulation/PressureSimulator.py @@ -0,0 +1,87 @@ +from .SimulatorInterface2 import SimulatorInterface2 +import random +from service.simulatelogic.ContinuousSimulatorMixin import ContinuousSimulatorMixin + +class PressureSimulator(ContinuousSimulatorMixin,SimulatorInterface2): + # 타입별 시뮬레이터 세팅 + SENSOR_TYPE = "pressure" + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 35.74, 10.38 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 35.74, 10.38 # 설비센서: 평균 45도, 표준편차 15 (변동성 큼) + + ENV_LOWER, ENV_UPPER = 3.5, 80 + + FAC_LOWER, FAC_UPPER = 3.5, 80 + OUTLIER_P = 0.05 + + def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): + ######################################### + # 시뮬레이터에서 공통적으로 사용하는 속성 + ######################################### + super().__init__( + idx=idx, + zone_id=zone_id, + equip_id=equip_id, + interval=interval, + msg_count=msg_count, + conn=conn + ) + + ######################################### + # 시뮬레이터 마다 개별적으로 사용하는 속성(토픽, 수집 데이터 초기값) + ######################################### + self.sensor_id = f"UA10H-PRS-3406089{idx}" # 센서 ID + self.type = "pressure" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + + # shadow 등록용 토픽 + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" + # shadow 제어 명령 구독용 토픽 + self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" + # 센서 데이터 publish용 토픽 + self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) + self.target_temperature = None # 초기값 설정(shadow 용) + + ################################################z + # 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현) + # 예) 온도, 습도, 진동, 전류 등등 + ################################################ + def _generate_data(self) -> dict: + """ 데이터 생성 메서드 """ + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + + return { + "zoneId": self.zone_id, + "equipId": self.equip_id, + "sensorId": self.sensor_id, + "sensorType": self.type, + "val": sensor_value + } + + ################################################ + # 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정) + ################################################ + def _apply_desired_state(self, desired_state): + target_pressure = desired_state.get("target_pressure") + if target_pressure is not None: + self.target_pressure = target_pressure + print(f"Desired state applied: {self.sensor_id} - Target pressure: {self.target_pressure}") + else: + print(f"No target pressure provided for {self.sensor_id}.") + + \ No newline at end of file diff --git a/service/simulation/SimulatorInterface2.py b/service/simulation/SimulatorInterface2.py index 4e9ff82..04c0dfb 100644 --- a/service/simulation/SimulatorInterface2.py +++ b/service/simulation/SimulatorInterface2.py @@ -53,13 +53,36 @@ def _on_message_received(self, topic, payload, dup, qos, retain): def _publish_data(self): """ 센서 데이터를 MQTT로 publish 하는 메서드 """ - payload = json.dumps(self._generate_data()) - self.conn.publish( - topic=self.topic_name, - payload=payload, - qos=mqtt.QoS.AT_LEAST_ONCE - ) - print(f"Published data to {self.topic_name}: {payload}") + data = self._generate_data() + + # 리스트인 경우 (PowerSimulator처럼 여러 데이터 반환) + if isinstance(data, list): + for item in data: + # 각 데이터 항목에 sensorType에 따라 토픽 결정 + sensor_type = item.get("sensorType", "unknown") + if sensor_type == "active_power": + topic = getattr(self, 'active_topic', self.active_topic) + elif sensor_type == "reactive_power": + topic = getattr(self, 'reactive_topic', self.reactive_topic) + else: + topic = self.topic_name + + payload = json.dumps(item) + self.conn.publish( + topic=topic, + payload=payload, + qos=mqtt.QoS.AT_LEAST_ONCE + ) + print(f"Published data to {topic}: {payload}") + else: + # 단일 데이터인 경우 (기존 시뮬레이터들) + payload = json.dumps(data) + self.conn.publish( + topic=self.topic_name, + payload=payload, + qos=mqtt.QoS.AT_LEAST_ONCE + ) + print(f"Published data to {self.topic_name}: {payload}") def _subscribe_to_shadow_desired(self): """ Shadow의 desired를 구독 (제어 메세지 수신용) """ diff --git a/service/simulation/TempSimulator.py b/service/simulation/TempSimulator.py index ab869a5..6d9d1c3 100644 --- a/service/simulation/TempSimulator.py +++ b/service/simulation/TempSimulator.py @@ -5,8 +5,16 @@ class TempSimulator(ContinuousSimulatorMixin, SimulatorInterface2): # 정규분포 상속로직에 집어넣을 숫자들 SENSOR_TYPE = "temp" # 센서 타입 - MU, SIGMA = 25, 10 # 평균, 표준편차 - LOWER, UPPER = -35, 50 # 최소, 최대값 + + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 25, 3 # 환경센서: 평균 25도, 표준편차 3 (안정적) + + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 71, 10 # 설비센서: 평균 45도, 표준편차 15 (변동성 큼) + + ENV_LOWER, ENV_UPPER = -35, 50 # 최소, 최대값 + + FAC_LOWER, FAC_UPPER = 10, 150 # 최소, 최대값 OUTLIER_P = 0.05 # 이상치 확률(기본 5 %) def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): @@ -28,6 +36,20 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co self.sensor_id = f"UA10T-TEM-3406089{idx}" # 센서 ID self.type = "temp" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + # shadow 등록용 토픽 self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" @@ -39,19 +61,22 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) self.target_temperature = None # 초기값 설정(shadow 용) - ################################################z # 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현) # 예) 온도, 습도, 진동, 전류 등등 ################################################ def _generate_data(self) -> dict: """ 데이터 생성 메서드 """ + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - "val": self._generate_continuous_val() + "val": sensor_value } ################################################ diff --git a/service/simulation/VibrationSimulator.py b/service/simulation/VibrationSimulator.py index d8eadf6..d627b1b 100644 --- a/service/simulation/VibrationSimulator.py +++ b/service/simulation/VibrationSimulator.py @@ -4,11 +4,16 @@ class VibrationSimulator(ContinuousSimulatorMixin ,SimulatorInterface2): # 정규분포 상속로직에 집어넣을 숫자들 SENSOR_TYPE = "vibration" # 센서 타입 - # MU, SIGMA = 3.5, 2 # 평균, 표준편차 - MU, SIGMA = 2.0, 2.0 # 평균, 표준편차 - LOWER, UPPER = 0, 10 # 최소, 최대값 - OUTLIER_P = 0.06 # 이상치 확률(기본 6 %) - SMALL_SIGMA_RATIO = 0.25 # 정상 구간 변동폭을 σ의 10 %로 + # MU, SIGMA = 2.0, 2.0 # 평균, 표준편차 + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 2.0, 2.0 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 1.61, 0.73 + + ENV_LOWER, ENV_UPPER = 0, 10 # 최소, 최대값 + FAC_LOWER, FAC_UPPER = -0.5, 5 # min, max 참조 (소수점까지 커버) + OUTLIER_P = 0.05 + SMALL_SIGMA_RATIO = 0.15 def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): # 시뮬레이터에서 공통적으로 사용하는 속성 @@ -23,6 +28,21 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 시뮬레이터 마다 개별적으로 사용하는 속성(토픽, 수집 데이터 초기값) self.sensor_id = f"UA10V-VIB-2406089{idx}" # Sensor ID self.type = "vibration" # Sensor type + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) @@ -40,12 +60,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현) def _generate_data(self) -> dict: + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - "val": self._generate_continuous_val() + "val": sensor_value } diff --git a/service/simulation/VocSimulator.py b/service/simulation/VocSimulator.py index 1da0c11..112ca2f 100644 --- a/service/simulation/VocSimulator.py +++ b/service/simulation/VocSimulator.py @@ -4,8 +4,13 @@ class VocSimulator(ContinuousSimulatorMixin, SimulatorInterface2): # voc_simulator.py – 상단 상수 정의 부분 SENSOR_TYPE = "voc" - MU, SIGMA = 400, 250 # 중심을 안전 구간에 가깝게, σ를 줄임 - LOWER, UPPER = 0, 2000 # 실제 상한값을 넉넉히 확보 + # MU, SIGMA = 400, 250 # 중심을 안전 구간에 가깝게, σ를 줄임 + # 환경센서용 정규분포 파라미터 + ENV_MU, ENV_SIGMA = 400, 250 + # 설비센서용 정규분포 파라미터 + FAC_MU, FAC_SIGMA = 400, 250 + ENV_LOWER, ENV_UPPER = 0, 2000 # 실제 상한값을 넉넉히 확보 + FAC_LOWER, FAC_UPPER = 0, 2000 OUTLIER_P = 0.05 # 5% 확률로 위험 구간을 넘기기 위한 이상치 발생 def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None): @@ -21,6 +26,20 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 시뮬레이터 마다 개별적으로 사용하는 속성(토픽, 수집 데이터 초기값) self.sensor_id = f"UA10V-VOC-2406089{idx}" # 센서 ID self.type = "voc" # 센서 타입 + + # 환경센서 vs 설비센서 구분 및 파라미터 설정 + self.is_environment_sensor = (zone_id == equip_id) + if self.is_environment_sensor: + self.MU = self.ENV_MU + self.SIGMA = self.ENV_SIGMA + self.LOWER = self.ENV_LOWER + self.UPPER = self.ENV_UPPER + else: + self.MU = self.FAC_MU + self.SIGMA = self.FAC_SIGMA + self.LOWER = self.FAC_LOWER + self.UPPER = self.FAC_UPPER + self.shadow_regist_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update" self.shadow_desired_topic_name = f"$aws/things/Sensor/shadow/name/{self.sensor_id}/update/desired" self.topic_name = self._build_topic(zone_id, equip_id,self.sensor_id, self.type) @@ -28,12 +47,16 @@ def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_co # 데이터 생성 로직 정의 def _generate_data(self) -> dict: + + # ContinuousSimulatorMixin의 메서드를 오버라이드하여 센서별 파라미터 적용 + sensor_value = self._generate_continuous_val() + return { "zoneId": self.zone_id, "equipId": self.equip_id, "sensorId": self.sensor_id, "sensorType": self.type, - "val": self._generate_continuous_val() + "val": sensor_value } ################################################ diff --git a/service/simulation/factory.py b/service/simulation/factory.py index 0173a60..eb73441 100644 --- a/service/simulation/factory.py +++ b/service/simulation/factory.py @@ -5,6 +5,8 @@ from service.simulation.DustSimulator import DustSimulator from service.simulation.ExampleSimulator import ExampleSimulator from service.simulation.VocSimulator import VocSimulator +from service.simulation.PowerSimulator import PowerSimulator +from service.simulation.PressureSimulator import PressureSimulator from mqtt_util.publish import AwsMQTT from typing import List # from .SimulatorInterface import SimulatorInterface @@ -29,6 +31,10 @@ def get_simulator(simulator_type: str, idx: int, zone_id: str, equip_id: str, in simulator_entity_list.append(ExampleSimulator(i, zone_id, equip_id, interval, msg_count, conn)) elif simulator_type == "voc": simulator_entity_list.append(VocSimulator(i, zone_id, equip_id, interval, msg_count, conn)) + elif simulator_type == "power": + simulator_entity_list.append(PowerSimulator(i, zone_id, equip_id, interval, msg_count, conn)) + elif simulator_type == "pressure": + simulator_entity_list.append(PressureSimulator(i, zone_id, equip_id, interval, msg_count, conn)) else: raise ValueError(f"Unknown simulator type: {simulator_type}") return simulator_entity_list \ No newline at end of file diff --git a/simulation_cconfig.json b/simulation_cconfig.json index 164e8d6..0bebfbc 100644 --- a/simulation_cconfig.json +++ b/simulation_cconfig.json @@ -5,7 +5,7 @@ "interval": 5, "equip_id": "20250507165750-827", "zone_id": "20250507165750-827", - "simulator": "real_sensor", + "simulator": "temp", "sensor_num": 1 }, { @@ -13,8 +13,8 @@ "interval": 5, "equip_id": "20250507171316-389", "zone_id": "20250507165750-827", - "simulator": "vibration", + "simulator": "power", "sensor_num": 1 - } + } ] } \ No newline at end of file diff --git a/streamlit_app/app.py b/streamlit_app/app.py index 5afe456..f98a802 100644 --- a/streamlit_app/app.py +++ b/streamlit_app/app.py @@ -166,7 +166,9 @@ def main(): icon = ("🌡️" if simulator_type == "temp" else "💧" if simulator_type == "humidity" else "📳" if simulator_type == "vibration" else + "⚡⚡" if simulator_type == "power" else "⚡" if simulator_type == "current" else + "⚙️" if simulator_type == "pressure" else "💨" if simulator_type == "dust" else "🌫️" if simulator_type == "voc" else "🔌" if simulator_type == "real_sensor" else @@ -191,7 +193,7 @@ def main(): st.caption("※ 설비 정보 == 공간 정보시 환경 센서로 인식합니다. (다를 시 설비 센서)") # device["simulator"] = st.text_input(f"Simulator (Device {i + 1})", value=device["simulator"], key=f"simulator_{i}") #드랍다운 선택 형식으로 시뮬레이터 적용 - simulator_options = ["temp", "humidity", "vibration", "current", "dust", "voc", "real_sensor"] + simulator_options = ["temp", "humidity", "vibration","power", "current", "pressure" , "dust", "voc", "real_sensor"] device["simulator"] = st.selectbox( f"Simulator (Device {i + 1}) - 시뮬레이터 타입 선택", options=simulator_options,