Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion service/simulatelogic/ContinuousSimulatorMixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
61 changes: 61 additions & 0 deletions service/simulatelogic/PowerContinuousSimulatorMixin.py
Original file line number Diff line number Diff line change
@@ -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
33 changes: 28 additions & 5 deletions service/simulation/CurrentSimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -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
}

################################################
Expand Down
23 changes: 21 additions & 2 deletions service/simulation/DustSimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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
}

################################################
Expand Down
32 changes: 28 additions & 4 deletions service/simulation/HumiditySimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
#########################################
Expand All @@ -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 제어 명령 구독용 토픽
Expand All @@ -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
}

################################################
Expand Down
107 changes: 107 additions & 0 deletions service/simulation/PowerSimulator.py
Original file line number Diff line number Diff line change
@@ -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}.")
Loading
Loading