From 1e1d4cd0452c4288fd605ba4c619c0864aa4b945 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Fri, 30 Jan 2026 18:12:43 +1100 Subject: [PATCH] better heuristics Signed-off-by: jokob-sk --- back/device_heuristics_rules.json | 36 ++++++++++++++++++++----------- server/scan/device_heuristics.py | 20 ++++++++--------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/back/device_heuristics_rules.json b/back/device_heuristics_rules.json index 419c3da1..3ff278cd 100755 --- a/back/device_heuristics_rules.json +++ b/back/device_heuristics_rules.json @@ -7,6 +7,24 @@ ], "name_pattern": [] }, + { + "dev_type": "Smart Switch", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "003192", "vendor": "TP-Link" }, + { "mac_prefix": "50C7BF", "vendor": "TP-Link" }, + { "mac_prefix": "B04E26", "vendor": "TP-Link" } + ], + "name_pattern": ["hs200", "hs210", "hs220", "ks230", "smart switch", "light switch", "wall switch"] + }, + { + "dev_type": "Smart Plug", + "icon_html": "", + "matching_pattern": [ + { "mac_prefix": "2887BA", "vendor": "TP-Link" } + ], + "name_pattern": ["kp115", "hs100", "hs103", "hs105", "smart plug", "outlet"] + }, { "dev_type": "Access Point", "icon_html": "", @@ -16,15 +34,16 @@ { "mac_prefix": "F4F5D8", "vendor": "TP-Link" }, { "mac_prefix": "F88E85", "vendor": "Netgear" } ], - "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch"] + "name_pattern": ["router", "gateway", "ap", "access point", "access-point", "switch", "sg105", "sg108", "managed switch", "unmanaged switch", "poe switch", "ethernet switch"] }, { "dev_type": "Phone", - "icon_html": "", + "icon_html": "", "matching_pattern": [ { "mac_prefix": "001A79", "vendor": "Apple" }, { "mac_prefix": "B0BE83", "vendor": "Samsung" }, - { "mac_prefix": "BC926B", "vendor": "Motorola" } + { "mac_prefix": "BC926B", "vendor": "Motorola" }, + { "mac_prefix": "", "vendor": "google" } ], "name_pattern": ["iphone", "ipad", "pixel", "galaxy", "redmi"] }, @@ -43,7 +62,7 @@ { "mac_prefix": "BC4C4C", "vendor": "Samsung" } ], "name_pattern": ["tablet", "pad"] - }, + }, { "dev_type": "IoT", "icon_html": "", @@ -164,7 +183,7 @@ "dev_type": "Smart Light", "icon_html": "", "matching_pattern": [], - "name_pattern": ["hue", "lifx", "bulb"] + "name_pattern": ["hue", "lifx", "bulb", "light"] }, { "dev_type": "Smart Home", @@ -189,12 +208,5 @@ "icon_html": "", "matching_pattern": [], "name_pattern": ["doorbell", "lock", "security"] - }, - { - "dev_type": "Smart Light", - "icon_html": "", - "matching_pattern": [ - ], - "name_pattern": ["light","bulb"] } ] diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py index afce2f72..b9c14520 100755 --- a/server/scan/device_heuristics.py +++ b/server/scan/device_heuristics.py @@ -177,12 +177,6 @@ def guess_device_attributes( name = str(name).lower().strip() if name else "(unknown)" mac_clean = mac.replace(":", "").replace("-", "").upper() - # --- Check for Random MAC --- - # If the MAC is randomized (private), skip vendor/heuristics assignment - if is_random_mac(mac): - mylog("debug", f"[guess_device_attributes] Random MAC detected ({mac}); returning defaults to avoid incorrect assignment.") - return default_icon, default_type - # # Internet shortcut # if mac == "INTERNET": # return ICONS.get("globe", default_icon), DEVICE_TYPES.get("Internet", default_type) @@ -190,17 +184,21 @@ def guess_device_attributes( type_ = None icon = None - # --- Strict MAC + vendor rule matching from external file --- + # 1. Try strict MAC match first type_, icon = match_mac_and_vendor(mac_clean, vendor, default_type, default_icon) + # 2. If no strict match, try Name match BEFORE checking for random MAC + if not type_ or type_ == default_type: + type_, icon = match_name(name, default_type, default_icon) + + # 3. Only if it's STILL not found, apply the Random MAC block + if type_ == default_type and is_random_mac(mac): + return default_icon, default_type + # --- Loose Vendor-based fallback --- if not type_ or type_ == default_type: type_, icon = match_vendor(vendor, default_type, default_icon) - # --- Loose Name-based fallback --- - if not type_ or type_ == default_type: - type_, icon = match_name(name, default_type, default_icon) - # --- Loose IP-based fallback --- if (not type_ or type_ == default_type) or (not icon or icon == default_icon): type_, icon = match_ip(ip, default_type, default_icon)