From ba18b2fba8274e58f5f4870602216911bfd2a1b7 Mon Sep 17 00:00:00 2001 From: Cobra Date: Tue, 14 Apr 2026 14:46:18 -0400 Subject: [PATCH] =?UTF-8?q?Add=20device=20labels=20to=20occupancy=20alerts?= =?UTF-8?q?=20=E2=80=94=20include=20who=20is=20present?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NET_ALERTER_DEVICE_LABELS=MAC=Name,MAC=Name maps known devices to friendly names. OCCUPIED alerts now show which devices are present. Falls back to vendor string then MAC tail if no label configured. --- net_alerter/net_alerter.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/net_alerter/net_alerter.py b/net_alerter/net_alerter.py index b39710a..12c8cc2 100644 --- a/net_alerter/net_alerter.py +++ b/net_alerter/net_alerter.py @@ -121,6 +121,7 @@ MOBILE_DEVICE_OUIS = { # Personal device tracking personal_devices = set() # {mac: ...} of detected personal device MACs personal_names = set() # {name: ...} device names for auto-discovery via mDNS +device_labels: dict = {} # {normalized_mac: friendly_name} from NET_ALERTER_DEVICE_LABELS personal_lock = threading.Lock() location_occupancy = "UNKNOWN" # Current location state: VACANT, OCCUPIED, UNKNOWN occupancy_lock = threading.Lock() @@ -664,6 +665,17 @@ def load_personal_macs_from_env() -> None: for name in names: personal_names.add(name) + # Load device labels (MAC=Name pairs for readable alerts) + labels_str = os.getenv("NET_ALERTER_DEVICE_LABELS", "") + if labels_str.strip(): + for pair in labels_str.split(","): + if "=" not in pair: + continue + mac_part, _, name_part = pair.partition("=") + normalized = _normalize_mac(mac_part.strip()) + if normalized: + device_labels[normalized] = name_part.strip() + # Log summary if personal_devices or personal_names: logging.info(f"Loaded {len(personal_devices)} personal device MAC(s) and {len(personal_names)} personal name(s)") @@ -900,7 +912,15 @@ def _update_occupancy_state_unlocked() -> None: # Fire alert on all transitions including startup UNKNOWN → OCCUPIED if new_occupancy != location_occupancy: - msg = f"[NET] Location: {new_occupancy}" + if new_occupancy == "OCCUPIED": + present = [] + for mac, dev in known_devices.items(): + if not dev.get('departing', False) and dev['ip'] not in infrastructure_ips: + label = device_labels.get(mac) or dev.get('vendor') or mac[-8:] + present.append(label) + msg = f"[NET] Location: OCCUPIED ({', '.join(present)})" + else: + msg = f"[NET] Location: VACANT" logging.info(msg) send_alert(msg)