Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bbc27a6
fix | sprint1 | FRB-107 | 시뮬레이터 인터페이스 임포트 오류 수정 | 정민석
minnnseokk Apr 29, 2025
8490281
feat | sprint1 | FRB-101 | 시뮬레이터 기능 추가, 데이터 형식 최종 정의 | 정민석
minnnseokk Apr 29, 2025
ed7487f
feat | sprint1 | FRB-101 | 온습도 시뮬레이터 추가 | 김우영
gwangbu-desu Apr 29, 2025
20d162c
feat | sprint1 | FRB-101 | 온습도 시뮬레이터 추가 | 김우영
gwangbu-desu Apr 29, 2025
f9e3e79
feat | sprint1 | FRB-101 | VOC 시뮬레이터 추가 | 김우영
gwangbu-desu Apr 29, 2025
10c2c82
feat | sprint1 | FRB-121 | 센서 스레드 형식, json 파일로 분리 | 정민석
minnnseokk Apr 29, 2025
d9ef476
feat | sprint1 | FRB-121 | FRB-101 작업 내용 머지지 | 김우영
gwangbu-desu Apr 30, 2025
ced015f
fix | sprint1 | FRB-121 | json 상대 경로로 변경 | 김우영
gwangbu-desu Apr 30, 2025
239f62f
fix | sprint1 | FRB-121 | 시뮬레이터 작동 에러 수정 | 정민석
minnnseokk May 5, 2025
72d46b2
Merge pull request #5 from Fac2Real/feature/FRB-121
gwangbu-desu May 5, 2025
a46058f
fix | sprint1 | FRB-121 | 시뮬레이터 속성명 수정 (spaceId -> zoneId / manufactu…
gwangbu-desu May 7, 2025
9582756
fix | sprint1 | FRB-121 | spaceId 한글 허용 | 정민석석
minnnseokk May 7, 2025
6bb06f7
Merge branch 'feature/FRB-121' of https://github.com/Fac2Real/monitor…
minnnseokk May 7, 2025
46e27f2
fix | sprint1 | FRB-121 | Shadow payload의 속성명 수정 | 김우영영
gwangbu-desu May 7, 2025
02b8fd2
fix |
gwangbu-desu May 7, 2025
61733cb
Merge branch 'feature/FRB-121' of https://github.com/Fac2Real/monitor…
gwangbu-desu May 7, 2025
0a6001f
fix | sprint1 | FRB-121 | Shadow payload의 속성명 수정 | 김우영
gwangbu-desu May 7, 2025
d954d62
Merge branch 'feature/FRB-121' of https://github.com/Fac2Real/monitor…
gwangbu-desu May 7, 2025
8aa2de9
Merge pull request #6 from Fac2Real/feature/FRB-121
gwangbu-desu May 12, 2025
4a3850c
feat | sprint2 | FRB-148 | streamlit 기반 시뮬레이터 UI 추가 | 김우영
gwangbu-desu May 13, 2025
29eebaf
Merge branch 'develop' of https://github.com/Fac2Real/monitory-iot in…
gwangbu-desu May 13, 2025
3707434
fix | sprint1 | FRB-32 | 시뮬레이터 데이터 랜덤 생성 로직 수정 | 김우영
gwangbu-desu May 13, 2025
18adb2b
chore | 없음 | 없음 | ecr 이미지 반영 | 김우영
gwangbu-desu May 15, 2025
e6c45d3
chore | 없음 | 없음 | 시뮬레이터 설정 경로 변경 | 김우영영
gwangbu-desu May 15, 2025
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Ignore Python cache files
*.pyc
__pycache__/

# Ignore environment files
.env

# Ignore virtual environments
venv/
.venv/

# Ignore other unnecessary files
*.log
*.tmp
49 changes: 49 additions & 0 deletions .github/workflows/docker-build-develop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Test and Build Python Image to ECR

on:
pull_request:
branches:
- main

jobs:
test-and-deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Run Streamlit sanity check
run: |
streamlit --version
python -m pytest || echo "Tests not configured, skipping..."

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: Build, tag, and push image to ECR
env:
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
ECR_REPOSITORY: streamlit-app
IMAGE_TAG: streamlit-latest
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Base image
FROM python:3.13.3

# Set the working directory
WORKDIR /app

# Copy requirements file
COPY requirements.txt .

# Install dependencies
RUN pip install wheel setuptools
RUN python -m pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application code
COPY . .

# Expose the port Streamlit runs on
EXPOSE 8501

# Command to run the Streamlit app
CMD ["streamlit", "run", "streamlit_app/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
Binary file modified requirements.txt
Binary file not shown.
58 changes: 54 additions & 4 deletions service/simulation/CurrentSimulator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,56 @@
from .SimulatorInterface import SimulatorInterface
from .SimulatorInterface2 import SimulatorInterface2
from simulate_type.simulate_list import generate_current_data
from scipy.stats import truncnorm

class CurrentSimulator(SimulatorInterface):
def generate_data(self, idx: int) -> dict:
return generate_current_data(idx)
class CurrentSimulator(SimulatorInterface2):
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"UA10C-CUR-2406089{idx}" # 센서 ID
self.type = "current" # 센서 타입
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 = f"sensor/{zone_id}/{equip_id}/{self.sensor_id}/{self.type}"
self.target_current = None # 초기값 설정(shadow 용)
self.mu = 62.51
self.sigma = 33.76
lower = 0
upper = self.mu + 3 * self.sigma
self.a = (lower - self.mu) / self.sigma
self.b = (upper - self.mu) / self.sigma


# 데이터 생성 로직 정의
def _generate_data(self) -> dict:
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": round(truncnorm.rvs(self.a, self.b, loc=self.mu, scale=self.sigma), 2) # 0: 7, 1: 7이상, 2: 30 이상 최소값은 0
}

################################################
# 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정)
# sprint 2 에서 더 구체화 예정
################################################
def _apply_desired_state(self, desired_state):
"""
Shadow의 desired 상태를 받아서 센서에 적용
예) {"target_Vibration": 25.0} 이런 명령을 받아 적용
"""
target_current = desired_state.get("target_current")
if target_current is not None:
self.target_current = target_current
print(f"Desired state applied: {self.sensor_id} - Target Current: {self.target_current}")
else:
print(f"No target current provided for {self.sensor_id}.")
56 changes: 56 additions & 0 deletions service/simulation/DustSimulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from .SimulatorInterface2 import SimulatorInterface2
import random
from scipy.stats import truncnorm

class DustSimulator(SimulatorInterface2):
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"UA10D-DST-2406089{idx}" # 센서 ID
self.type = "dust" # 센서 타입
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 = f"sensor/{zone_id}/{equip_id}/{self.sensor_id}/{self.type}"
self.target_current = None # 초기값 설정(shadow 용)

self.mu = 180 # 평균 미세먼지 수치
self.sigma = 60 # 표준편차
self.lower = 0
self.upper = self.mu + 3 * self.sigma

self.a = (self.lower - self.mu) / self.sigma
self.b = (self.upper - self.mu) / self.sigma

# 데이터 생성 로직 정의
def _generate_data(self) -> dict:
return {
"zoneId": self.zone_id,
"equipId": self.equip_id,
"sensorId": self.sensor_id,
"sensorType": self.type,
"val": round(truncnorm.rvs(self.a, self.b, loc=self.mu, scale=self.sigma), 2)
}

################################################
# 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정)
# sprint 2 에서 더 구체화 예정
################################################
def _apply_desired_state(self, desired_state):
"""
Shadow의 desired 상태를 받아서 센서에 적용
예) {"target_Vibration": 25.0} 이런 명령을 받아 적용
"""
target_current = desired_state.get("target_current")
if target_current is not None:
self.target_current = target_current
print(f"Desired state applied: {self.sensor_id} - Target Current: {self.target_current}")
else:
print(f"No target current provided for {self.sensor_id}.")
20 changes: 11 additions & 9 deletions service/simulation/ExampleSimulator.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
from .SimulationInterface2 import SimulatorInterface2
from .SimulatorInterface2 import SimulatorInterface2
from simulate_type.simulate_list import generate_temp_data
import random

class ExampleSimulator(SimulatorInterface2):
def __init__(self, idx: int, space_id:str, manufacture_id:str, interval:int = 5, msg_count:int = 10, conn=None):
def __init__(self, idx: int, zone_id:str, equip_id:str, interval:int = 5, msg_count:int = 10, conn=None):
#########################################
# 시뮬레이터에서 공통적으로 사용하는 속성
#########################################
super().__init__(
idx=idx,
space_id=space_id,
manufacture_id=manufacture_id,
zone_id=zone_id,
equip_id=equip_id,
interval=interval,
msg_count=msg_count,
conn=conn
Expand All @@ -29,7 +29,7 @@ def __init__(self, idx: int, space_id:str, manufacture_id:str, interval:int = 5,
self.shadow_desired_topic_name = f"$aws/things/KWYTEST/shadow/name/{self.sensor_id}/update/desired"

# 센서 데이터 publish용 토픽
self.topic_name = f"{space_id}/{manufacture_id}/{self.sensor_id}/{self.type}"
self.topic_name = f"sensor/{zone_id}/{equip_id}/{self.sensor_id}/{self.type}"

self.target_temperature = None # 초기값 설정(shadow 용)

Expand All @@ -38,16 +38,18 @@ def __init__(self, idx: int, space_id:str, manufacture_id:str, interval:int = 5,
# 예) 온도, 습도, 진동, 전류 등등
################################################
def _generate_data(self) -> dict:
""" 데이터 생성 메서드 """
return {
"id": self.sensor_id,
"type": self.type,
"temperature": round(random.uniform(20.0 + self.idx, 30.0 + self.idx), 2)
"zoneId": self.zone_id,
"equipId": self.equip_id,
"sensorId": self.sensor_id,
"sensorType": self.type,
"val": round(random.uniform(20.0 + self.idx, 30.0 + self.idx), 2)
}


################################################
# 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정)
# sprint 2 에서 더 구체화 예정
################################################
def _apply_desired_state(self, desired_state):
"""
Expand Down
64 changes: 59 additions & 5 deletions service/simulation/HumiditySimulator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,60 @@
from .SimulatorInterface import SimulatorInterface
from simulate_type.simulate_list import generate_humidity_data
from .SimulatorInterface2 import SimulatorInterface2
import random

class HumiditySimulator(SimulatorInterface):
def generate_data(self, idx: int) -> dict:
return generate_humidity_data(idx)
class HumiditySimulator(SimulatorInterface2):
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-HUM-2406089{idx}" # 센서 ID
self.type = "humid" # 센서 타입
# 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 = f"sensor/{zone_id}/{equip_id}/{self.sensor_id}/{self.type}"
self.target_temperature = None # 초기값 설정(shadow 용)

################################################z
# 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현)
# 예) 온도, 습도, 진동, 전류 등등
################################################
def _generate_data(self) -> dict:
""" 데이터 생성 메서드 """
return {
"zoneId": self.zone_id,
"equipId": self.equip_id,
"sensorId": self.sensor_id,
"sensorType": self.type,
"val": round(random.gauss(mu = 11.68, sigma = 29.38), 2) # 0: 60이하,1: 60초과, 2: 80 초과과
}

################################################
# 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정)
################################################
def _apply_desired_state(self, desired_state):
"""
Shadow의 desired 상태를 받아서 센서에 적용
예) {"target_humidity": 25.0} 이런 명령을 받아 적용
"""
target_humidity = desired_state.get("target_humidity")
if target_humidity is not None:
self.target_humidity = target_humidity
print(f"Desired state applied: {self.sensor_id} - Target humidity: {self.target_humidity}")
else:
print(f"No target humidity provided for {self.sensor_id}.")


64 changes: 57 additions & 7 deletions service/simulation/HumidityTempSimulator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,59 @@
from .SimulatorInterface import SimulatorInterface
from simulate_type.simulate_list import generate_humidity_temp_data
from .SimulatorInterface2 import SimulatorInterface2
import random

class HumidityTempSimulator(SimulatorInterface):
def generate_data(self, idx: int) -> dict:
class humidTempSimulator(SimulatorInterface2):
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-CHS-2406089{idx}" # 센서 ID
self.type = "temp_humid" # 센서 타입
# shadow 등록용 토픽
self.shadow_regist_topic_name = f"$aws/things/KWYTEST/shadow/name/{self.sensor_id}/update"
# shadow 제어 명령 구독용 토픽
self.shadow_desired_topic_name = f"$aws/things/KWYTEST/shadow/name/{self.sensor_id}/update/desired"
# 센서 데이터 publish용 토픽
self.topic_name = f"sensor/{zone_id}/{equip_id}/{self.sensor_id}/{self.type}"
self.target_temperature = None # 초기값 설정(shadow 용)

################################################
# 데이터 생성 로직을 정의 (시뮬레이터 마다 다르게 구현)
# 예) 온도, 습도, 진동, 전류 등등
################################################
def _generate_data(self) -> dict:
""" 데이터 생성 메서드 """
return {
"id": self.sensor_id,
"type": self.type,
"temperature": round(random.uniform(20.0 + self.idx, 30.0 + self.idx), 2)
}


################################################
# 제어 로직을 정의 ( shadow의 desired 상태를 구독하여 제어하는 로직을 구현할 예정)
################################################
def _apply_desired_state(self, desired_state):
"""
Shadow의 desired 상태를 받아서 센서에 적용
예) {"target_temperature": 25.0} 이런 명령을 받아 적용
"""
Generate a single data entry containing both humidity and temperature.
"""
return generate_humidity_temp_data(idx)
target_temperature = desired_state.get("target_temperature")
if target_temperature is not None:
self.target_temperature = target_temperature
print(f"Desired state applied: {self.sensor_id} - Target Temperature: {self.target_temperature}")
else:
print(f"No target temperature provided for {self.sensor_id}.")


Loading
Loading