Fix arrival suppression when device returns during departure debounce

Watchdog-triggered departures set departing=True after 15 min of silence.
If the device returned during the 15-min debounce window, on_arrival()
treated it as a rapid flap and suppressed the ARRIVED alert — even though
the device had been genuinely absent for 15+ minutes.

Store depart_initiated timestamp when setting departing=True. On re-arrival,
check the gap: < 120s = true rapid flap (transient Netlink/DHCP event),
suppress as before. >= 120s = real absence confirmed by watchdog, pop the
device from known_devices and let it fall through to the new-device path
so the ARRIVED alert fires.

Also removes debug traceback instrumentation left in _update_last_seen
from the prior investigation session.
This commit is contained in:
Cobra
2026-04-14 19:49:33 -04:00
parent 6cf44f57d1
commit 319f11c1dc
+12 -3
View File
@@ -985,15 +985,23 @@ def on_arrival(mac: str, ip: str, hostname: str = "") -> None:
with known_lock: with known_lock:
with departure_lock: with departure_lock:
# Check if device marked departing (rapid flap recovery) # Check if device marked departing
if mac in known_devices and known_devices[mac].get('departing'): if mac in known_devices and known_devices[mac].get('departing'):
# Device returned during departure window — cancel timer, clear flag, no alert depart_initiated = known_devices[mac].get('depart_initiated', now)
absence_secs = now - depart_initiated
if absence_secs < 120:
# True rapid flap (transient Netlink/DHCP event) — suppress
known_devices[mac]['departing'] = False known_devices[mac]['departing'] = False
with occupancy_lock: with occupancy_lock:
_update_occupancy_state_unlocked() _update_occupancy_state_unlocked()
last_departed_time.pop(mac, None) last_departed_time.pop(mac, None)
logging.debug(f"Device {mac} re-arrived during departure window — suppressed alert") logging.debug(f"Device {mac} rapid-flap suppressed ({int(absence_secs)}s)")
return return
else:
# Watchdog-confirmed absence — real re-arrival, let it alert
logging.debug(f"Device {mac} returning after {int(absence_secs)}s absence — treating as arrival")
known_devices.pop(mac)
last_departed_time.pop(mac, None)
# Log if we cancelled a timer # Log if we cancelled a timer
if timers_to_cancel: if timers_to_cancel:
@@ -1079,6 +1087,7 @@ def on_departure(mac: str) -> None:
dev_copy = dev.copy() dev_copy = dev.copy()
# Mark departing instead of popping — allows re-arrival to detect and cancel timer # Mark departing instead of popping — allows re-arrival to detect and cancel timer
known_devices[mac]['departing'] = True known_devices[mac]['departing'] = True
known_devices[mac]['depart_initiated'] = time.time()
# Start 15-minute debounce timer # Start 15-minute debounce timer
# Extract timer to cancel outside of lock to avoid deadlock # Extract timer to cancel outside of lock to avoid deadlock