88
99from homeassistant .core import HomeAssistant , callback
1010from homeassistant .helpers .update_coordinator import DataUpdateCoordinator , UpdateFailed
11- from homeassistant .helpers .dispatcher import async_dispatcher_connect , async_dispatcher_send
11+ from homeassistant .helpers .dispatcher import async_dispatcher_send
1212from homeassistant .helpers .entity_platform import AddEntitiesCallback
1313from homeassistant .helpers .entity_registry import async_get as async_get_entity_registry
14- from homeassistant .config_entries import ConfigEntry
1514from homeassistant .const import Platform
1615
1716from aiounifi .models .traffic_route import TrafficRoute
2221from aiounifi .models .wlan import Wlan
2322from aiounifi .models .device import Device
2423
25- from .const import DOMAIN , LOGGER , CONF_UPDATE_INTERVAL , DEFAULT_UPDATE_INTERVAL , DEBUG_WEBSOCKET , LOG_TRIGGERS
24+ from .const import DOMAIN , LOGGER , DEFAULT_UPDATE_INTERVAL , LOG_TRIGGERS
2625from .udm import UDMAPI
27- from .websocket import SIGNAL_WEBSOCKET_MESSAGE , UnifiRuleWebsocket
28- from .helpers .rule import get_rule_id , get_rule_name , get_rule_enabled , get_child_unique_id
26+ from .websocket import UnifiRuleWebsocket
27+ from .helpers .rule import get_rule_id , get_child_unique_id
2928from .utils .logger import log_data , log_websocket
3029from .models .firewall_rule import FirewallRule
3130from .models .qos_rule import QoSRule
3231from .models .vpn_config import VPNConfig
32+ from .models .port_profile import PortProfile
33+ from .models .network import NetworkConf
3334
3435# This is a fallback if no update_interval is specified
3536SCAN_INTERVAL = timedelta (seconds = 60 )
@@ -110,6 +111,8 @@ def __init__(
110111 self .vpn_clients : List [VPNConfig ] = []
111112 self .vpn_servers : List [VPNConfig ] = []
112113 self .devices : List [Device ] = [] # For LED toggle switches
114+ self .port_profiles : List [PortProfile ] = []
115+ self .networks : List [NetworkConf ] = []
113116
114117 # For dynamic entity creation
115118 self .async_add_entities_callback : AddEntitiesCallback | None = None
@@ -370,6 +373,8 @@ async def _async_update_data(self) -> Dict[str, List[Any]]:
370373 "vpn_clients" : [],
371374 "vpn_servers" : [],
372375 "devices" : [],
376+ "port_profiles" : [],
377+ "networks" : [],
373378 }
374379
375380 # Store the previous data to detect deletions and protect against API failures
@@ -418,7 +423,7 @@ async def _async_update_data(self) -> Dict[str, List[Any]]:
418423 LOGGER .warning ("Authentication failure detected during initial fetch: %s" , error_msg )
419424 # Trigger auth recovery but continue trying other endpoints
420425 if hasattr (self .api , "handle_auth_failure" ):
421- recovery_task = asyncio .create_task (self .api .handle_auth_failure (error_msg ))
426+ asyncio .create_task (self .api .handle_auth_failure (error_msg ))
422427
423428 # Preserve previous port forwards data if available
424429 if previous_data and "port_forwards" in previous_data and previous_data ["port_forwards" ]:
@@ -467,6 +472,14 @@ async def _async_update_data(self) -> Dict[str, List[Any]]:
467472
468473 # Then devices (for LED switches)
469474 await self ._update_devices_in_dict (rules_data )
475+ await asyncio .sleep (api_call_delay )
476+
477+ # Then port profiles
478+ await self ._update_port_profiles_in_dict (rules_data )
479+ await asyncio .sleep (api_call_delay )
480+
481+ # Then networks
482+ await self ._update_networks_in_dict (rules_data )
470483
471484 # Verify the data is valid - check if we have at least some data in key categories
472485 # This helps prevent entity removal during temporary API errors
@@ -582,6 +595,8 @@ async def _async_update_data(self) -> Dict[str, List[Any]]:
582595 self .vpn_clients = rules_data .get ("vpn_clients" , [])
583596 self .vpn_servers = rules_data .get ("vpn_servers" , [])
584597 self .devices = rules_data .get ("devices" , [])
598+ self .port_profiles = rules_data .get ("port_profiles" , [])
599+ self .networks = rules_data .get ("networks" , [])
585600
586601 LOGGER .info ("Rule collections after refresh: Port Forwards=%d, Traffic Routes=%d, Firewall Policies=%d, Traffic Rules=%d, Legacy Firewall Rules=%d, WLANs=%d, QoS Rules=%d, VPN Clients=%d, VPN Servers=%d, Devices=%d" ,
587602 len (self .port_forwards ),
@@ -601,10 +616,11 @@ async def _async_update_data(self) -> Dict[str, List[Any]]:
601616 LOGGER .error ("Error updating coordinator data: %s" , err )
602617
603618 # Check if this is an authentication error
604- auth_error = False
619+ # Track auth errors for control flow only (no use afterwards)
620+ _auth_error = False
605621 error_str = str (err ).lower ()
606622 if "401 unauthorized" in error_str or "403 forbidden" in error_str :
607- auth_error = True
623+ _auth_error = True
608624 self ._auth_failures += 1
609625 self ._authentication_in_progress = True
610626 try :
@@ -659,6 +675,8 @@ def _check_for_deleted_rules(self, new_data: Dict[str, List[Any]]) -> None:
659675 "wlans" ,
660676 "vpn_clients" ,
661677 "vpn_servers" ,
678+ "port_profiles" ,
679+ "networks" ,
662680 ]
663681
664682 for rule_type in all_rule_sources_types :
@@ -845,6 +863,49 @@ async def _update_devices_in_dict(self, data: Dict[str, List[Any]]) -> None:
845863 if not hasattr (self , 'devices' ):
846864 self .devices = []
847865
866+ async def _update_port_profiles_in_dict (self , data : Dict [str , List [Any ]]) -> None :
867+ """Update port profiles in the data dictionary and convert to typed objects."""
868+ try :
869+ LOGGER .info ("Fetching port profiles..." )
870+ future = await self .api .queue_api_operation (self .api .get_port_profiles )
871+ profiles = await future if hasattr (future , "__await__" ) else future
872+ typed : List [PortProfile ] = []
873+ for item in profiles or []:
874+ try :
875+ typed .append (PortProfile (item ))
876+ except Exception as err :
877+ LOGGER .warning ("Error converting port profile: %s" , err )
878+ data ["port_profiles" ] = typed
879+ self .port_profiles = typed
880+ LOGGER .info ("Updated %d port profiles" , len (typed ))
881+ except Exception as err :
882+ LOGGER .error ("Failed to update port profiles: %s" , err )
883+ data ["port_profiles" ] = []
884+ if not hasattr (self , 'port_profiles' ):
885+ self .port_profiles = []
886+
887+ async def _update_networks_in_dict (self , data : Dict [str , List [Any ]]) -> None :
888+ """Update networks list in the data dictionary and convert to typed objects."""
889+ try :
890+ LOGGER .info ("Fetching networks..." )
891+ future = await self .api .queue_api_operation (self .api .get_networks )
892+ networks = await future if hasattr (future , "__await__" ) else future
893+ typed : List [NetworkConf ] = []
894+ for item in networks or []:
895+ try :
896+ # item is already NetworkConf from API layer
897+ typed .append (item )
898+ except Exception as err :
899+ LOGGER .warning ("Error converting network: %s" , err )
900+ data ["networks" ] = typed
901+ self .networks = typed
902+ LOGGER .info ("Updated %d networks" , len (typed ))
903+ except Exception as err :
904+ LOGGER .error ("Failed to update networks: %s" , err )
905+ data ["networks" ] = []
906+ if not hasattr (self , 'networks' ):
907+ self .networks = []
908+
848909 async def _update_rule_type (self , rule_type : str , fetch_method : Callable ) -> None :
849910 """Update a specific rule type in self.data.
850911
@@ -994,7 +1055,7 @@ async def _force_refresh_with_cache_clear(self) -> None:
9941055 log_websocket ("Starting forced refresh after rule change detected" )
9951056
9961057 # Store previous data for deletion detection
997- previous_data = self .data .copy () if self .data else {}
1058+ _unused_previous = self .data .copy () if self .data else {}
9981059
9991060 # Clear the API cache to ensure we get fresh data
10001061 # But do so without disrupting authentication
0 commit comments