import streamlit as st import yaml import subprocess import os import time import signal import web_status import psutil from pathlib import Path # --- 1. CONFIGURAZIONE PAGINA --- st.set_page_config( page_title="BLE Localizer Manager", layout="wide", initial_sidebar_state="auto" ) # --- 2. CONFIGURAZIONE PERCORSI E STATI PERSISTENTI --- REAL_CONFIG_PATH = "/config/config.yaml" if os.path.exists("/config/config.yaml") else "/app/config/config.yaml" LOG_FILE = "/tmp/main_process.log" # File di stato su disco per la persistenza CORE_STATE_FILE = "/data/config/core.enabled" LOG_PID_FILE = "/tmp/mqtt_logging.pid" def load_yaml(path): if not os.path.exists(path): return {} with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) or {} def save_yaml(path, data): with open(path, 'w', encoding='utf-8') as f: yaml.dump(data, f, default_flow_style=False) cfg = load_yaml(REAL_CONFIG_PATH) # --- 3. LOGICA PERSISTENZA CORE --- def stop_core_engine(): """Arresta il core e rimuove il flag di persistenza.""" if os.path.exists(CORE_STATE_FILE): os.remove(CORE_STATE_FILE) for proc in psutil.process_iter(['cmdline']): try: cmd = proc.info['cmdline'] if cmd and ('app.main' in ' '.join(cmd) or 'main.py' in ' '.join(cmd)): proc.terminate() proc.wait(timeout=2) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.TimeoutExpired): try: proc.kill() except: pass def start_core_engine(): """Avvia il core e crea il flag di persistenza su disco.""" Path(CORE_STATE_FILE).touch() env = os.environ.copy() env["CONFIG"] = REAL_CONFIG_PATH env["PYTHONPATH"] = "/app" # Reindirizzamento append log per non perdere lo storico dell'entrypoint with open(LOG_FILE, "a") as log_f: subprocess.Popen( ["python3", "-m", "app.main"], env=env, cwd="/app", stdout=log_f, stderr=log_f, start_new_session=True ) # --- 4. MONITORAGGIO ATTIVO (SENZA AUTO-START BLOCCANTE) --- # La logica di avvio al boot รจ ora gestita da entrypoint.sh. # Qui segnaliamo solo se lo stato desiderato (file esistente) non coincide con quello reale. if os.path.exists(CORE_STATE_FILE) and not web_status.is_main_running(): st.sidebar.warning("โš ๏ธ Core abilitato ma non in esecuzione.") if st.sidebar.button("RIPRISTINA ORA"): start_core_engine() st.rerun() # --- 5. TITOLO E LOGIN --- st.title("๐Ÿ›ฐ๏ธ BLE AI Localizer - Suite") if "password_correct" not in st.session_state: st.session_state["password_correct"] = False if not st.session_state["password_correct"]: user = st.text_input("Username") pw = st.text_input("Password", type="password") if st.button("ACCEDI"): if user == os.environ.get("UI_USER", "admin") and pw == os.environ.get("UI_PASSWORD", "password"): st.session_state["password_correct"] = True st.rerun() else: st.error("Credenziali errate") st.stop() # --- 6. SIDEBAR: CONTROLLI SISTEMA --- with st.sidebar: st.header("๐Ÿ› ๏ธ Core Control") is_running = web_status.is_main_running() st.markdown(f"STATO: {':green[**CORE ATTIVO**]' if is_running else ':red[**CORE FERMO**]'}") if st.button("๐Ÿš€ RIAVVIA CORE" if is_running else "โ–ถ๏ธ AVVIA CORE", use_container_width=True): stop_core_engine() time.sleep(1) start_core_engine() st.rerun() st.divider() # --- GESTIONE MQTT RAW PERSISTENTE --- is_logging_active = False if os.path.exists(LOG_PID_FILE): try: with open(LOG_PID_FILE, "r") as f: saved_pid = int(f.read().strip()) if psutil.pid_exists(saved_pid): is_logging_active = True else: os.remove(LOG_PID_FILE) except: pass raw_active = st.toggle("๐Ÿ”ด MQTT RAW RECORDING", value=is_logging_active) if raw_active and not is_logging_active: RAW_LOG_DIR = cfg.get('paths', {}).get('mqtt_raw_dir', '/data/mqtt_raw/') os.makedirs(RAW_LOG_DIR, exist_ok=True) dt = time.strftime("%Y%m%d_%H%M%S") filepath = os.path.join(RAW_LOG_DIR, f"mqtt_raw_{dt}.log") m_cfg = cfg.get('mqtt', {}) m_filter = m_cfg.get('topic', 'publish_out').split('/')[0] cmd = f"stdbuf -oL mosquitto_sub -v -h {m_cfg.get('host')} -p {m_cfg.get('port')} -t '#' | stdbuf -oL grep '{m_filter}' > {filepath}" proc = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid) with open(LOG_PID_FILE, "w") as f: f.write(str(proc.pid)) st.rerun() elif not raw_active and is_logging_active: try: with open(LOG_PID_FILE, "r") as f: pid_to_kill = int(f.read().strip()) os.killpg(os.getpgid(pid_to_kill), signal.SIGTERM) if os.path.exists(LOG_PID_FILE): os.remove(LOG_PID_FILE) st.rerun() except: pass # --- 7. IMPORT MODULI E TABS --- from map_manager import show_mapper from web_gateway import show_gateway_manager from web_beacon import show_beacon_manager from web_test_inference import show_test_inference import web_training_data import web_inference tab_log, tab_cfg, tab_sec, tab_gw, tab_beac, tab_map, tab_model, tab_infer_test, tab_infer, tab_status = st.tabs([ "๐Ÿ“œ Log", "โš™๏ธ Config", "๐Ÿ”‘ Secrets", "๐ŸŒ Gateway", "๐Ÿท๏ธ Beacon", "๐Ÿ“ Rilevamento", "๐Ÿง  Modello", "๐Ÿงช InferTest", "๐Ÿค– Inferenza", "๐Ÿ–ฅ๏ธ Stato" ]) with tab_log: col_l1, col_l2 = st.columns(2) if col_l1.button("๐Ÿ”„ AGGIORNA LOG", use_container_width=True): st.rerun() if col_l2.button("๐Ÿ—‘๏ธ AZZERA LOG", use_container_width=True): try: with open(LOG_FILE, "w") as f: f.write(f"--- Log resettato il {time.strftime('%Y-%m-%d %H:%M:%S')} ---\n") st.success("Log azzerato!"); time.sleep(1); st.rerun() except: st.error("Errore pulizia log") if os.path.exists(LOG_FILE): with open(LOG_FILE, "r") as f: st.code("".join(f.readlines()[-100:])) with tab_cfg: st.subheader("๐Ÿš€ Configurazione Operativa") cfg_text = st.text_area("Expert Mode (YAML)", yaml.dump(cfg), height=400) if st.button("๐Ÿ’พ SALVA CONFIG"): save_yaml(REAL_CONFIG_PATH, yaml.safe_load(cfg_text)); st.success("Salvato!") with tab_sec: CURR_SECRETS = os.environ.get("SECRETS_FILE") or "/config/secrets.yaml" sec = load_yaml(CURR_SECRETS) sec_edit = st.text_area("Secrets YAML", yaml.dump(sec), height=200) if st.button("SALVA SECRETS"): save_yaml(CURR_SECRETS, yaml.safe_load(sec_edit)); st.success("OK!") with tab_gw: show_gateway_manager(cfg) with tab_beac: show_beacon_manager(cfg) with tab_map: show_mapper(cfg) with tab_model: web_training_data.show_training_data_manager(cfg) with tab_infer_test: show_test_inference(cfg) with tab_infer: web_inference.show_inference_page(cfg) with tab_status: sec_data = load_yaml(os.environ.get("SECRETS_FILE") or "/config/secrets.yaml") web_status.show_system_status(cfg, sec_data)