Skip to content

Commit 49ed8a7

Browse files
committed
Enhance API operation queue with retry logic and device LED control
This update introduces a robust retry mechanism for API operations, allowing failed operations to be retried with exponential backoff. A new `QueuedOperation` class encapsulates operation metadata and retry logic. Additionally, the `ApiOperationQueue` now supports a retry queue for managing failed operations. Furthermore, the LED toggle functionality has been extended to support various UniFi devices, allowing for LED state management through the queue system. This includes special handling for device LED toggles and updates to the `UnifiLedToggleSwitch` class to inherit resilient patterns from the base switch class. Key changes: - Added `QueuedOperation` class for managing queued operations with retries. - Enhanced `ApiOperationQueue` to include retry logic and queue management. - Updated LED toggle handling for devices in `switch.py` and `rule.py`. This improves the integration's reliability and flexibility across a wider range of UniFi hardware.
1 parent d4dd109 commit 49ed8a7

3 files changed

Lines changed: 407 additions & 215 deletions

File tree

custom_components/unifi_network_rules/helpers/rule.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from aiounifi.models.port_forward import PortForward
1111
from aiounifi.models.firewall_zone import FirewallZone
1212
from aiounifi.models.wlan import Wlan
13+
from aiounifi.models.device import Device
1314

1415
# Import our custom models
1516
from ..models.firewall_rule import FirewallRule
@@ -35,6 +36,16 @@ def get_rule_enabled(rule: Any) -> bool:
3536
if isinstance(rule, (PortForward, TrafficRoute, FirewallPolicy, TrafficRule, Wlan, QoSRule, VPNConfig)):
3637
return getattr(rule, "enabled", False)
3738

39+
# Special handling for Device LED state
40+
if isinstance(rule, Device):
41+
# For devices, "enabled" means LED is not overridden (default state)
42+
# When led_override is "default", the LED follows normal behavior (enabled)
43+
# When led_override is "off", the LED is disabled
44+
if hasattr(rule, 'raw') and 'led_override' in rule.raw:
45+
led_state = rule.raw.get('led_override')
46+
return led_state != 'off' # True if not explicitly turned off
47+
return True # Default to enabled if no override info
48+
3849
# For dictionaries, try common enabled attributes
3950
if isinstance(rule, dict):
4051
return rule.get("enabled", False)
@@ -63,6 +74,7 @@ def get_rule_id(rule: Any) -> str | None:
6374
Examples:
6475
- unr_policy_123456
6576
- unr_route_abcdef
77+
- unr_device_abc123_led
6678
"""
6779
# Access different rule types and return appropriate ID with type prefix
6880
if isinstance(rule, PortForward):
@@ -134,6 +146,14 @@ def get_rule_id(rule: Any) -> str | None:
134146
LOGGER.warning("VPNConfig without id: %s", rule)
135147
return None
136148

149+
# Handle Device objects for LED switches
150+
if isinstance(rule, Device):
151+
if hasattr(rule, 'mac') and rule.mac:
152+
return f"unr_device_{rule.mac}_led"
153+
else:
154+
LOGGER.warning("Device without mac attribute: %s", rule)
155+
return None
156+
137157
# Dictionary fallback - this should not happen with properly typed data
138158
if isinstance(rule, dict):
139159
_id = rule.get("_id") or rule.get("id")
@@ -165,7 +185,8 @@ def get_rule_prefix(rule_type: str) -> str:
165185
"traffic_rules": "Traffic Rule",
166186
"legacy_firewall_rules": "Legacy Rule",
167187
"qos_rules": "QoS",
168-
"wlans": "WLAN"
188+
"wlans": "WLAN",
189+
"devices": "Device"
169190
}
170191

171192
return rule_types.get(rule_type, "Rule")
@@ -312,6 +333,16 @@ def extract_descriptive_name(rule: Any, coordinator=None) -> str | None:
312333

313334
return None
314335

336+
elif isinstance(rule, Device):
337+
# For devices, return the device name for LED switches
338+
if hasattr(rule, 'name') and rule.name:
339+
return f"{rule.name}"
340+
elif hasattr(rule, 'raw') and 'name' in rule.raw:
341+
return f"{rule.raw['name']}"
342+
elif hasattr(rule, 'mac') and rule.mac:
343+
return f"Device {rule.mac}"
344+
return "Device"
345+
315346
elif isinstance(rule, dict):
316347
# For dictionaries, try common name attributes
317348
return rule.get("name") or rule.get("description")
@@ -341,6 +372,8 @@ def get_rule_name(rule: Any, coordinator=None) -> str | None:
341372
rule_type = "wlans"
342373
elif isinstance(rule, QoSRule):
343374
rule_type = "qos_rules"
375+
elif isinstance(rule, Device):
376+
rule_type = "devices"
344377
elif isinstance(rule, dict) and "type" in rule:
345378
rule_type = rule.get("type")
346379

0 commit comments

Comments
 (0)