| @@ -21,10 +21,7 @@ COPY app/ /app/app/ | |||||
| COPY entrypoint.sh /app/entrypoint.sh | COPY entrypoint.sh /app/entrypoint.sh | ||||
| RUN chmod +x /app/entrypoint.sh | RUN chmod +x /app/entrypoint.sh | ||||
| # Setup utente e permessi | |||||
| RUN useradd -m appuser && \ | |||||
| mkdir -p /home/appuser/data /home/appuser/models && \ | |||||
| chown -R appuser:appuser /home/appuser /app | |||||
| # Setup permessi (manteniamo root per l'esecuzione dello script di boot e accesso a /tmp) | |||||
| RUN mkdir -p /data/config /data/mqtt_raw /data/model | |||||
| USER appuser | |||||
| ENTRYPOINT ["/app/entrypoint.sh"] | ENTRYPOINT ["/app/entrypoint.sh"] | ||||
| @@ -9,28 +9,26 @@ import yaml | |||||
| import paho.mqtt.client as mqtt | import paho.mqtt.client as mqtt | ||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||
| from pathlib import Path | from pathlib import Path | ||||
| from typing import Any, Callable, Dict, List, Optional, Tuple | |||||
| from typing import Any, Dict, List, Tuple | |||||
| from .logger_utils import log_msg as log | from .logger_utils import log_msg as log | ||||
| # Importiamo la funzione per caricare i gateway per mantenere l'ordine delle feature | |||||
| from .csv_config import load_gateway_features_csv | |||||
| BUILD_TAG = "infer-debug-v20-autoreload" | |||||
| BUILD_TAG = "infer-mac-fix-v21" | |||||
| # ------------------------- | # ------------------------- | ||||
| # UTILITIES | # UTILITIES | ||||
| # ------------------------- | # ------------------------- | ||||
| def _norm_mac(s: str) -> str: | def _norm_mac(s: str) -> str: | ||||
| s = (s or "").strip().replace("-", "").replace(":", "").replace(".", "").upper() | |||||
| """ | |||||
| Forza il formato standard xx:xx:xx:xx:xx:xx in MINUSCOLO per matchare il modello. | |||||
| Esempio MQTT: 'AC233FC1DD4E' -> 'ac:23:3f:c1:dd:4e' | |||||
| """ | |||||
| s = (s or "").strip().replace("-", "").replace(":", "").replace(".", "").lower() | |||||
| if len(s) != 12: return s | if len(s) != 12: return s | ||||
| return ":".join([s[i:i+2] for i in range(0, 12, 2)]) | return ":".join([s[i:i+2] for i in range(0, 12, 2)]) | ||||
| def _predict_xyz(model_pkg: Dict[str, Any], X: np.ndarray) -> Tuple[int, float, float]: | def _predict_xyz(model_pkg: Dict[str, Any], X: np.ndarray) -> Tuple[int, float, float]: | ||||
| floor_clf = model_pkg.get("floor_clf") | floor_clf = model_pkg.get("floor_clf") | ||||
| if floor_clf is None: | |||||
| raise ValueError("Il pacchetto modello non contiene 'floor_clf'") | |||||
| z_pred = floor_clf.predict(X) | z_pred = floor_clf.predict(X) | ||||
| z = int(z_pred[0]) | z = int(z_pred[0]) | ||||
| @@ -42,7 +40,6 @@ def _predict_xyz(model_pkg: Dict[str, Any], X: np.ndarray) -> Tuple[int, float, | |||||
| xy_pred = regressor.predict(X) | xy_pred = regressor.predict(X) | ||||
| x, y = xy_pred[0] | x, y = xy_pred[0] | ||||
| return z, float(x), float(y) | return z, float(x), float(y) | ||||
| @dataclass | @dataclass | ||||
| @@ -56,6 +53,7 @@ class RollingRSSI: | |||||
| self.data: Dict[str, Dict[str, List[_Point]]] = {} | self.data: Dict[str, Dict[str, List[_Point]]] = {} | ||||
| def add(self, bm: str, gm: str, rssi: float): | def add(self, bm: str, gm: str, rssi: float): | ||||
| # bm e gm arrivano già normalizzati da on_message | |||||
| self.data.setdefault(bm, {}).setdefault(gm, []).append(_Point(time.time(), rssi)) | self.data.setdefault(bm, {}).setdefault(gm, []).append(_Point(time.time(), rssi)) | ||||
| def prune(self): | def prune(self): | ||||
| @@ -71,6 +69,7 @@ class RollingRSSI: | |||||
| feats = [] | feats = [] | ||||
| found_count = 0 | found_count = 0 | ||||
| for gm in gws: | for gm in gws: | ||||
| # gm è già minuscolo con : perché preso da gateways_ordered (dal modello) | |||||
| vals = [p.v for p in per_gw.get(gm, [])] | vals = [p.v for p in per_gw.get(gm, [])] | ||||
| if vals: | if vals: | ||||
| val = np.median(vals) if agg == "median" else np.mean(vals) | val = np.median(vals) if agg == "median" else np.mean(vals) | ||||
| @@ -93,10 +92,9 @@ def run_infer(settings: Dict[str, Any]): | |||||
| log(f"INFER_MODE build tag={BUILD_TAG}") | log(f"INFER_MODE build tag={BUILD_TAG}") | ||||
| # Variabili di stato per il caricamento dinamico | |||||
| model_pkg = None | model_pkg = None | ||||
| last_model_mtime = 0 | last_model_mtime = 0 | ||||
| gateways_ordered = [] | |||||
| gateways_ordered = [] # Saranno in formato aa:bb:cc... | |||||
| gateways_set = set() | gateways_set = set() | ||||
| nan_fill = -110.0 | nan_fill = -110.0 | ||||
| @@ -105,34 +103,24 @@ def run_infer(settings: Dict[str, Any]): | |||||
| try: | try: | ||||
| current_mtime = os.path.getmtime(model_path) | current_mtime = os.path.getmtime(model_path) | ||||
| if current_mtime != last_model_mtime: | if current_mtime != last_model_mtime: | ||||
| log(f"🧠 Caricamento/Aggiornamento modello: {model_path}") | |||||
| log(f"🧠 Aggiornamento modello: {model_path}") | |||||
| model_pkg = joblib.load(model_path) | model_pkg = joblib.load(model_path) | ||||
| last_model_mtime = current_mtime | last_model_mtime = current_mtime | ||||
| # METADATI PER DEBUG | |||||
| gateways_ordered = model_pkg.get("gateways_order", []) | |||||
| # Il modello ha i gateway salvati come caricati nell'addestramento | |||||
| gateways_ordered = [gw.lower() for gw in model_pkg.get("gateways_order", [])] | |||||
| gateways_set = set(gateways_ordered) | gateways_set = set(gateways_ordered) | ||||
| nan_fill = float(model_pkg.get("nan_fill", -110.0)) | nan_fill = float(model_pkg.get("nan_fill", -110.0)) | ||||
| floors = list(model_pkg.get("xy_by_floor", {}).keys()) | |||||
| log(f"✅ MODELLO PRONTO: {len(gateways_ordered)} GW allenati.") | |||||
| log(f"🏢 Piani mappati: {floors}") | |||||
| log(f"🧪 Valore Fill (NaN): {nan_fill}") | |||||
| if len(gateways_ordered) > 0: | |||||
| log(f"📡 Primi 3 GW di riferimento: {gateways_ordered[:3]}") | |||||
| log(f"✅ MODELLO PRONTO: {len(gateways_ordered)} GW. Fill: {nan_fill}") | |||||
| return True | return True | ||||
| except Exception as e: | |||||
| if model_pkg is None: | |||||
| log(f"⚠️ In attesa di un modello valido in {model_path}...") | |||||
| return False | |||||
| except: return False | |||||
| return True | return True | ||||
| # Primo caricamento | |||||
| load_model_dynamic() | load_model_dynamic() | ||||
| rolling = RollingRSSI(float(inf_c.get("window_seconds", 5.0))) | rolling = RollingRSSI(float(inf_c.get("window_seconds", 5.0))) | ||||
| # --- Gestione Token e Beacons --- | |||||
| # Cache Beacons | |||||
| token_cache = {"token": None, "expires_at": 0} | token_cache = {"token": None, "expires_at": 0} | ||||
| def get_token(): | def get_token(): | ||||
| if time.time() < token_cache["expires_at"]: return token_cache["token"] | if time.time() < token_cache["expires_at"]: return token_cache["token"] | ||||
| @@ -159,18 +147,25 @@ def run_infer(settings: Dict[str, Any]): | |||||
| try: | try: | ||||
| headers = {"Authorization": f"Bearer {token}", "accept": "application/json"} | headers = {"Authorization": f"Bearer {token}", "accept": "application/json"} | ||||
| resp = requests.get(api_c["get_beacons_url"], headers=headers, verify=False, timeout=10) | resp = requests.get(api_c["get_beacons_url"], headers=headers, verify=False, timeout=10) | ||||
| return [it["mac"] for it in resp.json() if "mac" in it] if resp.status_code == 200 else [] | |||||
| # Normalizziamo anche i beacon cercati per il confronto | |||||
| return [_norm_mac(it["mac"]) for it in resp.json() if "mac" in it] if resp.status_code == 200 else [] | |||||
| except: return [] | except: return [] | ||||
| def on_message(client, userdata, msg): | def on_message(client, userdata, msg): | ||||
| if not gateways_set: return | |||||
| gw = _norm_mac(msg.topic.split("/")[-1]) | |||||
| if gw not in gateways_set: return | |||||
| # msg.topic es: publish_out/ac233fc1dd4e | |||||
| raw_gw = msg.topic.split("/")[-1] | |||||
| gw_norm = _norm_mac(raw_gw) | |||||
| if gw_norm not in gateways_set: return | |||||
| try: | try: | ||||
| items = json.loads(msg.payload.decode()) | items = json.loads(msg.payload.decode()) | ||||
| for it in items: | for it in items: | ||||
| bm, rssi = _norm_mac(it.get("mac")), it.get("rssi") | |||||
| if bm and rssi is not None: rolling.add(bm, gw, float(rssi)) | |||||
| raw_bm = it.get("mac") | |||||
| rssi = it.get("rssi") | |||||
| if raw_bm and rssi is not None: | |||||
| bm_norm = _norm_mac(raw_bm) | |||||
| rolling.add(bm_norm, gw_norm, float(rssi)) | |||||
| except: pass | except: pass | ||||
| mqtt_client = mqtt.Client() | mqtt_client = mqtt.Client() | ||||
| @@ -180,28 +175,25 @@ def run_infer(settings: Dict[str, Any]): | |||||
| mqtt_client.subscribe(mqtt_c["topic"]) | mqtt_client.subscribe(mqtt_c["topic"]) | ||||
| mqtt_client.loop_start() | mqtt_client.loop_start() | ||||
| except Exception as e: | except Exception as e: | ||||
| log(f"MQTT Connect Error: {e}") | |||||
| log(f"MQTT Error: {e}") | |||||
| last_predict, last_api_refresh = 0.0, 0.0 | last_predict, last_api_refresh = 0.0, 0.0 | ||||
| beacons = [] | |||||
| beacons_to_track = [] | |||||
| while True: | while True: | ||||
| now = time.time() | now = time.time() | ||||
| rolling.prune() | rolling.prune() | ||||
| # Verifica se il link simbolico model.joblib è cambiato | |||||
| load_model_dynamic() | load_model_dynamic() | ||||
| if now - last_api_refresh >= float(api_c.get("refresh_seconds", 30)): | if now - last_api_refresh >= float(api_c.get("refresh_seconds", 30)): | ||||
| beacons = fetch_beacons() | |||||
| beacons_to_track = fetch_beacons() | |||||
| last_api_refresh = now | last_api_refresh = now | ||||
| if now - last_predict >= float(inf_c.get("refresh_seconds", 10.0)): | if now - last_predict >= float(inf_c.get("refresh_seconds", 10.0)): | ||||
| rows, count_ok = [], 0 | rows, count_ok = [], 0 | ||||
| if model_pkg: | |||||
| for bm in beacons: | |||||
| bm_n = _norm_mac(bm) | |||||
| if model_pkg and beacons_to_track: | |||||
| for bm_n in beacons_to_track: | |||||
| X, n_found = rolling.aggregate_features(bm_n, gateways_ordered, inf_c.get("aggregate", "median"), nan_fill) | X, n_found = rolling.aggregate_features(bm_n, gateways_ordered, inf_c.get("aggregate", "median"), nan_fill) | ||||
| z, x, y = -1, -1.0, -1.0 | z, x, y = -1, -1.0, -1.0 | ||||
| @@ -209,8 +201,7 @@ def run_infer(settings: Dict[str, Any]): | |||||
| try: | try: | ||||
| z, x, y = _predict_xyz(model_pkg, X) | z, x, y = _predict_xyz(model_pkg, X) | ||||
| if z != -1: count_ok += 1 | if z != -1: count_ok += 1 | ||||
| except Exception as e: | |||||
| pass | |||||
| except: pass | |||||
| rows.append(f"{bm_n};{int(z)};{int(round(x))};{int(round(y))}") | rows.append(f"{bm_n};{int(z)};{int(round(x))};{int(round(y))}") | ||||
| @@ -221,7 +212,7 @@ def run_infer(settings: Dict[str, Any]): | |||||
| f.write("mac;z;x;y\n") | f.write("mac;z;x;y\n") | ||||
| for r in rows: f.write(r + "\n") | for r in rows: f.write(r + "\n") | ||||
| os.replace(str(out_p) + ".tmp", out_p) | os.replace(str(out_p) + ".tmp", out_p) | ||||
| log(f"CYCLE: {count_ok}/{len(rows)} localized") | |||||
| log(f"CYCLE: {count_ok}/{len(beacons_to_track)} localized (Input GW match: {len(gateways_set)})") | |||||
| except Exception as e: log(f"File Error: {e}") | except Exception as e: log(f"File Error: {e}") | ||||
| last_predict = now | last_predict = now | ||||
| @@ -33,80 +33,95 @@ def process_train_jobs(): | |||||
| nan_fill = job["nan_fill"] | nan_fill = job["nan_fill"] | ||||
| gw_csv = job["gateways_csv"] | gw_csv = job["gateways_csv"] | ||||
| # --- GENERAZIONE NOME FILE CON TIMESTAMP (BACKUP) --- | |||||
| # --- GENERAZIONE NOME FILE CON TIMESTAMP --- | |||||
| now_str = datetime.now().strftime("%Y%m%d_%H%M%S") | now_str = datetime.now().strftime("%Y%m%d_%H%M%S") | ||||
| model_filename = f"model_camp_{campagna}_{now_str}.joblib" | model_filename = f"model_camp_{campagna}_{now_str}.joblib" | ||||
| model_path = Path("/data/model") / model_filename | model_path = Path("/data/model") / model_filename | ||||
| # Caricamento Gateway | |||||
| # Caricamento Gateway con normalizzazione MAC | |||||
| gws = load_gateway_features_csv(gw_csv) | gws = load_gateway_features_csv(gw_csv) | ||||
| gateways_order = [g.mac for g in gws] | |||||
| gateways_order = [g.mac.lower().strip() for g in gws] | |||||
| log(f"[TRAIN-CORE] Feature vector: {len(gateways_order)} gateway caricati da {gw_csv}") | |||||
| # Analisi file campioni | # Analisi file campioni | ||||
| samples_dir = Path("/data/train/samples") | samples_dir = Path("/data/train/samples") | ||||
| sample_files = list(samples_dir.glob(f"{campagna}_*.csv")) | sample_files = list(samples_dir.glob(f"{campagna}_*.csv")) | ||||
| log(f"[TRAIN-CORE] Analisi di {len(sample_files)} file per campagna '{campagna}'") | |||||
| X_list, y_z, y_xy = [], [], [] | X_list, y_z, y_xy = [], [], [] | ||||
| for fp in sample_files: | for fp in sample_files: | ||||
| try: | try: | ||||
| df = pd.read_csv(fp, sep=";") | df = pd.read_csv(fp, sep=";") | ||||
| if df.empty: continue | if df.empty: continue | ||||
| # Normalizziamo le colonne del DF in minuscolo | |||||
| df.columns = [c.lower().strip() for c in df.columns] | |||||
| row = df.iloc[0] | row = df.iloc[0] | ||||
| # Mapping RSSI basato su gateway.csv (risolve errore 'mac') | |||||
| X_list.append([float(row.get(gw, nan_fill)) for gw in gateways_order]) | |||||
| features = [] | |||||
| for gw in gateways_order: | |||||
| val = row.get(gw) | |||||
| # Gestione esplicita di 'nan' stringa o NaN numerico | |||||
| if val is not None and str(val).lower() != 'nan' and not pd.isna(val): | |||||
| features.append(float(val)) | |||||
| else: | |||||
| features.append(float(nan_fill)) | |||||
| X_list.append(features) | |||||
| y_z.append(int(round(float(row.get("z"))))) | y_z.append(int(round(float(row.get("z"))))) | ||||
| y_xy.append([float(row.get("x")), float(row.get("y"))]) | y_xy.append([float(row.get("x")), float(row.get("y"))]) | ||||
| except: continue | |||||
| except Exception as e: | |||||
| log(f"[TRAIN-CORE] Errore nel file {fp.name}: {e}") | |||||
| continue | |||||
| if not X_list: | if not X_list: | ||||
| log(f"[TRAIN-CORE] ERRORE: Dati non validi per campagna {campagna}") | |||||
| log(f"[TRAIN-CORE] ❌ ABORTO: Nessun dato matchato tra gateway.csv e fingerprint!") | |||||
| job_path.unlink() | job_path.unlink() | ||||
| continue | continue | ||||
| X, Y_z, Y_xy = np.array(X_list), np.array(y_z), np.array(y_xy) | |||||
| X = np.array(X_list) | |||||
| # Calcolo copertura reale | |||||
| matches_per_point = [np.sum(np.array(vec) > nan_fill) for vec in X_list] | |||||
| avg_match = np.mean(matches_per_point) | |||||
| log(f"[TRAIN-CORE] Dataset pronto. Punti: {len(X_list)}. Media match Gateway: {avg_match:.2f}/{len(gateways_order)}") | |||||
| # Fitting KNN | |||||
| log(f"[TRAIN-CORE] Fitting modello per {model_filename}...") | |||||
| # --- LOG PARAMETRI ADDESTRAMENTO --- | |||||
| k_val = int(knn_cfg.get('k', 5)) | |||||
| w_val = knn_cfg.get('weights', 'distance') | |||||
| m_val = knn_cfg.get('metric', 'euclidean') | |||||
| log(f"[TRAIN-CORE] Fitting KNN -> k: {k_val}, weights: {w_val}, metric: {m_val}, nan_fill: {nan_fill}") | |||||
| # Fitting | |||||
| floor_clf = KNeighborsClassifier( | floor_clf = KNeighborsClassifier( | ||||
| n_neighbors=int(knn_cfg.get('k', 5)), | |||||
| weights=knn_cfg.get('weights', 'distance'), | |||||
| metric=knn_cfg.get('metric', 'euclidean') | |||||
| ).fit(X, Y_z) | |||||
| n_neighbors=k_val, weights=w_val, metric=m_val | |||||
| ).fit(X, np.array(y_z)) | |||||
| models_xy = {} | models_xy = {} | ||||
| for z in np.unique(Y_z): | |||||
| idx = np.where(Y_z == z)[0] | |||||
| for z in np.unique(y_z): | |||||
| idx = np.where(np.array(y_z) == z)[0] | |||||
| # k_xy non può essere superiore al numero di campioni per piano | |||||
| current_k_xy = min(k_val, len(idx)) | |||||
| models_xy[int(z)] = KNeighborsRegressor( | models_xy[int(z)] = KNeighborsRegressor( | ||||
| n_neighbors=min(int(knn_cfg.get('k', 5)), len(idx)), | |||||
| weights=knn_cfg.get('weights', 'distance'), | |||||
| metric=knn_cfg.get('metric', 'euclidean') | |||||
| ).fit(X[idx], Y_xy[idx]) | |||||
| n_neighbors=current_k_xy, weights=w_val, metric=m_val | |||||
| ).fit(X[idx], np.array(y_xy)[idx]) | |||||
| # Salvataggio Pacchetto | |||||
| # Salvataggio | |||||
| model_pkg = { | model_pkg = { | ||||
| "floor_clf": floor_clf, | |||||
| "xy_by_floor": models_xy, | |||||
| "gateways_order": gateways_order, | |||||
| "nan_fill": nan_fill, | |||||
| "created_at": datetime.now().isoformat(), | |||||
| "campaign": campagna, | |||||
| "filename": model_filename | |||||
| "floor_clf": floor_clf, "xy_by_floor": models_xy, | |||||
| "gateways_order": gateways_order, "nan_fill": nan_fill, | |||||
| "knn_params": {"k": k_val, "weights": w_val, "metric": m_val}, | |||||
| "created_at": datetime.now().isoformat(), "campaign": campagna | |||||
| } | } | ||||
| Path("/data/model").mkdir(parents=True, exist_ok=True) | Path("/data/model").mkdir(parents=True, exist_ok=True) | ||||
| joblib.dump(model_pkg, model_path) | joblib.dump(model_pkg, model_path) | ||||
| log(f"[TRAIN-CORE] ✅ Addestramento COMPLETATO: {model_filename}") | log(f"[TRAIN-CORE] ✅ Addestramento COMPLETATO: {model_filename}") | ||||
| except Exception as e: | except Exception as e: | ||||
| log(f"[TRAIN-CORE] ❌ ERRORE CRITICO: {str(e)}") | log(f"[TRAIN-CORE] ❌ ERRORE CRITICO: {str(e)}") | ||||
| finally: | finally: | ||||
| if job_path.exists(): | |||||
| job_path.unlink() | |||||
| if job_path.exists(): job_path.unlink() | |||||
| def run_train_monitor(): | def run_train_monitor(): | ||||
| """Loop di monitoraggio per il Core Orchestrator.""" | |||||
| while True: | while True: | ||||
| process_train_jobs() | process_train_jobs() | ||||
| time.sleep(5) | time.sleep(5) | ||||
| @@ -3,6 +3,8 @@ import pandas as pd | |||||
| import os | import os | ||||
| import json | import json | ||||
| import folium | import folium | ||||
| import requests | |||||
| import yaml | |||||
| from streamlit_folium import st_folium | from streamlit_folium import st_folium | ||||
| from pathlib import Path | from pathlib import Path | ||||
| from PIL import Image | from PIL import Image | ||||
| @@ -19,6 +21,48 @@ def get_image_base64(img_path): | |||||
| img_str = base64.b64encode(buffered.getvalue()).decode("ascii") | img_str = base64.b64encode(buffered.getvalue()).decode("ascii") | ||||
| return f"data:image/png;base64,{img_str}", w, h | return f"data:image/png;base64,{img_str}", w, h | ||||
| def _norm_mac_internal(s: str) -> str: | |||||
| """Standardizza il MAC in formato xx:xx:xx:xx:xx:xx minuscolo per il matching.""" | |||||
| s = (s or "").strip().replace("-", "").replace(":", "").replace(".", "").lower() | |||||
| if len(s) != 12: return s | |||||
| return ":".join([s[i:i+2] for i in range(0, 12, 2)]) | |||||
| def fetch_beacons_metadata(cfg): | |||||
| """Recupera l'elenco dei beacon dalle API e normalizza i MAC per il lookup.""" | |||||
| api_c = cfg.get("api", {}) | |||||
| token_url = api_c.get("token_url") | |||||
| beacons_url = api_c.get("get_beacons_url") | |||||
| try: | |||||
| secrets_path = os.environ.get("SECRETS_FILE") or "/config/secrets.yaml" | |||||
| with open(secrets_path, "r") as f: | |||||
| sec = yaml.safe_load(f).get("oidc", {}) | |||||
| # 1. Autenticazione OIDC | |||||
| payload = { | |||||
| "grant_type": "password", | |||||
| "client_id": api_c.get("client_id", "Fastapi"), | |||||
| "client_secret": sec.get("client_secret", ""), | |||||
| "username": sec.get("username", "core"), | |||||
| "password": sec.get("password", "") | |||||
| } | |||||
| resp_t = requests.post(token_url, data=payload, verify=False, timeout=5) | |||||
| if resp_t.status_code != 200: return {} | |||||
| token = resp_t.json().get("access_token") | |||||
| # 2. Download Beacon List e Normalizzazione | |||||
| headers = {"Authorization": f"Bearer {token}", "accept": "application/json"} | |||||
| resp_b = requests.get(beacons_url, headers=headers, verify=False, timeout=5) | |||||
| if resp_b.status_code == 200: | |||||
| # Dizionario {mac_normalizzato: nome_amichevole} | |||||
| return { | |||||
| _norm_mac_internal(it["mac"]): it.get("name", "N/A") | |||||
| for it in resp_b.json() if "mac" in it | |||||
| } | |||||
| except Exception as e: | |||||
| st.error(f"Errore recupero nomi beacon: {e}") | |||||
| return {} | |||||
| def show_inference_page(cfg): | def show_inference_page(cfg): | ||||
| st.subheader("📡 Monitoraggio Beacon Real-Time") | st.subheader("📡 Monitoraggio Beacon Real-Time") | ||||
| @@ -26,7 +70,7 @@ def show_inference_page(cfg): | |||||
| MAPS_DIR = Path(cfg['maps']['map_dir']) | MAPS_DIR = Path(cfg['maps']['map_dir']) | ||||
| INFER_FILE = Path("/data/infer/infer.csv") | INFER_FILE = Path("/data/infer/infer.csv") | ||||
| # --- 1. SELEZIONE E STATO (RIGA COMPATTA) --- | |||||
| # --- 1. SELEZIONE E STATO --- | |||||
| maps = sorted([f.replace(cfg['maps']['floor_prefix'], "").split('.')[0] | maps = sorted([f.replace(cfg['maps']['floor_prefix'], "").split('.')[0] | ||||
| for f in os.listdir(MAPS_DIR) if f.startswith(cfg['maps']['floor_prefix'])]) | for f in os.listdir(MAPS_DIR) if f.startswith(cfg['maps']['floor_prefix'])]) | ||||
| @@ -34,30 +78,24 @@ def show_inference_page(cfg): | |||||
| st.warning("Nessuna mappa configurata.") | st.warning("Nessuna mappa configurata.") | ||||
| return | return | ||||
| # Lettura dati per conteggio | |||||
| df_infer = pd.DataFrame() | df_infer = pd.DataFrame() | ||||
| if INFER_FILE.exists(): | if INFER_FILE.exists(): | ||||
| df_infer = pd.read_csv(INFER_FILE, sep=";") | df_infer = pd.read_csv(INFER_FILE, sep=";") | ||||
| # Layout riga 1 | |||||
| c_piano, c_count, c_size = st.columns([3, 2, 2]) | c_piano, c_count, c_size = st.columns([3, 2, 2]) | ||||
| with c_piano: | with c_piano: | ||||
| sub1, sub2 = st.columns([1, 1.2]) | sub1, sub2 = st.columns([1, 1.2]) | ||||
| sub1.markdown("<p style='padding-top:35px; font-weight:bold; font-size:15px;'>Piano Visualizzato:</p>", unsafe_allow_html=True) | sub1.markdown("<p style='padding-top:35px; font-weight:bold; font-size:15px;'>Piano Visualizzato:</p>", unsafe_allow_html=True) | ||||
| floor_id = sub2.selectbox("", maps, key="inf_floor_v24", label_visibility="collapsed") | floor_id = sub2.selectbox("", maps, key="inf_floor_v24", label_visibility="collapsed") | ||||
| # Filtro dati per il piano scelto | |||||
| df_active = df_infer[(df_infer['z'].astype(str) == str(floor_id)) & (df_infer['x'] != -1)] if not df_infer.empty else pd.DataFrame() | df_active = df_infer[(df_infer['z'].astype(str) == str(floor_id)) & (df_infer['x'] != -1)] if not df_infer.empty else pd.DataFrame() | ||||
| with c_count: | with c_count: | ||||
| st.info(f"📡 Beacon Attivi: **{len(df_active)}**\n(Totali nel file: {len(df_infer)})") | st.info(f"📡 Beacon Attivi: **{len(df_active)}**\n(Totali nel file: {len(df_infer)})") | ||||
| with c_size: | with c_size: | ||||
| # Slider per dimensione pallini (come nel mapper) | |||||
| m_size = st.slider("Dimensione Beacon:", 5, 20, 8, key="inf_msize_v24") | m_size = st.slider("Dimensione Beacon:", 5, 20, 8, key="inf_msize_v24") | ||||
| # Caricamento Metadati | |||||
| meta_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json" | meta_path = MAPS_DIR / f"{cfg['maps']['meta_prefix']}{floor_id}.json" | ||||
| if not meta_path.exists(): return | if not meta_path.exists(): return | ||||
| with open(meta_path, "r") as f: meta = json.load(f) | with open(meta_path, "r") as f: meta = json.load(f) | ||||
| @@ -73,30 +111,57 @@ def show_inference_page(cfg): | |||||
| m.options.update({"minZoom": -6, "maxZoom": 6, "zoomSnap": 0.25, "maxBounds": bounds, "maxBoundsViscosity": 1.0}) | m.options.update({"minZoom": -6, "maxZoom": 6, "zoomSnap": 0.25, "maxBounds": bounds, "maxBoundsViscosity": 1.0}) | ||||
| folium.raster_layers.ImageOverlay(image=img_data, bounds=bounds).add_to(m) | folium.raster_layers.ImageOverlay(image=img_data, bounds=bounds).add_to(m) | ||||
| # Disegno Beacon | |||||
| # Recupero nomi per etichette mappa | |||||
| names_map = fetch_beacons_metadata(cfg) | |||||
| if meta["calibrated"] and meta["origin"] != [0,0]: | if meta["calibrated"] and meta["origin"] != [0,0]: | ||||
| # Origine | |||||
| # Origine (Punto di riferimento) | |||||
| folium.CircleMarker(location=[meta["origin"][1], meta["origin"][0]], radius=4, color="black", fill=True).add_to(m) | folium.CircleMarker(location=[meta["origin"][1], meta["origin"][0]], radius=4, color="black", fill=True).add_to(m) | ||||
| for _, row in df_active.iterrows(): | for _, row in df_active.iterrows(): | ||||
| px_x = (row['x'] * meta["pixel_ratio"]) + meta["origin"][0] | px_x = (row['x'] * meta["pixel_ratio"]) + meta["origin"][0] | ||||
| px_y = meta["origin"][1] - (row['y'] * meta["pixel_ratio"]) | px_y = meta["origin"][1] - (row['y'] * meta["pixel_ratio"]) | ||||
| mac_label = str(row['mac'])[-5:] | |||||
| # --- UMANIZZAZIONE ETICHETTA MAPPA --- | |||||
| norm_mac = _norm_mac_internal(row['mac']) | |||||
| # Se disponibile usa il nome da API, altrimenti le ultime cifre del MAC | |||||
| label_text = names_map.get(norm_mac, str(row['mac'])[-5:]) | |||||
| # Pallino Beacon | |||||
| folium.CircleMarker( | folium.CircleMarker( | ||||
| location=[px_y, px_x], radius=m_size, color="blue", | location=[px_y, px_x], radius=m_size, color="blue", | ||||
| fill=True, fill_color="cyan", fill_opacity=0.8 | |||||
| fill=True, fill_color="cyan", fill_opacity=0.8, | |||||
| tooltip=f"Device: {label_text} | MAC: {row['mac']}" | |||||
| ).add_to(m) | ).add_to(m) | ||||
| # Etichetta Nome (accanto al punto) | |||||
| folium.Marker( | folium.Marker( | ||||
| location=[px_y, px_x], | location=[px_y, px_x], | ||||
| icon=folium.DivIcon(html=f"""<div style="font-family: sans-serif; color: black; font-weight: bold; font-size: {int(m_size*1.2)}pt; width: 80px; transform: translate({m_size+2}px, -{m_size+2}px);">{mac_label}</div>""") | |||||
| icon=folium.DivIcon(html=f""" | |||||
| <div style=" | |||||
| font-family: sans-serif; | |||||
| color: #0d47a1; | |||||
| font-weight: bold; | |||||
| white-space: nowrap; | |||||
| font-size: {int(m_size*1.1)}pt; | |||||
| transform: translate({m_size+2}px, -{m_size+2}px); | |||||
| text-shadow: 1px 1px 2px white; | |||||
| "> | |||||
| {label_text} | |||||
| </div> | |||||
| """) | |||||
| ).add_to(m) | ).add_to(m) | ||||
| st_folium(m, height=700, width=None, key=f"inf_map_v24_{floor_id}", use_container_width=True) | st_folium(m, height=700, width=None, key=f"inf_map_v24_{floor_id}", use_container_width=True) | ||||
| # --- 3. TABELLA RIEPILOGO COMPLETA --- | |||||
| # --- 3. TABELLA RIEPILOGO COMPLETA (Arricchita con Nome API) --- | |||||
| if not df_infer.empty: | if not df_infer.empty: | ||||
| st.subheader("Dettaglio Dispositivi (Tutti i Piani)") | st.subheader("Dettaglio Dispositivi (Tutti i Piani)") | ||||
| # Aggiunta colonna Z per chiarezza | |||||
| st.dataframe(df_infer[['mac', 'z', 'x', 'y']], use_container_width=True) | |||||
| df_display = df_infer.copy() | |||||
| df_display['mac_norm'] = df_display['mac'].apply(_norm_mac_internal) | |||||
| df_display['name'] = df_display['mac_norm'].map(names_map).fillna("Sconosciuto") | |||||
| # Selezione e riordino colonne finali | |||||
| cols_to_show = ['mac', 'name', 'z', 'x', 'y'] | |||||
| st.dataframe(df_display[cols_to_show], use_container_width=True) | |||||
| @@ -6,6 +6,7 @@ import time | |||||
| import signal | import signal | ||||
| import web_status | import web_status | ||||
| import psutil | import psutil | ||||
| from pathlib import Path | |||||
| # --- 1. CONFIGURAZIONE PAGINA --- | # --- 1. CONFIGURAZIONE PAGINA --- | ||||
| st.set_page_config( | st.set_page_config( | ||||
| @@ -14,13 +15,14 @@ st.set_page_config( | |||||
| initial_sidebar_state="auto" | initial_sidebar_state="auto" | ||||
| ) | ) | ||||
| # --- 2. CONFIGURAZIONE PERCORSI --- | |||||
| REAL_CONFIG_PATH = "/config/config.yaml" | |||||
| if not os.path.exists(REAL_CONFIG_PATH): | |||||
| REAL_CONFIG_PATH = "/app/config/config.yaml" | |||||
| # --- 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" | 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): | def load_yaml(path): | ||||
| if not os.path.exists(path): return {} | if not os.path.exists(path): return {} | ||||
| with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) or {} | with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) or {} | ||||
| @@ -30,7 +32,12 @@ def save_yaml(path, data): | |||||
| cfg = load_yaml(REAL_CONFIG_PATH) | cfg = load_yaml(REAL_CONFIG_PATH) | ||||
| # --- 3. LOGICA PERSISTENZA CORE --- | |||||
| def stop_core_engine(): | 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']): | for proc in psutil.process_iter(['cmdline']): | ||||
| try: | try: | ||||
| cmd = proc.info['cmdline'] | cmd = proc.info['cmdline'] | ||||
| @@ -42,9 +49,12 @@ def stop_core_engine(): | |||||
| except: pass | except: pass | ||||
| def start_core_engine(): | 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 = os.environ.copy() | ||||
| env["CONFIG"] = REAL_CONFIG_PATH | env["CONFIG"] = REAL_CONFIG_PATH | ||||
| env["PYTHONPATH"] = "/app" | env["PYTHONPATH"] = "/app" | ||||
| # Reindirizzamento append log per non perdere lo storico dell'entrypoint | |||||
| with open(LOG_FILE, "a") as log_f: | with open(LOG_FILE, "a") as log_f: | ||||
| subprocess.Popen( | subprocess.Popen( | ||||
| ["python3", "-m", "app.main"], | ["python3", "-m", "app.main"], | ||||
| @@ -55,10 +65,18 @@ def start_core_engine(): | |||||
| start_new_session=True | start_new_session=True | ||||
| ) | ) | ||||
| # --- 3. TITOLO FISSO --- | |||||
| # --- 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") | st.title("🛰️ BLE AI Localizer - Suite") | ||||
| # --- 4. LOGICA LOGIN --- | |||||
| if "password_correct" not in st.session_state: | if "password_correct" not in st.session_state: | ||||
| st.session_state["password_correct"] = False | st.session_state["password_correct"] = False | ||||
| @@ -73,63 +91,60 @@ if not st.session_state["password_correct"]: | |||||
| st.error("Credenziali errate") | st.error("Credenziali errate") | ||||
| st.stop() | st.stop() | ||||
| # --- 5. AUTO-START --- | |||||
| if not web_status.is_main_running(): | |||||
| if "core_auto_launched" not in st.session_state: | |||||
| start_core_engine() | |||||
| st.session_state["core_auto_launched"] = True | |||||
| time.sleep(1) | |||||
| st.rerun() | |||||
| # --- 6. SIDEBAR: CONTROLLI SISTEMA --- | # --- 6. SIDEBAR: CONTROLLI SISTEMA --- | ||||
| with st.sidebar: | with st.sidebar: | ||||
| st.header("🛠️ Core Control") | st.header("🛠️ Core Control") | ||||
| is_running = web_status.is_main_running() | is_running = web_status.is_main_running() | ||||
| if is_running: | |||||
| st.markdown("STATO: :green[**CORE ATTIVO**]") | |||||
| else: | |||||
| st.markdown("STATO: :red[**CORE FERMO**]") | |||||
| 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): | if st.button("🚀 RIAVVIA CORE" if is_running else "▶️ AVVIA CORE", use_container_width=True): | ||||
| with st.spinner("Gestione Core..."): | |||||
| stop_core_engine() | |||||
| time.sleep(1) | |||||
| start_core_engine() | |||||
| time.sleep(2) | |||||
| st.rerun() | |||||
| stop_core_engine() | |||||
| time.sleep(1) | |||||
| start_core_engine() | |||||
| st.rerun() | |||||
| st.divider() | st.divider() | ||||
| # --- GESTIONE MQTT RAW (DINAMICA DA CONFIG) --- | |||||
| m_cfg = cfg.get('mqtt', {}) | |||||
| m_host = m_cfg.get('host', '127.0.0.1') | |||||
| m_port = m_cfg.get('port', 1883) | |||||
| m_proto = m_cfg.get('protocol', 'mqttv311') | |||||
| # Estraiamo la parola chiave dal topic (es: 'publish_out' da 'publish_out/#') | |||||
| m_filter = m_cfg.get('topic', 'publish_out').split('/')[0] | |||||
| RAW_LOG_DIR = cfg.get('paths', {}).get('mqtt_raw_dir', '/data/mqtt_raw/') | |||||
| os.makedirs(RAW_LOG_DIR, exist_ok=True) | |||||
| raw_active = st.toggle("🔴 MQTT RAW RECORDING", value=st.session_state.get("mqtt_logging", False)) | |||||
| if raw_active and not st.session_state.get("mqtt_logging"): | |||||
| # --- 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") | dt = time.strftime("%Y%m%d_%H%M%S") | ||||
| filepath = os.path.join(RAW_LOG_DIR, f"mqtt_raw_{dt}.log") | 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] | |||||
| # Comando generato dinamicamente dai parametri YAML | |||||
| cmd = f"stdbuf -oL mosquitto_sub -v -h {m_host} -p {m_port} -t '#' -V {m_proto} | stdbuf -oL grep '{m_filter}' > {filepath}" | |||||
| 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) | proc = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid) | ||||
| st.session_state["mqtt_logging"] = True | |||||
| st.session_state["mqtt_proc_pid"] = proc.pid | |||||
| st.success(f"Log avviato: {m_host}") | |||||
| with open(LOG_PID_FILE, "w") as f: | |||||
| f.write(str(proc.pid)) | |||||
| st.rerun() | |||||
| if not raw_active and st.session_state.get("mqtt_logging"): | |||||
| elif not raw_active and is_logging_active: | |||||
| try: | try: | ||||
| os.killpg(os.getpgid(st.session_state["mqtt_proc_pid"]), signal.SIGTERM) | |||||
| 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: | except: | ||||
| pass | pass | ||||
| st.session_state["mqtt_logging"] = False | |||||
| # --- 7. IMPORT MODULI E TABS --- | # --- 7. IMPORT MODULI E TABS --- | ||||
| from map_manager import show_mapper | from map_manager import show_mapper | ||||
| @@ -53,14 +53,34 @@ def show_test_inference(cfg): | |||||
| m_pkg = joblib.load(MODEL_DIR / selected_model) | m_pkg = joblib.load(MODEL_DIR / selected_model) | ||||
| delim = cfg.get('paths', {}).get('csv_delimiter', ';') | delim = cfg.get('paths', {}).get('csv_delimiter', ';') | ||||
| df_test = pd.read_csv(TEST_SAMPLES_DIR / selected_test, sep=delim) | df_test = pd.read_csv(TEST_SAMPLES_DIR / selected_test, sep=delim) | ||||
| row = df_test.iloc[0] | row = df_test.iloc[0] | ||||
| z_real, x_real, y_real = int(round(float(row['z']))), float(row['x']), float(row['y']) | |||||
| z_real, x_real, y_real = int(round(float(row['z']))), float(row['x']), float(row['y']) | |||||
| gws = m_pkg['gateways_order'] | gws = m_pkg['gateways_order'] | ||||
| fill = m_pkg.get('nan_fill', -110.0) | fill = m_pkg.get('nan_fill', -110.0) | ||||
| X_in = np.array([[float(row.get(gw, fill)) for gw in gws]]) | |||||
| # --- DEBUG VETTORE INPUT --- | |||||
| raw_vals = [] | |||||
| for gw in gws: | |||||
| val = row.get(gw) | |||||
| raw_vals.append(float(val) if val is not None and not pd.isna(val) else fill) | |||||
| X_in = np.array([raw_vals]) | |||||
| # Visualizzazione Debug in UI per l'operatore | |||||
| with st.expander("🔍 Analisi Vettore di Input (Fingerprint vs Modello)"): | |||||
| debug_df = pd.DataFrame({ | |||||
| "Gateway MAC": gws, | |||||
| "RSSI Letto": [row.get(gw, "NON TROVATO") for gw in gws], | |||||
| "RSSI Finale (Input AI)": raw_vals | |||||
| }) | |||||
| st.dataframe(debug_df) | |||||
| match_count = np.sum(X_in[0] > fill) | |||||
| st.write(f"**Gateway Corrispondenti:** {match_count} su {len(gws)}") | |||||
| if match_count == 0: | |||||
| st.error("ERRORE: Il file di test non contiene nessuno dei Gateway usati per l'addestramento!") | |||||
| # Predizione | |||||
| z_pred = int(m_pkg['floor_clf'].predict(X_in)[0]) | z_pred = int(m_pkg['floor_clf'].predict(X_in)[0]) | ||||
| x_pred, y_pred = -1.0, -1.0 | x_pred, y_pred = -1.0, -1.0 | ||||
| if z_pred in m_pkg['xy_by_floor']: | if z_pred in m_pkg['xy_by_floor']: | ||||
| @@ -68,27 +88,24 @@ def show_test_inference(cfg): | |||||
| x_pred, y_pred = float(xy[0]), float(xy[1]) | x_pred, y_pred = float(xy[0]), float(xy[1]) | ||||
| z_err, dist_err = calculate_error(z_real, x_real, y_real, z_pred, x_pred, y_pred) | z_err, dist_err = calculate_error(z_real, x_real, y_real, z_pred, x_pred, y_pred) | ||||
| st.session_state.test_results = { | st.session_state.test_results = { | ||||
| "z_real": z_real, "x_real": x_real, "y_real": y_real, | "z_real": z_real, "x_real": x_real, "y_real": y_real, | ||||
| "z_pred": z_pred, "x_pred": x_pred, "y_pred": y_pred, | "z_pred": z_pred, "x_pred": x_pred, "y_pred": y_pred, | ||||
| "z_err": z_err, "dist_err": dist_err, | |||||
| "model_used": selected_model | |||||
| "z_err": z_err, "dist_err": dist_err, "model_used": selected_model | |||||
| } | } | ||||
| except Exception as e: | except Exception as e: | ||||
| st.error(f"Errore: {e}") | |||||
| st.error(f"Errore durante l'inferenza: {e}") | |||||
| # --- VISUALIZZAZIONE GRAFICA DEI RISULTATI --- | |||||
| if st.session_state.test_results: | if st.session_state.test_results: | ||||
| res = st.session_state.test_results | res = st.session_state.test_results | ||||
| # --- RIPRISTINO INFORMAZIONI COORDINATE --- | |||||
| c_info1, c_info2 = st.columns(2) | c_info1, c_info2 = st.columns(2) | ||||
| with c_info1: | with c_info1: | ||||
| st.info(f"📍 **Test Reale** | Piano: {res['z_real']} | X: **{res['x_real']}** | Y: **{res['y_real']}**") | st.info(f"📍 **Test Reale** | Piano: {res['z_real']} | X: **{res['x_real']}** | Y: **{res['y_real']}**") | ||||
| with c_info2: | with c_info2: | ||||
| st.success(f"🔮 **Predizione** | Piano: {res['z_pred']} | X: **{round(res['x_pred'],1)}** | Y: **{round(res['y_pred'],1)}**") | st.success(f"🔮 **Predizione** | Piano: {res['z_pred']} | X: **{round(res['x_pred'],1)}** | Y: **{round(res['y_pred'],1)}**") | ||||
| # --- LEGENDA AGGIORNATA CON NUOVI COLORI --- | |||||
| st.markdown(f""" | st.markdown(f""" | ||||
| <div style="background-color: #f8f9fa; padding: 12px; border-radius: 8px; border-left: 5px solid #00bcd4; margin: 10px 0;"> | <div style="background-color: #f8f9fa; padding: 12px; border-radius: 8px; border-left: 5px solid #00bcd4; margin: 10px 0;"> | ||||
| <h5 style="margin:0 0 8px 0;">📍 Legenda Mappa</h5> | <h5 style="margin:0 0 8px 0;">📍 Legenda Mappa</h5> | ||||
| @@ -122,14 +139,13 @@ def show_test_inference(cfg): | |||||
| p_real = to_px(res['x_real'], res['y_real']) | p_real = to_px(res['x_real'], res['y_real']) | ||||
| p_pred = to_px(res['x_pred'], res['y_pred']) | p_pred = to_px(res['x_pred'], res['y_pred']) | ||||
| # 🔵 PUNTO DI TEST (Celeste) | |||||
| # Marker Posizione Reale | |||||
| folium.CircleMarker( | folium.CircleMarker( | ||||
| location=p_real, radius=11, color="#00838f", fill=True, | location=p_real, radius=11, color="#00838f", fill=True, | ||||
| fill_color="#00bcd4", fill_opacity=0.85, | |||||
| tooltip="PUNTO DI TEST (REALE)" | |||||
| fill_color="#00bcd4", fill_opacity=0.85, tooltip="PUNTO DI TEST (REALE)" | |||||
| ).add_to(m) | ).add_to(m) | ||||
| # 🟠 PUNTO PREDETTO (Arancio) | |||||
| # Marker Predizione | |||||
| if res['x_pred'] != -1.0: | if res['x_pred'] != -1.0: | ||||
| folium.CircleMarker( | folium.CircleMarker( | ||||
| location=p_pred, radius=11, color="#e65100", fill=True, | location=p_pred, radius=11, color="#e65100", fill=True, | ||||
| @@ -137,7 +153,7 @@ def show_test_inference(cfg): | |||||
| tooltip=f"PREDIZIONE (Modello: {res['model_used']})" | tooltip=f"PREDIZIONE (Modello: {res['model_used']})" | ||||
| ).add_to(m) | ).add_to(m) | ||||
| # Linea Errore | |||||
| # Linea di errore (Gialla tratteggiata) | |||||
| folium.PolyLine( | folium.PolyLine( | ||||
| locations=[p_real, p_pred], color="#ffeb3b", | locations=[p_real, p_pred], color="#ffeb3b", | ||||
| weight=4, opacity=0.7, dash_array='8' | weight=4, opacity=0.7, dash_array='8' | ||||
| @@ -60,7 +60,7 @@ infer: | |||||
| refresh_seconds: 10 | refresh_seconds: 10 | ||||
| rssi_max: -25 | rssi_max: -25 | ||||
| rssi_min: -110 | rssi_min: -110 | ||||
| window_seconds: 7 | |||||
| window_seconds: 30 | |||||
| xy_round: 0 | xy_round: 0 | ||||
| maps: | maps: | ||||
| default_dot_size: 20 | default_dot_size: 20 | ||||
| @@ -70,7 +70,7 @@ maps: | |||||
| meta_prefix: meta_ | meta_prefix: meta_ | ||||
| show_grid_default: true | show_grid_default: true | ||||
| ml: | ml: | ||||
| k: 5 | |||||
| k: 3 | |||||
| method: knn | method: knn | ||||
| metric: euclidean | metric: euclidean | ||||
| weights: distance | weights: distance | ||||
| @@ -103,7 +103,7 @@ train: | |||||
| folds: 5 | folds: 5 | ||||
| k_max: 15 | k_max: 15 | ||||
| k_min: 3 | k_min: 3 | ||||
| k: 5 | |||||
| k: 3 | |||||
| metric: euclidean | metric: euclidean | ||||
| weights: distance | weights: distance | ||||
| model_path: /data/model/model.joblib | model_path: /data/model/model.joblib | ||||
| @@ -1,13 +1,13 @@ | |||||
| mac;z;x;y | mac;z;x;y | ||||
| C8:3F:8F:17:DB:35;-1;-1;-1 | |||||
| C3:00:00:39:47:DF;-1;-1;-1 | |||||
| C3:00:00:39:47:C4;-1;1917;650 | |||||
| C3:00:00:39:47:E2;-1;-1;-1 | |||||
| C7:AE:56:1E:38:B7;-1;1917;650 | |||||
| E0:1F:9A:7A:47:D2;-1;-1;-1 | |||||
| C3:00:00:57:B9:D9;-1;1917;650 | |||||
| C3:00:00:57:B9:DB;-1;1917;650 | |||||
| C3:00:00:57:B9:F4;-1;1917;650 | |||||
| C3:00:00:57:B9:DC;-1;1917;650 | |||||
| C3:00:00:57:B9:DD;-1;1917;650 | |||||
| C3:00:00:57:B9:DF;-1;1917;650 | |||||
| c8:3f:8f:17:db:35;-1;-1;-1 | |||||
| c3:00:00:39:47:df;1;2082;1337 | |||||
| c3:00:00:39:47:c4;1;883;1263 | |||||
| c3:00:00:39:47:e2;1;1506;778 | |||||
| c7:ae:56:1e:38:b7;1;2254;672 | |||||
| e0:1f:9a:7a:47:d2;-1;-1;-1 | |||||
| c3:00:00:57:b9:d9;1;2084;1344 | |||||
| c3:00:00:57:b9:db;1;2066;1345 | |||||
| c3:00:00:57:b9:f4;1;1790;989 | |||||
| c3:00:00:57:b9:dc;1;875;1177 | |||||
| c3:00:00:57:b9:dd;1;918;1037 | |||||
| c3:00:00:57:b9:df;1;919;971 | |||||
| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "pixel_ratio": 1.5838698443273016, | |||||
| "calibrated": true, | |||||
| "origin": [ | |||||
| 1299, | |||||
| 3742 | |||||
| ], | |||||
| "grid_size": 50 | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| { | |||||
| "pixel_ratio": 1.5923301441291737, | |||||
| "calibrated": true, | |||||
| "origin": [ | |||||
| 1348, | |||||
| 3537 | |||||
| ], | |||||
| "grid_size": 50 | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| model_camp_00_20260218_114109.joblib | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1096_1145;0;BC-00-21;1096;1145;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1096_1145;0;BC-04-22;1096;1145;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1096_1145;0;BC-08-23;1096;1145;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1096_1145;0;BC-12-24;1096;1145;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1229_709;0;BC-00-21;1229;709;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1229_709;0;BC-04-22;1229;709;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1229_709;0;BC-08-23;1229;709;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1229_709;0;BC-12-24;1229;709;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1438_496;0;BC-00-21;1438;496;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1438_496;0;BC-04-22;1438;496;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1438_496;0;BC-08-23;1438;496;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1438_496;0;BC-12-24;1438;496;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1438_981;0;BC-00-21;1438;981;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1438_981;0;BC-04-22;1438;981;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1438_981;0;BC-08-23;1438;981;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1438_981;0;BC-12-24;1438;981;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1641_1401;0;BC-00-21;1641;1401;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1641_1401;0;BC-04-22;1641;1401;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1641_1401;0;BC-08-23;1641;1401;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1641_1401;0;BC-12-24;1641;1401;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1671_101;0;BC-00-21;1671;101;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1671_101;0;BC-04-22;1671;101;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1671_101;0;BC-08-23;1671;101;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1671_101;0;BC-12-24;1671;101;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_1675_918;0;BC-00-21;1675;918;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_1675_918;0;BC-04-22;1675;918;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_1675_918;0;BC-08-23;1675;918;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_1675_918;0;BC-12-24;1675;918;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2022_700;0;BC-00-21;2022;700;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2022_700;0;BC-04-22;2022;700;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2022_700;0;BC-08-23;2022;700;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2022_700;0;BC-12-24;2022;700;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2041_477;0;BC-00-21;2041;477;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2041_477;0;BC-04-22;2041;477;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2041_477;0;BC-08-23;2041;477;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2041_477;0;BC-12-24;2041;477;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2096_680;0;BC-00-21;2096;680;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2096_680;0;BC-04-22;2096;680;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2096_680;0;BC-08-23;2096;680;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2096_680;0;BC-12-24;2096;680;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2102_676;0;BC-00-21;2102;676;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2102_676;0;BC-04-22;2102;676;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2102_676;0;BC-08-23;2102;676;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2102_676;0;BC-12-24;2102;676;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2107_792;0;BC-00-21;2107;792;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2107_792;0;BC-04-22;2107;792;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2107_792;0;BC-08-23;2107;792;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2107_792;0;BC-12-24;2107;792;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_218_120;0;BC-00-21;218;120;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_218_120;0;BC-04-22;218;120;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_218_120;0;BC-08-23;218;120;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_218_120;0;BC-12-24;218;120;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2244_735;0;BC-00-21;2244;735;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2244_735;0;BC-04-22;2244;735;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2244_735;0;BC-08-23;2244;735;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2244_735;0;BC-12-24;2244;735;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2377_671;0;BC-00-21;2377;671;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2377_671;0;BC-04-22;2377;671;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2377_671;0;BC-08-23;2377;671;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2377_671;0;BC-12-24;2377;671;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2503_523;0;BC-00-21;2503;523;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2503_523;0;BC-04-22;2503;523;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2503_523;0;BC-08-23;2503;523;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2503_523;0;BC-12-24;2503;523;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2702_660;0;BC-00-21;2702;660;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2702_660;0;BC-04-22;2702;660;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2702_660;0;BC-08-23;2702;660;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2702_660;0;BC-12-24;2702;660;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2816_375;0;BC-00-21;2816;375;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2816_375;0;BC-04-22;2816;375;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2816_375;0;BC-08-23;2816;375;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2816_375;0;BC-12-24;2816;375;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_2818_931;0;BC-00-21;2818;931;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_2818_931;0;BC-04-22;2818;931;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_2818_931;0;BC-08-23;2818;931;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_2818_931;0;BC-12-24;2818;931;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_631_93;0;BC-00-21;631;93;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_631_93;0;BC-04-22;631;93;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_631_93;0;BC-08-23;631;93;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_631_93;0;BC-12-24;631;93;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_848_1113;0;BC-00-21;848;1113;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_848_1113;0;BC-04-22;848;1113;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_848_1113;0;BC-08-23;848;1113;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_848_1113;0;BC-12-24;848;1113;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_849_102;0;BC-00-21;849;102;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_849_102;0;BC-04-22;849;102;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_849_102;0;BC-08-23;849;102;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_849_102;0;BC-12-24;849;102;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_0_849_1408;0;BC-00-21;849;1408;0;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_0_849_1408;0;BC-04-22;849;1408;0;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_0_849_1408;0;BC-08-23;849;1408;0;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_0_849_1408;0;BC-12-24;849;1408;0;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1050_1197;1;BC-00-21;1050;1197;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1050_1197;1;BC-04-22;1050;1197;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1050_1197;1;BC-08-23;1050;1197;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1050_1197;1;BC-12-24;1050;1197;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1129_114;1;BC-00-21;1129;114;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1129_114;1;BC-04-22;1129;114;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1129_114;1;BC-08-23;1129;114;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1129_114;1;BC-12-24;1129;114;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1290_1423;1;BC-00-21;1290;1423;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1290_1423;1;BC-04-22;1290;1423;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1290_1423;1;BC-08-23;1290;1423;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1290_1423;1;BC-12-24;1290;1423;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1302_1344;1;BC-00-21;1302;1344;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1302_1344;1;BC-04-22;1302;1344;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1302_1344;1;BC-08-23;1302;1344;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1302_1344;1;BC-12-24;1302;1344;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1363_749;1;BC-00-21;1363;749;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1363_749;1;BC-04-22;1363;749;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1363_749;1;BC-08-23;1363;749;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1363_749;1;BC-12-24;1363;749;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1467_373;1;BC-00-21;1467;373;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1467_373;1;BC-04-22;1467;373;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1467_373;1;BC-08-23;1467;373;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1467_373;1;BC-12-24;1467;373;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_1547_1421;1;BC-00-21;1547;1421;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_1547_1421;1;BC-04-22;1547;1421;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_1547_1421;1;BC-08-23;1547;1421;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_1547_1421;1;BC-12-24;1547;1421;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2211_1431;1;BC-00-21;2211;1431;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2211_1431;1;BC-04-22;2211;1431;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2211_1431;1;BC-08-23;2211;1431;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2211_1431;1;BC-12-24;2211;1431;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2219_440;1;BC-00-21;2219;440;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2219_440;1;BC-04-22;2219;440;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2219_440;1;BC-08-23;2219;440;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2219_440;1;BC-12-24;2219;440;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2248_980;1;BC-00-21;2248;980;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2248_980;1;BC-04-22;2248;980;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2248_980;1;BC-08-23;2248;980;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2248_980;1;BC-12-24;2248;980;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2250_742;1;BC-00-21;2250;742;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2250_742;1;BC-04-22;2250;742;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2250_742;1;BC-08-23;2250;742;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2250_742;1;BC-12-24;2250;742;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2628_1116;1;BC-00-21;2628;1116;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2628_1116;1;BC-04-22;2628;1116;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2628_1116;1;BC-08-23;2628;1116;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2628_1116;1;BC-12-24;2628;1116;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2629_652;1;BC-00-21;2629;652;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2629_652;1;BC-04-22;2629;652;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2629_652;1;BC-08-23;2629;652;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2629_652;1;BC-12-24;2629;652;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2811_921;1;BC-00-21;2811;921;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2811_921;1;BC-04-22;2811;921;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2811_921;1;BC-08-23;2811;921;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2811_921;1;BC-12-24;2811;921;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_2880_519;1;BC-00-21;2880;519;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_2880_519;1;BC-04-22;2880;519;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_2880_519;1;BC-08-23;2880;519;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_2880_519;1;BC-12-24;2880;519;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_852_1106;1;BC-00-21;852;1106;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_852_1106;1;BC-04-22;852;1106;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_852_1106;1;BC-08-23;852;1106;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_852_1106;1;BC-12-24;852;1106;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_854_1111;1;BC-00-21;854;1111;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_854_1111;1;BC-04-22;854;1111;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_854_1111;1;BC-08-23;854;1111;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_854_1111;1;BC-12-24;854;1111;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_855_745;1;BC-00-21;855;745;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_855_745;1;BC-04-22;855;745;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_855_745;1;BC-08-23;855;745;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_855_745;1;BC-12-24;855;745;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-21_1_988_802;1;BC-00-21;988;802;1;BC-00-21;C3:00:00:57:B9:E6 | |||||
| BC-04-22_1_988_802;1;BC-04-22;988;802;1;BC-04-22;C3:00:00:57:B9:D4 | |||||
| BC-08-23_1_988_802;1;BC-08-23;988;802;1;BC-08-23;C3:00:00:57:B9:E8 | |||||
| BC-12-24_1_988_802;1;BC-12-24;988;802;1;BC-12-24;C3:00:00:57:B9:F1 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1421_1424;0;BC-00-25;1421;1424;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1421_1424;0;BC-04-26;1421;1424;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1421_1424;0;BC-08-27;1421;1424;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1421_1424;0;BC-12-28;1421;1424;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1438_829;0;BC-00-25;1438;829;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1438_829;0;BC-04-26;1438;829;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1438_829;0;BC-08-27;1438;829;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1438_829;0;BC-12-28;1438;829;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1493_797;0;BC-00-25;1493;797;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1493_797;0;BC-04-26;1493;797;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1493_797;0;BC-08-27;1493;797;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1493_797;0;BC-12-28;1493;797;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1557_299;0;BC-00-25;1557;299;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1557_299;0;BC-04-26;1557;299;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1557_299;0;BC-08-27;1557;299;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1557_299;0;BC-12-28;1557;299;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1673_498;0;BC-00-25;1673;498;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1673_498;0;BC-04-26;1673;498;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1673_498;0;BC-08-27;1673;498;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1673_498;0;BC-12-28;1673;498;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1859_51;0;BC-00-25;1859;51;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1859_51;0;BC-04-26;1859;51;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1859_51;0;BC-08-27;1859;51;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1859_51;0;BC-12-28;1859;51;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1861_1118;0;BC-00-25;1861;1118;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1861_1118;0;BC-04-26;1861;1118;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1861_1118;0;BC-08-27;1861;1118;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1861_1118;0;BC-12-28;1861;1118;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_1958_250;0;BC-00-25;1958;250;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_1958_250;0;BC-04-26;1958;250;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_1958_250;0;BC-08-27;1958;250;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_1958_250;0;BC-12-28;1958;250;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2004_1404;0;BC-00-25;2004;1404;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2004_1404;0;BC-04-26;2004;1404;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2004_1404;0;BC-08-27;2004;1404;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2004_1404;0;BC-12-28;2004;1404;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2025_791;0;BC-00-25;2025;791;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2025_791;0;BC-04-26;2025;791;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2025_791;0;BC-08-27;2025;791;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2025_791;0;BC-12-28;2025;791;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2106_789;0;BC-00-25;2106;789;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2106_789;0;BC-04-26;2106;789;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2106_789;0;BC-08-27;2106;789;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2106_789;0;BC-12-28;2106;789;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2114_688;0;BC-00-25;2114;688;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2114_688;0;BC-04-26;2114;688;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2114_688;0;BC-08-27;2114;688;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2114_688;0;BC-12-28;2114;688;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2192_100;0;BC-00-25;2192;100;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2192_100;0;BC-04-26;2192;100;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2192_100;0;BC-08-27;2192;100;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2192_100;0;BC-12-28;2192;100;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2193_371;0;BC-00-25;2193;371;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2193_371;0;BC-04-26;2193;371;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2193_371;0;BC-08-27;2193;371;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2193_371;0;BC-12-28;2193;371;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2230_927;0;BC-00-25;2230;927;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2230_927;0;BC-04-26;2230;927;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2230_927;0;BC-08-27;2230;927;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2230_927;0;BC-12-28;2230;927;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2242_740;0;BC-00-25;2242;740;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2242_740;0;BC-04-26;2242;740;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2242_740;0;BC-08-27;2242;740;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2242_740;0;BC-12-28;2242;740;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2373_789;0;BC-00-25;2373;789;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2373_789;0;BC-04-26;2373;789;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2373_789;0;BC-08-27;2373;789;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2373_789;0;BC-12-28;2373;789;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2412_679;0;BC-00-25;2412;679;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2412_679;0;BC-04-26;2412;679;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2412_679;0;BC-08-27;2412;679;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2412_679;0;BC-12-28;2412;679;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2414_796;0;BC-00-25;2414;796;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2414_796;0;BC-04-26;2414;796;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2414_796;0;BC-08-27;2414;796;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2414_796;0;BC-12-28;2414;796;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2489_1157;0;BC-00-25;2489;1157;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2489_1157;0;BC-04-26;2489;1157;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2489_1157;0;BC-08-27;2489;1157;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2489_1157;0;BC-12-28;2489;1157;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2505_220;0;BC-00-25;2505;220;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2505_220;0;BC-04-26;2505;220;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2505_220;0;BC-08-27;2505;220;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2505_220;0;BC-12-28;2505;220;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2812_1418;0;BC-00-25;2812;1418;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2812_1418;0;BC-04-26;2812;1418;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2812_1418;0;BC-08-27;2812;1418;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2812_1418;0;BC-12-28;2812;1418;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2894_522;0;BC-00-25;2894;522;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2894_522;0;BC-04-26;2894;522;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2894_522;0;BC-08-27;2894;522;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2894_522;0;BC-12-28;2894;522;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_2894_776;0;BC-00-25;2894;776;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_2894_776;0;BC-04-26;2894;776;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_2894_776;0;BC-08-27;2894;776;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_2894_776;0;BC-12-28;2894;776;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_528_1457;0;BC-00-25;528;1457;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_528_1457;0;BC-04-26;528;1457;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_528_1457;0;BC-08-27;528;1457;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_528_1457;0;BC-12-28;528;1457;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_848_842;0;BC-00-25;848;842;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_848_842;0;BC-04-26;848;842;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_848_842;0;BC-08-27;848;842;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_848_842;0;BC-12-28;848;842;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_852_707;0;BC-00-25;852;707;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_852_707;0;BC-04-26;852;707;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_852_707;0;BC-08-27;852;707;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_852_707;0;BC-12-28;852;707;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_0_993_456;0;BC-00-25;993;456;0;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_0_993_456;0;BC-04-26;993;456;0;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_0_993_456;0;BC-08-27;993;456;0;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_0_993_456;0;BC-12-28;993;456;0;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1150_518;1;BC-00-25;1150;518;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1150_518;1;BC-04-26;1150;518;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1150_518;1;BC-08-27;1150;518;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1150_518;1;BC-12-28;1150;518;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1150_743;1;BC-00-25;1150;743;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1150_743;1;BC-04-26;1150;743;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1150_743;1;BC-08-27;1150;743;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1150_743;1;BC-12-28;1150;743;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1298_1107;1;BC-00-25;1298;1107;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1298_1107;1;BC-04-26;1298;1107;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1298_1107;1;BC-08-27;1298;1107;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1298_1107;1;BC-12-28;1298;1107;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1491_115;1;BC-00-25;1491;115;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1491_115;1;BC-04-26;1491;115;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1491_115;1;BC-08-27;1491;115;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1491_115;1;BC-12-28;1491;115;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1735_1415;1;BC-00-25;1735;1415;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1735_1415;1;BC-04-26;1735;1415;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1735_1415;1;BC-08-27;1735;1415;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1735_1415;1;BC-12-28;1735;1415;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_1979_582;1;BC-00-25;1979;582;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_1979_582;1;BC-04-26;1979;582;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_1979_582;1;BC-08-27;1979;582;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_1979_582;1;BC-12-28;1979;582;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_2084_1179;1;BC-00-25;2084;1179;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_2084_1179;1;BC-04-26;2084;1179;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_2084_1179;1;BC-08-27;2084;1179;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_2084_1179;1;BC-12-28;2084;1179;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_2413_793;1;BC-00-25;2413;793;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_2413_793;1;BC-04-26;2413;793;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_2413_793;1;BC-08-27;2413;793;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_2413_793;1;BC-12-28;2413;793;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_2415_672;1;BC-00-25;2415;672;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_2415_672;1;BC-04-26;2415;672;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_2415_672;1;BC-08-27;2415;672;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_2415_672;1;BC-12-28;2415;672;1;BC-12-28;C3:00:00:57:B9:F6 | |||||
| @@ -0,0 +1,5 @@ | |||||
| Position;Floor;RoomName;X;Y;Z;BeaconName;MAC | |||||
| BC-00-25_1_2433_931;1;BC-00-25;2433;931;1;BC-00-25;C3:00:00:57:B9:E7 | |||||
| BC-04-26_1_2433_931;1;BC-04-26;2433;931;1;BC-04-26;C3:00:00:57:B9:D6 | |||||
| BC-08-27_1_2433_931;1;BC-08-27;2433;931;1;BC-08-27;C3:00:00:57:B9:D7 | |||||
| BC-12-28_1_2433_931;1;BC-12-28;2433;931;1;BC-12-28;C3:00:00:57:B9:F6 | |||||