From 7c2cb3877a0cc1d50e2df61f1053f219fee18698 Mon Sep 17 00:00:00 2001 From: minnnseokk Date: Fri, 23 May 2025 15:30:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feature=20|=20sprint2=20|=20FRB-171=20|=20S?= =?UTF-8?q?QL=20lite=20=ED=8C=8C=EC=9D=BC=20=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?|=20=EC=A0=95=EB=AF=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simulation_config.db | Bin 12288 -> 12288 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/simulation_config.db b/simulation_config.db index d419dc72355c8a7c12a1dfffdc70838820ca1e63..03d8170d637be1754ded661115bcdbd5074decca 100644 GIT binary patch delta 234 zcmZojXh@hK&B!!S##xYwL9b1dmw|zSiEjo2-z&Zun*{|l_$E)}3+5DNWEYo~W^AsV z{FZM%M`~edW4FS(-5=wJb9^wU`M2ADuWO delta 100 zcmZojXh@hK&B!=W##xY&L9b1dmw|zSiEknU-($Xs8w(ZrCePpt=9FS&7nhc1Y~h~# tg>ODrZem_(T4HiZY0>13{1VIsiOE1Zn2AAPv!KEw{)r6|Y#>7rTmU+88(jbZ From 63fe0775df576b4576c041ffbaa1d1eda1533fd8 Mon Sep 17 00:00:00 2001 From: minnnseokk Date: Fri, 23 May 2025 15:33:28 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feature=20|=20sprint2=20|=20FRB-171=20|=20M?= =?UTF-8?q?anufacture=5Fid=20->=20Equip=5Fid=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20|=20=EC=A0=95=EB=AF=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- streamlit_app/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streamlit_app/app.py b/streamlit_app/app.py index 4c7b0eb..071cdff 100644 --- a/streamlit_app/app.py +++ b/streamlit_app/app.py @@ -122,7 +122,7 @@ def main(): st.subheader(f"Device {i + 1} Details") device["count"] = st.number_input(f"Count (Device {i + 1})", value=device["count"], key=f"count_{i}") device["interval"] = st.number_input(f"Interval (Device {i + 1})", value=device["interval"], key=f"interval_{i}") - device["equip_id"] = st.text_input(f"Manufacture ID (Device {i + 1})", value=device["equip_id"], key=f"equip_id_{i}") + device["equip_id"] = st.text_input(f"Equip ID (Device {i + 1})", value=device["equip_id"], key=f"equip_id_{i}") device["zone_id"] = st.text_input(f"Space ID (Device {i + 1})", value=device["zone_id"], key=f"zone_id_{i}") # device["simulator"] = st.text_input(f"Simulator (Device {i + 1})", value=device["simulator"], key=f"simulator_{i}") #드랍다운 선택 형식으로 시뮬레이터 적용 From cc1b03006729281e23bb9aa027095d7a2e5cd963 Mon Sep 17 00:00:00 2001 From: minnnseokk Date: Fri, 23 May 2025 20:47:36 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feature=20|=20sprint2=20|=20FRB-171=20|=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=EB=B2=8C=20=EC=A0=88=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20|=20=EC=A0=95=EB=AF=BC=EC=84=9D=EB=AF=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- service/simulation/SimulatorInterface2.py | 29 ++++++++++++++++++----- service/simulation/simulateTest.py | 24 ++++++++++++++++++- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/service/simulation/SimulatorInterface2.py b/service/simulation/SimulatorInterface2.py index 244b288..4e9ff82 100644 --- a/service/simulation/SimulatorInterface2.py +++ b/service/simulation/SimulatorInterface2.py @@ -96,14 +96,31 @@ def _publish_loop(self): self._update_shadow(status="ON") # 초기 상태를 ON으로 설정 try: + next_publish_time = time.time() # 초기 시작 시간 + for _ in range(self.msg_count): - try: + if self.stop_event.is_set(): + break + + # 데이터 발행 + self._publish_data() + + # 다음 발행 시간 계산 + next_publish_time += self.interval + + # 다음 발행 시간까지 남은 시간 계산 + wait_time = next_publish_time - time.time() + + # 지연이 발생했다면 기다리지 않고 즉시 다음 작업 진행 + if wait_time <= 0: + print(f"Warning: Publishing is behind schedule by {-wait_time:.3f} seconds") + continue + + # 매우 짧은 간격으로 stop_event 확인하면서 대기 + while time.time() < next_publish_time: if self.stop_event.is_set(): - break - self._publish_data() - time.sleep(self.interval) - except Exception as e: - print(f"Error in publish loop: {e}") + return # 즉시 종료 + time.sleep(0.01) # 매우 짧은 sleep (거의 폴링) finally: self._update_shadow(status="OFF") ######################################################################################## diff --git a/service/simulation/simulateTest.py b/service/simulation/simulateTest.py index 40635c2..6ba2ff7 100644 --- a/service/simulation/simulateTest.py +++ b/service/simulation/simulateTest.py @@ -59,6 +59,9 @@ def run_simulator_from_streamlit(simulator_type, count, interval, sensor_num, zo return threads # 새로운 함수: stop_event가 설정된 상태에서 시뮬레이터 실행 def run_simulator_with_stop(simulator, count, interval, stop_event=None): + # 절대 시간 기준 시작점 설정 + next_run_time = time.time() + for i in range(count): if stop_event and stop_event.is_set(): print(f"[Simulator] Stopping due to stop_event") @@ -66,7 +69,26 @@ def run_simulator_with_stop(simulator, count, interval, stop_event=None): data = simulator.start_publishing() print(f"[{time.strftime('%H:%M:%S')}] Publishing data: {json.dumps(data)}") - time.sleep(interval) # 실제 interval 간격으로 실행 + # time.sleep(interval) # 실제 interval 간격으로 실행 + + # 다음 실행 시간 계산 + next_run_time += interval + + # 대기 시간 계산 + wait_time = next_run_time - time.time() + + # 지연 발생 시 경고 출력 + if wait_time < 0: + print(f"[Warning] Behind schedule by {-wait_time:.3f}s at iteration {i+1}/{count}") + continue # 지연됐으면 기다리지 않고 다음 반복으로 + + # 짧은 간격으로 나누어 stop_event 지속적 확인 + end_time = time.time() + wait_time + while time.time() < end_time: + if stop_event and stop_event.is_set(): + print(f"[Simulator] Stopping during wait time") + return # 즉시 함수 종료 + time.sleep(0.05) # 50ms 간격으로 확인 (조정 가능) # 시뮬레이션 함수 def run_simulation_from_json(json_file_path): From 5b6577212a36df7da01e1ad5f0a5a94073b9fcd5 Mon Sep 17 00:00:00 2001 From: minnnseokk Date: Fri, 23 May 2025 21:59:31 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feature=20|=20sprint2=20|=20FRB-171=20|=20?= =?UTF-8?q?=EC=8B=9C=EB=AE=AC=EB=A0=88=EC=9D=B4=ED=84=B0=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=EC=84=B1=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20|=20=EC=A0=95=EB=AF=BC=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- streamlit_app/app.py | 87 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 11 deletions(-) diff --git a/streamlit_app/app.py b/streamlit_app/app.py index 071cdff..e2aabd7 100644 --- a/streamlit_app/app.py +++ b/streamlit_app/app.py @@ -105,30 +105,89 @@ def main(): st.session_state.data = load_json() # Sidebar options to load data - if st.sidebar.button("Load from JSON"): + st.sidebar.header("Load Options") + if st.sidebar.button("Load from JSON - JSON 파일에서 센서 데이터 불러오기"): st.session_state.data = load_json() st.success("Loaded data from JSON.") st.rerun() - elif st.sidebar.button("Load from SQLite"): + elif st.sidebar.button("Load from SQLite - DB에서 센서 데이터 불러오기"): st.session_state.data = load_from_db() st.success("Loaded data from SQLite.") st.rerun() + + # 모든 시뮬레이션 동시 실행/중지 섹션 추가 + + st.header("Batch Operations") + col1, col2 = st.columns(2) + + with col1: + if st.button("Run All Simulators - 모든 센서 동시 실행", use_container_width=True): + if not st.session_state.data.get("devices"): + st.warning("No devices found. Please add devices first.") + else: + # 모든 센서 동시에 시작 + for i, device in enumerate(st.session_state.data["devices"]): + if i not in simulation_threads or not simulation_threads[i].is_alive(): + stop_events[i] = threading.Event() + thread = threading.Thread(target=run_simulation_with_stop, args=( + device["simulator"], + device["count"], + device["interval"], + device["sensor_num"], + device["zone_id"], + device["equip_id"], + stop_events[i] + )) + simulation_threads[i] = thread + thread.start() + st.success(f"Started simulations for all {len(st.session_state.data['devices'])} devices.") + + # 시뮬레이션 상태 대시보드 (시각적으로 개선) + st.subheader("💻 Simulation Status Dashboard") + + # 실행 중인 디바이스 수 표시 + active_count = sum(1 for t in simulation_threads.values() if t and t.is_alive()) + total_count = len(st.session_state.data["devices"]) if "devices" in st.session_state.data else 0 + + # 전체 진행 상태 표시 + st.markdown(f"**활성 시뮬레이션**: {active_count}/{total_count} 디바이스 실행 중") + + # 각 디바이스 상태를 시각적 요소로 표시 + if "devices" in st.session_state.data and st.session_state.data["devices"]: + # 디바이스 상태 그리드 레이아웃 (3열) + cols = st.columns(3) + for i, device in enumerate(st.session_state.data["devices"]): + with cols[i % 3]: + is_running = i in simulation_threads and simulation_threads[i].is_alive() + + # 디바이스 타입에 따라 아이콘 선택 + simulator_type = device["simulator"] + icon = "🌡️" if simulator_type == "temp" else "💧" if simulator_type == "humidity" else "📊" + + # 상태에 따른 색상 및 프로그레스 바 + if is_running: + st.markdown(f"**{icon} Device {i+1}**: 🟢 Running") + st.progress(100) # 실행 중인 경우 100% 표시 + else: + st.markdown(f"**{icon} Device {i+1}**: ⚪ Idle") + st.progress(0) # 유휴 상태인 경우 0% 표시 # Display devices in blocks st.header("Devices") if "devices" in st.session_state.data and st.session_state.data["devices"]: for i, device in enumerate(st.session_state.data["devices"]): with st.expander(f"Device {i + 1}"): st.subheader(f"Device {i + 1} Details") - device["count"] = st.number_input(f"Count (Device {i + 1})", value=device["count"], key=f"count_{i}") - device["interval"] = st.number_input(f"Interval (Device {i + 1})", value=device["interval"], key=f"interval_{i}") - device["equip_id"] = st.text_input(f"Equip ID (Device {i + 1})", value=device["equip_id"], key=f"equip_id_{i}") - device["zone_id"] = st.text_input(f"Space ID (Device {i + 1})", value=device["zone_id"], key=f"zone_id_{i}") + device["count"] = st.number_input(f"Count (Device {i + 1}) - 보낼 센서의 데이터 수", value=device["count"], key=f"count_{i}") + device["interval"] = st.number_input(f"Interval (Device {i + 1}) - 데이터 전송간 시간 간격", value=device["interval"], key=f"interval_{i}") + device["equip_id"] = st.text_input(f"Equip ID (Device {i + 1}) - Equip 설비 정보", value=device["equip_id"], key=f"equip_id_{i}") + device["zone_id"] = st.text_input(f"Space ID (Device {i + 1}) - Zone 공간 정보", value=device["zone_id"], key=f"zone_id_{i}") + 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"] device["simulator"] = st.selectbox( - f"Simulator (Device {i + 1})", + f"Simulator (Device {i + 1}) - 시뮬레이터 타입 선택", options=simulator_options, index=simulator_options.index(device["simulator"]) if device["simulator"] in simulator_options else 0, key=f"simulator_{i}" @@ -137,7 +196,7 @@ def main(): if device["simulator"] == "real_sensor": st.warning("⚠️ 'real_sensor'는 로컬 환경에서만 사용 가능하며, 센서를 USB 포트에 연결해야 합니다.") # 센서 갯수 입력 - device["sensor_num"] = st.number_input(f"Sensor Num (Device {i + 1})", value=device["sensor_num"], key=f"sensor_num_{i}") + device["sensor_num"] = st.number_input(f"Sensor Num (Device {i + 1}) - 생성할 센서의 수", value=device["sensor_num"], key=f"sensor_num_{i}") # Run Simulation Button if st.button(f"Run Simulation for Device {i + 1}", key=f"run_{i}"): @@ -179,7 +238,7 @@ def main(): # Add new device st.header("Add New Device") - if st.button("Add Device"): + if st.button("Add Device - 새로운 디바이스 추가"): st.session_state.data["devices"].append({ "count": 1, "interval": 1.0, @@ -192,12 +251,18 @@ def main(): # Save options st.sidebar.header("Save Options") - if st.sidebar.button("Save to JSON"): + if st.sidebar.button("Save to JSON - 현재 설정해둔 센서 데이터를 JSON에 저장"): save_json(st.session_state.data) st.success("Saved data to JSON.") - if st.sidebar.button("Save to SQLite"): + if st.sidebar.button("Save to SQLite - 현재 설정해둔 센서 데이터를 DB에 저장"): save_to_db(st.session_state.data) st.success("Saved data to SQLite.") + # 자동 새로고침 구현 (맨 마지막에 추가) + if simulation_threads and any(thread.is_alive() for thread in simulation_threads.values()): + # 활성 시뮬레이션이 있으면 주기적으로 새로고침 + st.empty() # 빈 요소 추가 + time.sleep(3) # 3초 대기 + st.rerun() # 페이지 전체 새로고침 if __name__ == "__main__": init_db()