Add Garmin/Fitbit OUIs; consolidate personal device detection into is_personal_device()
Garmin and Fitbit wearables use globally-assigned OUI MACs (no private addressing) so the LAA bit check misses them. Added their OUIs to MOBILE_DEVICE_OUIS. Consolidated all three detection signals into is_personal_device(): 1. Explicit config (device_labels, personal_devices) 2. OUI table (phones + Garmin/Fitbit) 3. LAA bit (iOS 14+/Android 10+ private MACs) Occupancy _is_personal now delegates to is_personal_device instead of duplicating logic.
This commit is contained in:
+19
-19
@@ -116,6 +116,10 @@ MOBILE_DEVICE_OUIS = {
|
||||
"001555", "001620", "001645", "001693", "0016B8", "001730", "0017F6",
|
||||
"001822", "001888", "0018F8", "001913", "001985", "001A45", "001A92",
|
||||
"001AFC",
|
||||
# Garmin (GPS watches/wearables with WiFi — do not use private MACs)
|
||||
"086698", "F8B5AB", "E87941", "1430C6", "801F02", "68F3B1", "283B96", "24DEC6",
|
||||
# Fitbit (wearables with WiFi sync — do not use private MACs)
|
||||
"E890AC", "88A70E", "E407AA", "A41566", "008B17",
|
||||
}
|
||||
|
||||
# Personal device tracking
|
||||
@@ -728,25 +732,30 @@ def load_env():
|
||||
|
||||
def is_personal_device(mac: str) -> bool:
|
||||
"""
|
||||
Check if MAC is a personal device (mobile OUI or manually configured).
|
||||
Returns True if device OUI is in MOBILE_DEVICE_OUIS OR mac is in personal_devices set.
|
||||
Check if MAC belongs to a phone or wearable.
|
||||
|
||||
Three signals, checked in order:
|
||||
1. Explicit config — device_labels or personal_devices (operator-configured)
|
||||
2. OUI table — MOBILE_DEVICE_OUIS covers phones + Garmin/Fitbit (no private MACs)
|
||||
3. LAA bit — locally-administered bit set = iOS 14+/Android 10+ private MAC = phone/watch
|
||||
"""
|
||||
# Normalize MAC
|
||||
mac_normalized = _normalize_mac(mac)
|
||||
if not mac_normalized: # Skip invalid MACs
|
||||
if not mac_normalized:
|
||||
return False
|
||||
|
||||
oui = mac_normalized[:6]
|
||||
|
||||
# Check personal_devices set first (manually configured)
|
||||
with personal_lock:
|
||||
if mac_normalized in personal_devices:
|
||||
if mac_normalized in personal_devices or mac_normalized in device_labels:
|
||||
return True
|
||||
|
||||
# Check against MOBILE_DEVICE_OUIS
|
||||
if oui in MOBILE_DEVICE_OUIS:
|
||||
return True
|
||||
|
||||
# Locally-administered bit: modern phones randomize MACs; TVs/infra don't
|
||||
try:
|
||||
return bool(int(mac_normalized[:2], 16) & 0x02)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
@@ -901,18 +910,9 @@ def _update_occupancy_state_unlocked() -> None:
|
||||
"""
|
||||
global location_occupancy
|
||||
|
||||
# Detect phones/wearables via LAA MAC (locally-administered bit set in first octet).
|
||||
# iOS 14+, Android 10+ all use private/random MACs — this bit is always set.
|
||||
# TVs, gateways, smart home devices use globally-assigned OUI MACs (bit clear).
|
||||
# Explicit device_labels / personal_devices also qualify (older phones, pre-config).
|
||||
def _is_personal(mac: str) -> bool:
|
||||
n = _normalize_mac(mac)
|
||||
if n in device_labels or n in personal_devices:
|
||||
return True
|
||||
try:
|
||||
return bool(int(n[:2], 16) & 0x02) # locally-administered bit
|
||||
except ValueError:
|
||||
return False
|
||||
# Delegate to is_personal_device: checks explicit config, OUI table (phones + Garmin/Fitbit),
|
||||
# and LAA bit (iOS 14+/Android 10+ private MACs). No pre-config needed for modern phones.
|
||||
_is_personal = is_personal_device
|
||||
|
||||
active_enrolled_count = 0
|
||||
for mac, dev in known_devices.items():
|
||||
|
||||
Reference in New Issue
Block a user