Skip to content

Commit b4528ae

Browse files
authored
Merge pull request #17 from Fac2Real/feature/FRB-171
feature | sprint2 | FRB-171 | 인터벌 문제 수정 시뮬레이터 선택 드랍다운 적용 | 정민석
2 parents ff8d0dc + ea1880e commit b4528ae

4 files changed

Lines changed: 89 additions & 44 deletions

File tree

.github/workflows/docker-build-develop.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
pull_request:
55
branches:
66
- main
7+
- develop
78

89
jobs:
910
test-and-deploy:
@@ -39,11 +40,22 @@ jobs:
3940
id: login-ecr
4041
uses: aws-actions/amazon-ecr-login@v1
4142

43+
- name: Set IMAGE_TAG based on branch
44+
id: set-tag
45+
run: |
46+
if [[ "${{ github.base_ref }}" == "main" ]]; then
47+
echo "tag=prod-latest" >> $GITHUB_OUTPUT
48+
elif [[ "${{ github.base_ref }}" == "develop" ]]; then
49+
echo "tag=dev-latest" >> $GITHUB_OUTPUT
50+
else
51+
echo "tag=unknown" >> $GITHUB_OUTPUT
52+
fi
53+
4254
- name: Build, tag, and push image to ECR
4355
env:
4456
ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
4557
ECR_REPOSITORY: streamlit-app
46-
IMAGE_TAG: streamlit-latest
58+
IMAGE_TAG: ${{ steps.set-tag.outputs.tag }}
4759
run: |
4860
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
49-
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
61+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

service/simulation/SimulatorInterface2.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22
from awscrt import mqtt
33
import json
44
import threading
5+
from threading import Event
56
from mqtt_util.publish import AwsMQTT
67
import time
78
from scipy.stats import truncnorm
89

910
class SimulatorInterface2(ABC):
10-
def __init__(self, idx: int, zone_id: str, equip_id: str, interval: int, msg_count: int, conn:AwsMQTT=None): # 센서 idx를 받기
11+
def __init__(self, idx: int, zone_id: str, equip_id: str, interval: int, msg_count: int, conn:AwsMQTT=None, stop_event: Event = None): # 센서 idx를 받기
1112
self.idx = idx # 센서 번호
1213
self.zone_id = zone_id # 공간 ID
1314
self.equip_id = equip_id # 설비 ID
1415
self.interval = interval # publish 주기
1516
self.msg_count = msg_count # publish 횟수
1617
self.conn = conn # 시뮬레이터 별로 생성된 MQTT 연결 객체를 singleton으로 사용하기 위함
1718
self.thead = None # 스레드 객체
18-
self.stop_event = threading.Event() # 스레드 종료 이벤트 객체
19+
# stop_event가 None이면 새 Event 객체 생성
20+
self.stop_event = stop_event if stop_event is not None else Event()
1921
##########################################################
2022
# @abstractmethod 시뮬레이터 마다 로직이 달라 구현해야되는 메서드
2123
##########################################################
@@ -160,6 +162,37 @@ def publish_value(self, sensor_type: str, value: float):
160162
self.conn.publish(topic, payload, mqtt.QoS.AT_LEAST_ONCE)
161163
print(f"Published data to {topic}: {payload}, {threading.current_thread().name}")
162164
def stop_publishing(self):
163-
"""시뮬레이터 중지"""
164-
self.stop_event.set()
165-
print(f"[{self.__class__.__name__}] Stopping publishing...")
165+
# """시뮬레이터 중지"""
166+
# self.stop_event.set()
167+
# print(f"[{self.__class__.__name__}] Stopping publishing...")
168+
"""시뮬레이터 중지 및 리소스 정리"""
169+
if hasattr(self, 'stop_event') and self.stop_event:
170+
print(f"[{self.__class__.__name__}] Setting stop event...")
171+
self.stop_event.set() # 중지 이벤트 설정
172+
173+
# 스레드가 있고 살아있다면 종료 대기 (최대 3초)
174+
if hasattr(self, 'thread') and self.thread and self.thread.is_alive():
175+
print(f"[{self.__class__.__name__}] Waiting for thread to terminate...")
176+
self.thread.join(timeout=3)
177+
178+
# 여전히 살아있다면 경고
179+
if self.thread.is_alive():
180+
print(f"Warning: Thread for {self.__class__.__name__} could not be terminated normally")
181+
182+
# shadow 상태 업데이트 - 센서 OFF 상태 알림
183+
try:
184+
self._update_shadow(status="OFF")
185+
print(f"[{self.__class__.__name__}] Shadow updated to OFF state")
186+
except Exception as e:
187+
print(f"[{self.__class__.__name__}] Error updating shadow: {e}")
188+
189+
# 추가 리소스 정리
190+
self._cleanup_resources()
191+
192+
print(f"[{self.__class__.__name__}] Successfully stopped")
193+
else:
194+
print(f"[{self.__class__.__name__}] No stop_event available")
195+
196+
def _cleanup_resources(self):
197+
"""자식 클래스에서 오버라이드하여 추가 리소스 정리 가능"""
198+
pass

simulation_cconfig.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22
"devices": [
33
{
44
"count": 5,
5-
"interval": 5,
5+
"interval": 3,
66
"equip_id": "20250507171316-389",
77
"zone_id": "20250507165750-827",
88
"simulator": "real_sensor",
99
"sensor_num": 1
1010
},
1111
{
1212
"count": 5,
13-
"interval": 5,
13+
"interval": 3,
1414
"equip_id": "20250507171316-389",
1515
"zone_id": "20250507165750-827",
16-
"simulator": "current",
16+
"simulator": "vibration",
1717
"sensor_num": 1
18-
}
18+
}
1919
]
2020
}

streamlit_app/app.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sqlite3
66
import threading
77
import time
8-
8+
from threading import Event
99
# 프로젝트 루트 디렉터리를 sys.path에 추가
1010
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
1111

@@ -76,37 +76,26 @@ def load_from_db():
7676
return {"devices": [dict(zip(["count", "interval", "equip_id", "zone_id", "simulator", "sensor_num"], row)) for row in rows]}
7777

7878
# Function to run simulation with stop functionality
79-
def run_simulation_with_stop(simulator_type, count, interval, sensor_num, zone_id, equip_id, stop_event):
80-
# RealSensor와 가상 시뮬레이터를 구분하여 처리
81-
if simulator_type == "real_sensor":
82-
# RealSensor 모드: 쓰레드를 실행하고 포트 해제를 위해 join() 사용
83-
threads = run_simulator_from_streamlit(
84-
simulator_type, count, interval,
85-
sensor_num, zone_id, equip_id,
86-
stop_event # stop_event 전달 중요!
87-
)
88-
89-
# join() 호출 제거 - 쓰레드가 백그라운드에서 실행되도록 함
90-
print(f"RealSensor threads started in background. Count: {len(threads or [])}")
79+
def run_simulation_with_stop(simulator_type, count, interval, sensor_num, zone_id, equip_id, stop_event: Event):
80+
# for _ in range(count):
81+
# if stop_event.is_set(): # Stop 이벤트가 설정되었는지 확인
82+
# print(f"Stopping simulation for {simulator_type}")
83+
# break
84+
# # run_simulator_from_streamlit(simulator_type, count, interval, sensor_num, zone_id, equip_id)
85+
# time.sleep(interval) # 시뮬레이션 간격
9186

92-
# 선택사항: 쓰레드 정리를 위한 참조 저장
93-
# 현재의 코드에서는 이미 simulation_threads에 참조가 저장되므로 추가 작업 필요 없음
94-
else:
95-
# 가상 시뮬레이터 모드: 기존에 잘 작동하던 방식 사용
96-
for _ in range(count):
97-
if stop_event.is_set(): # Stop 이벤트가 설정되었는지 확인
98-
print(f"Stopping simulation for {simulator_type}")
99-
break
100-
101-
# 시뮬레이터 한 번 실행
102-
run_simulator_from_streamlit(
103-
simulator_type, 1, interval, # count=1로 한 번만 실행
104-
sensor_num, zone_id, equip_id,
105-
stop_event
106-
)
107-
108-
time.sleep(interval) # 시뮬레이션 간격
109-
87+
88+
# ① 시뮬레이터(혹은 RealSensor) 실행 → 쓰레드 리스트 반환
89+
threads = run_simulator_from_streamlit(
90+
simulator_type, count, interval,
91+
sensor_num, zone_id, equip_id,stop_event)
92+
93+
# ② 반환된 쓰레드가 있으면 모두 종료될 때까지 기다렸다가 포트 해제
94+
for th in threads or []: # None 방어
95+
if th is not None:
96+
th.join() # ← 여기서 blocking, ser.close() 까지 완료
97+
98+
11099
# Streamlit app
111100
def main():
112101
st.title("Simulation Configuration Manager")
@@ -135,7 +124,19 @@ def main():
135124
device["interval"] = st.number_input(f"Interval (Device {i + 1})", value=device["interval"], key=f"interval_{i}")
136125
device["equip_id"] = st.text_input(f"Manufacture ID (Device {i + 1})", value=device["equip_id"], key=f"equip_id_{i}")
137126
device["zone_id"] = st.text_input(f"Space ID (Device {i + 1})", value=device["zone_id"], key=f"zone_id_{i}")
138-
device["simulator"] = st.text_input(f"Simulator (Device {i + 1})", value=device["simulator"], key=f"simulator_{i}")
127+
# device["simulator"] = st.text_input(f"Simulator (Device {i + 1})", value=device["simulator"], key=f"simulator_{i}")
128+
#드랍다운 선택 형식으로 시뮬레이터 적용
129+
simulator_options = ["temp", "humidity", "vibration", "current", "dust", "voc", "real_sensor"]
130+
device["simulator"] = st.selectbox(
131+
f"Simulator (Device {i + 1})",
132+
options=simulator_options,
133+
index=simulator_options.index(device["simulator"]) if device["simulator"] in simulator_options else 0,
134+
key=f"simulator_{i}"
135+
)
136+
# real_sensor가 선택된 경우 경고 메시지 표시
137+
if device["simulator"] == "real_sensor":
138+
st.warning("⚠️ 'real_sensor'는 로컬 환경에서만 사용 가능하며, 센서를 USB 포트(COM3)에 연결해야 합니다.")
139+
# 센서 갯수 입력
139140
device["sensor_num"] = st.number_input(f"Sensor Num (Device {i + 1})", value=device["sensor_num"], key=f"sensor_num_{i}")
140141

141142
# Run Simulation Button
@@ -198,7 +199,6 @@ def main():
198199
save_to_db(st.session_state.data)
199200
st.success("Saved data to SQLite.")
200201

201-
202202
if __name__ == "__main__":
203203
init_db()
204204
main()

0 commit comments

Comments
 (0)