Skip to content

Commit b234af5

Browse files
committed
Add device LED support in trigger and websocket handling
- Introduced RULE_TYPE_DEVICES to manage device LED changes in trigger.py. - Enhanced logging for LED state changes in websocket.py, including detection of LED overrides. - Updated translation files to include device LED terminology for better user experience.
1 parent 7d98e15 commit b234af5

3 files changed

Lines changed: 42 additions & 11 deletions

File tree

custom_components/unifi_network_rules/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"vpn_servers": "VPN Server",
4141
"legacy_firewall_rules": "Legacy Firewall Rule",
4242
"traffic_rules": "Traffic Rule",
43-
"wlans": "WLAN"
43+
"wlans": "WLAN",
44+
"devices": "Device LED"
4445
}
4546
},
4647
"triggers": {

custom_components/unifi_network_rules/trigger.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
RULE_TYPE_LEGACY_FIREWALL_RULE = "legacy_firewall_rules"
2323
RULE_TYPE_TRAFFIC_RULE = "traffic_rules"
2424
RULE_TYPE_WLAN = "wlans"
25+
RULE_TYPE_DEVICES = "devices"
2526

2627
# Trigger types
2728
TRIGGER_RULE_ENABLED = "rule_enabled"
@@ -51,6 +52,7 @@
5152
RULE_TYPE_LEGACY_FIREWALL_RULE: ["firewallrule", "legacy", "accept", "reject"],
5253
RULE_TYPE_TRAFFIC_RULE: ["trafficrule", "traffic_rule"],
5354
RULE_TYPE_WLAN: ["wlan", "wireless", "wifi", "ssid"],
55+
RULE_TYPE_DEVICES: ["device", "led", "led_override", "access_point", "uap"],
5456
}
5557

5658
# Configuration schema for platform triggers
@@ -74,6 +76,7 @@
7476
RULE_TYPE_LEGACY_FIREWALL_RULE,
7577
RULE_TYPE_TRAFFIC_RULE,
7678
RULE_TYPE_WLAN,
79+
RULE_TYPE_DEVICES,
7780
]),
7881
vol.Optional("name_filter"): cv.string,
7982
}
@@ -131,6 +134,15 @@ def get_rule_name_from_data(rule_data: Dict[str, Any], rule_id: str, rule_type:
131134
if "ssid" in rule_data:
132135
return f"WLAN: {rule_data['ssid']}"
133136

137+
elif rule_type == RULE_TYPE_DEVICES:
138+
# For device LED changes, construct a meaningful name
139+
device_name = rule_data.get('name', rule_data.get('device_id', 'Unknown'))
140+
device_model = rule_data.get('model', '')
141+
if device_model:
142+
return f"{device_name} LED ({device_model})"
143+
else:
144+
return f"{device_name} LED"
145+
134146
# For device configuration changes (cfgversion events), extract device info
135147
if rule_id.startswith("device_config_"):
136148
device_part = rule_id.replace("device_config_", "")
@@ -250,11 +262,16 @@ def _handle_websocket_msg(msg: Dict[str, Any]) -> None:
250262
elif isinstance(msg_data, dict):
251263
LOGGER.info("📋 RULE DATA: keys=%s", list(msg_data.keys()))
252264
LOGGER.info("📄 RULE CONTENT: %s", str(msg)[:300] + "..." if len(str(msg)) > 300 else str(msg))
253-
elif msg_type == "device:update" and isinstance(msg_data, list):
265+
elif msg_type in ["device:update", "device:sync"] and isinstance(msg_data, list):
254266
# Very limited logging for device updates to avoid spam
255267
first_item = msg_data[0] if msg_data else {}
256268
if isinstance(first_item, dict) and "cfgversion" in first_item:
257269
LOGGER.debug("📱 CFGVERSION UPDATE: %s", first_item.get("cfgversion"))
270+
# Check for LED changes in device:sync messages
271+
elif isinstance(first_item, dict) and any(led_field in first_item for led_field in ["led_override", "led_override_color", "led_override_color_brightness"]):
272+
led_state = first_item.get("led_override", "unknown")
273+
device_mac = first_item.get("mac", msg.get("meta", {}).get("mac", "unknown"))
274+
LOGGER.info("🔴 TRIGGER LED DETECTION: %s led=%s, device=%s", msg_type, led_state, device_mac)
258275
try:
259276
# Extract message type and data from UniFi OS websocket structure
260277
meta = msg.get("meta", {})
@@ -283,7 +300,7 @@ def _handle_websocket_msg(msg: Dict[str, Any]) -> None:
283300
# Check for cfgversion changes (but only refresh if we have active rule triggers)
284301
has_cfgversion_change = False
285302
cfgversion = None
286-
if msg_type == "device:update" and isinstance(msg_data, list):
303+
if msg_type in ["device:update", "device:sync"] and isinstance(msg_data, list):
287304
for item in msg_data:
288305
if isinstance(item, dict) and "cfgversion" in item:
289306
cfgversion = item.get("cfgversion")
@@ -344,6 +361,9 @@ def _handle_websocket_msg(msg: Dict[str, Any]) -> None:
344361
detected_rule_type = RULE_TYPE_TRAFFIC_ROUTE
345362
elif "ssid" in rule_data:
346363
detected_rule_type = RULE_TYPE_WLAN
364+
elif "led_override" in rule_data or ("type" in rule_data and rule_data.get("type") == "uap") or "mac" in rule_data:
365+
# Device LED detection - check for LED override field, UAP device type, or MAC address (device identifier)
366+
detected_rule_type = RULE_TYPE_DEVICES
347367

348368
# Fallback: Keyword matching with SPECIFIC order (most specific first)
349369
if not detected_rule_type:
@@ -365,6 +385,8 @@ def _handle_websocket_msg(msg: Dict[str, Any]) -> None:
365385
detected_rule_type = RULE_TYPE_TRAFFIC_ROUTE
366386
elif any(keyword in rule_data_str for keyword in ["wlan", "ssid"]):
367387
detected_rule_type = RULE_TYPE_WLAN
388+
elif any(keyword in rule_data_str for keyword in ["led_override", "device", "access_point", "uap"]):
389+
detected_rule_type = RULE_TYPE_DEVICES
368390

369391
if not detected_rule_type:
370392
if LOG_TRIGGERS:

custom_components/unifi_network_rules/websocket.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def _handle_message(self, message: dict[str, Any]) -> None:
293293
rule_keywords = [
294294
"firewall", "rule", "policy", "network", "port", "route", "forward",
295295
"nat", "security", "update", "change", "cfgversion", "provision",
296-
"qos", "quality", "service", "vpn" # Add QoS-related keywords
296+
"qos", "quality", "service", "vpn", "device", "led" # Add QoS and device LED keywords
297297
]
298298

299299
# Extract message type for filtering
@@ -317,25 +317,33 @@ def _handle_message(self, message: dict[str, Any]) -> None:
317317
# Check if message type is rule-related
318318
is_rule_related = any(keyword in msg_type_lower for keyword in rule_keywords)
319319

320-
# Special case for device:update messages - IMPORTANT for rule changes
321-
if msg_type_lower == "device:update":
320+
# Special case for device messages - IMPORTANT for rule and LED changes
321+
if msg_type_lower in ["device:update", "device:sync"]:
322322
data_list = message.get("data", [])
323323
if isinstance(data_list, list):
324324
for item in data_list:
325-
# Any device:update with cfgversion is almost always rule-related
326-
if isinstance(item, dict) and "cfgversion" in item:
325+
# Check for LED changes FIRST (more specific than cfgversion)
326+
if isinstance(item, dict) and any(led_field in item for led_field in ["led_override", "led_override_color", "led_override_color_brightness"]):
327327
is_rule_related = True
328-
log_websocket("Detected config version change in device:update: %s", item.get("cfgversion", ""))
328+
led_state = item.get("led_override", "unknown")
329+
device_mac = item.get("mac", message.get("meta", {}).get("mac", "unknown"))
330+
LOGGER.info("🔴 LED CHANGE DETECTED: %s led=%s, device=%s", msg_type_lower, led_state, device_mac)
331+
log_websocket("Detected LED change in %s: led=%s, device=%s", msg_type_lower, led_state, device_mac)
332+
break
333+
# Any device:update with cfgversion is almost always rule-related (but check LED first)
334+
elif isinstance(item, dict) and "cfgversion" in item:
335+
is_rule_related = True
336+
log_websocket("Detected config version change in %s: %s", msg_type_lower, item.get("cfgversion", ""))
329337
break
330338
# Provisioning events also indicate rule changes
331339
elif isinstance(item, dict) and "provisioned" in str(item).lower():
332340
is_rule_related = True
333-
log_websocket("Detected provisioning event in device:update")
341+
log_websocket("Detected provisioning event in %s", msg_type_lower)
334342
break
335343
# State changes can indicate rule application
336344
elif isinstance(item, dict) and "state" in item:
337345
is_rule_related = True
338-
log_websocket("Detected state change in device:update: %s", item.get("state", ""))
346+
log_websocket("Detected state change in %s: %s", msg_type_lower, item.get("state", ""))
339347
break
340348

341349
# If not found in message type, check data payload for UniFi OS rule identifiers

0 commit comments

Comments
 (0)