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:
+21
-21
@@ -116,6 +116,10 @@ MOBILE_DEVICE_OUIS = {
|
|||||||
"001555", "001620", "001645", "001693", "0016B8", "001730", "0017F6",
|
"001555", "001620", "001645", "001693", "0016B8", "001730", "0017F6",
|
||||||
"001822", "001888", "0018F8", "001913", "001985", "001A45", "001A92",
|
"001822", "001888", "0018F8", "001913", "001985", "001A45", "001A92",
|
||||||
"001AFC",
|
"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
|
# Personal device tracking
|
||||||
@@ -728,26 +732,31 @@ def load_env():
|
|||||||
|
|
||||||
def is_personal_device(mac: str) -> bool:
|
def is_personal_device(mac: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if MAC is a personal device (mobile OUI or manually configured).
|
Check if MAC belongs to a phone or wearable.
|
||||||
Returns True if device OUI is in MOBILE_DEVICE_OUIS OR mac is in personal_devices set.
|
|
||||||
|
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)
|
mac_normalized = _normalize_mac(mac)
|
||||||
if not mac_normalized: # Skip invalid MACs
|
if not mac_normalized:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
oui = mac_normalized[:6]
|
oui = mac_normalized[:6]
|
||||||
|
|
||||||
# Check personal_devices set first (manually configured)
|
|
||||||
with personal_lock:
|
with personal_lock:
|
||||||
if mac_normalized in personal_devices:
|
if mac_normalized in personal_devices or mac_normalized in device_labels:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Check against MOBILE_DEVICE_OUIS
|
|
||||||
if oui in MOBILE_DEVICE_OUIS:
|
if oui in MOBILE_DEVICE_OUIS:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
# Locally-administered bit: modern phones randomize MACs; TVs/infra don't
|
||||||
|
try:
|
||||||
|
return bool(int(mac_normalized[:2], 16) & 0x02)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def lookup_oui(mac: str) -> str:
|
def lookup_oui(mac: str) -> str:
|
||||||
@@ -901,18 +910,9 @@ def _update_occupancy_state_unlocked() -> None:
|
|||||||
"""
|
"""
|
||||||
global location_occupancy
|
global location_occupancy
|
||||||
|
|
||||||
# Detect phones/wearables via LAA MAC (locally-administered bit set in first octet).
|
# Delegate to is_personal_device: checks explicit config, OUI table (phones + Garmin/Fitbit),
|
||||||
# iOS 14+, Android 10+ all use private/random MACs — this bit is always set.
|
# and LAA bit (iOS 14+/Android 10+ private MACs). No pre-config needed for modern phones.
|
||||||
# TVs, gateways, smart home devices use globally-assigned OUI MACs (bit clear).
|
_is_personal = is_personal_device
|
||||||
# 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
|
|
||||||
|
|
||||||
active_enrolled_count = 0
|
active_enrolled_count = 0
|
||||||
for mac, dev in known_devices.items():
|
for mac, dev in known_devices.items():
|
||||||
|
|||||||
Reference in New Issue
Block a user