1 """Switch platform for UniFi Network integration.
3 Support for controlling power supply of clients which are powered over Ethernet (POE).
4 Support for controlling network access of clients selected in option flow.
5 Support for controlling deep packet inspection (DPI) restriction groups.
6 Support for controlling WLAN availability.
9 from __future__
import annotations
12 from collections.abc
import Callable, Coroutine
13 from dataclasses
import dataclass
14 from typing
import TYPE_CHECKING, Any
17 from aiounifi.interfaces.api_handlers
import ItemEvent
18 from aiounifi.interfaces.clients
import Clients
19 from aiounifi.interfaces.dpi_restriction_groups
import DPIRestrictionGroups
20 from aiounifi.interfaces.outlets
import Outlets
21 from aiounifi.interfaces.port_forwarding
import PortForwarding
22 from aiounifi.interfaces.ports
import Ports
23 from aiounifi.interfaces.traffic_rules
import TrafficRules
24 from aiounifi.interfaces.wlans
import Wlans
25 from aiounifi.models.api
import ApiItemT
26 from aiounifi.models.client
import Client, ClientBlockRequest
27 from aiounifi.models.device
import DeviceSetOutletRelayRequest
28 from aiounifi.models.dpi_restriction_app
import DPIRestrictionAppEnableRequest
29 from aiounifi.models.dpi_restriction_group
import DPIRestrictionGroup
30 from aiounifi.models.event
import Event, EventKey
31 from aiounifi.models.outlet
import Outlet
32 from aiounifi.models.port
import Port
33 from aiounifi.models.port_forward
import PortForward, PortForwardEnableRequest
34 from aiounifi.models.traffic_rule
import TrafficRule, TrafficRuleEnableRequest
35 from aiounifi.models.wlan
import Wlan, WlanEnableRequest
38 DOMAIN
as SWITCH_DOMAIN,
41 SwitchEntityDescription,
49 from .
import UnifiConfigEntry
50 from .const
import ATTR_MANUFACTURER, DOMAIN
as UNIFI_DOMAIN
55 UnifiEntityDescription,
56 async_client_device_info_fn,
57 async_device_available_fn,
58 async_device_device_info_fn,
59 async_wlan_device_info_fn,
61 from .hub
import UnifiHub
63 CLIENT_BLOCKED = (EventKey.WIRED_CLIENT_BLOCKED, EventKey.WIRELESS_CLIENT_BLOCKED)
64 CLIENT_UNBLOCKED = (EventKey.WIRED_CLIENT_UNBLOCKED, EventKey.WIRELESS_CLIENT_UNBLOCKED)
69 """Check if client is allowed."""
70 if obj_id
in hub.config.option_supported_clients:
72 return obj_id
in hub.config.option_block_clients
77 """Calculate if all apps are enabled."""
80 api.dpi_apps[app_id].enabled
81 for app_id
in dpi_group.dpiapp_ids
or []
82 if app_id
in api.dpi_apps
88 """Create device registry entry for DPI group."""
90 entry_type=DeviceEntryType.SERVICE,
91 identifiers={(SWITCH_DOMAIN, f
"unifi_controller_{obj_id}")},
92 manufacturer=ATTR_MANUFACTURER,
93 model=
"UniFi Network",
100 """Create device registry entry for the UniFi Network application."""
101 unique_id = hub.config.entry.unique_id
102 assert unique_id
is not None
104 entry_type=DeviceEntryType.SERVICE,
105 identifiers={(SWITCH_DOMAIN, unique_id)},
106 manufacturer=ATTR_MANUFACTURER,
107 model=
"UniFi Network",
108 name=
"UniFi Network",
113 hub: UnifiHub, obj_id: str, target: bool
115 """Control network access of client."""
116 await hub.api.request(ClientBlockRequest.create(obj_id,
not target))
120 """Enable or disable DPI group."""
121 dpi_group = hub.api.dpi_groups[obj_id]
122 await asyncio.gather(
124 hub.api.request(DPIRestrictionAppEnableRequest.create(app_id, target))
125 for app_id
in dpi_group.dpiapp_ids
or []
132 """Determine if an outlet supports switching."""
133 outlet = hub.api.outlets[obj_id]
134 return outlet.has_relay
or outlet.caps
in (1, 3)
138 """Control outlet relay."""
139 mac, _, index = obj_id.partition(
"_")
140 device = hub.api.devices[mac]
141 await hub.api.request(
142 DeviceSetOutletRelayRequest.create(device,
int(index), target)
147 """Control poe state."""
148 mac, _, index = obj_id.partition(
"_")
149 port = hub.api.ports[obj_id]
150 on_state =
"auto" if port.raw[
"poe_caps"] != 8
else "passthrough"
151 state = on_state
if target
else "off"
152 hub.queue_poe_port_command(mac,
int(index), state)
156 hub: UnifiHub, obj_id: str, target: bool
158 """Control port forward state."""
159 port_forward = hub.api.port_forwarding[obj_id]
160 await hub.api.request(PortForwardEnableRequest.create(port_forward, target))
164 hub: UnifiHub, obj_id: str, target: bool
166 """Control traffic rule state."""
167 traffic_rule = hub.api.traffic_rules[obj_id].raw
168 await hub.api.request(TrafficRuleEnableRequest.create(traffic_rule, target))
170 await hub.api.traffic_rules.update()
174 """Control outlet relay."""
175 await hub.api.request(WlanEnableRequest.create(obj_id, target))
178 @dataclass(frozen=True, kw_only=True)
180 SwitchEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
182 """Class describing UniFi switch entity."""
184 control_fn: Callable[[UnifiHub, str, bool], Coroutine[Any, Any,
None]]
185 is_on_fn: Callable[[UnifiHub, ApiItemT], bool]
188 custom_subscribe: Callable[[aiounifi.Controller], SubscriptionT] |
None =
None
189 """Callback for additional subscriptions to any UniFi handler."""
190 only_event_for_state_change: bool =
False
191 """Use only UniFi events to trigger state changes."""
194 ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
195 UnifiSwitchEntityDescription[Clients, Client](
197 translation_key=
"block_client",
198 device_class=SwitchDeviceClass.SWITCH,
199 entity_category=EntityCategory.CONFIG,
200 allowed_fn=async_block_client_allowed_fn,
201 api_handler_fn=
lambda api: api.clients,
202 control_fn=async_block_client_control_fn,
203 device_info_fn=async_client_device_info_fn,
204 event_is_on=set(CLIENT_UNBLOCKED),
205 event_to_subscribe=CLIENT_BLOCKED + CLIENT_UNBLOCKED,
206 is_on_fn=
lambda hub, client:
not client.blocked,
207 object_fn=
lambda api, obj_id: api.clients[obj_id],
208 only_event_for_state_change=
True,
209 unique_id_fn=
lambda hub, obj_id: f
"block-{obj_id}",
211 UnifiSwitchEntityDescription[DPIRestrictionGroups, DPIRestrictionGroup](
212 key=
"DPI restriction",
213 translation_key=
"dpi_restriction",
214 has_entity_name=
False,
215 entity_category=EntityCategory.CONFIG,
216 allowed_fn=
lambda hub, obj_id: hub.config.option_dpi_restrictions,
217 api_handler_fn=
lambda api: api.dpi_groups,
218 control_fn=async_dpi_group_control_fn,
219 custom_subscribe=
lambda api: api.dpi_apps.subscribe,
220 device_info_fn=async_dpi_group_device_info_fn,
221 is_on_fn=async_dpi_group_is_on_fn,
222 name_fn=
lambda group: group.name,
223 object_fn=
lambda api, obj_id: api.dpi_groups[obj_id],
224 supported_fn=
lambda hub, obj_id:
bool(hub.api.dpi_groups[obj_id].dpiapp_ids),
225 unique_id_fn=
lambda hub, obj_id: obj_id,
227 UnifiSwitchEntityDescription[Outlets, Outlet](
228 key=
"Outlet control",
229 device_class=SwitchDeviceClass.OUTLET,
230 api_handler_fn=
lambda api: api.outlets,
231 available_fn=async_device_available_fn,
232 control_fn=async_outlet_control_fn,
233 device_info_fn=async_device_device_info_fn,
234 is_on_fn=
lambda hub, outlet: outlet.relay_state,
235 name_fn=
lambda outlet: outlet.name,
236 object_fn=
lambda api, obj_id: api.outlets[obj_id],
237 supported_fn=async_outlet_switching_supported_fn,
238 unique_id_fn=
lambda hub, obj_id: f
"outlet-{obj_id}",
240 UnifiSwitchEntityDescription[PortForwarding, PortForward](
241 key=
"Port forward control",
242 translation_key=
"port_forward_control",
243 device_class=SwitchDeviceClass.SWITCH,
244 entity_category=EntityCategory.CONFIG,
245 api_handler_fn=
lambda api: api.port_forwarding,
246 control_fn=async_port_forward_control_fn,
247 device_info_fn=async_unifi_network_device_info_fn,
248 is_on_fn=
lambda hub, port_forward: port_forward.enabled,
249 name_fn=
lambda port_forward: f
"{port_forward.name}",
250 object_fn=
lambda api, obj_id: api.port_forwarding[obj_id],
251 unique_id_fn=
lambda hub, obj_id: f
"port_forward-{obj_id}",
253 UnifiSwitchEntityDescription[TrafficRules, TrafficRule](
254 key=
"Traffic rule control",
255 translation_key=
"traffic_rule_control",
256 device_class=SwitchDeviceClass.SWITCH,
257 entity_category=EntityCategory.CONFIG,
258 api_handler_fn=
lambda api: api.traffic_rules,
259 control_fn=async_traffic_rule_control_fn,
260 device_info_fn=async_unifi_network_device_info_fn,
261 is_on_fn=
lambda hub, traffic_rule: traffic_rule.enabled,
262 name_fn=
lambda traffic_rule: traffic_rule.description,
263 object_fn=
lambda api, obj_id: api.traffic_rules[obj_id],
264 unique_id_fn=
lambda hub, obj_id: f
"traffic_rule-{obj_id}",
266 UnifiSwitchEntityDescription[Ports, Port](
267 key=
"PoE port control",
268 translation_key=
"poe_port_control",
269 device_class=SwitchDeviceClass.OUTLET,
270 entity_category=EntityCategory.CONFIG,
271 entity_registry_enabled_default=
False,
272 api_handler_fn=
lambda api: api.ports,
273 available_fn=async_device_available_fn,
274 control_fn=async_poe_port_control_fn,
275 device_info_fn=async_device_device_info_fn,
276 is_on_fn=
lambda hub, port: port.poe_mode !=
"off",
277 name_fn=
lambda port: f
"{port.name} PoE",
278 object_fn=
lambda api, obj_id: api.ports[obj_id],
279 supported_fn=
lambda hub, obj_id:
bool(hub.api.ports[obj_id].port_poe),
280 unique_id_fn=
lambda hub, obj_id: f
"poe-{obj_id}",
282 UnifiSwitchEntityDescription[Wlans, Wlan](
284 translation_key=
"wlan_control",
285 device_class=SwitchDeviceClass.SWITCH,
286 entity_category=EntityCategory.CONFIG,
287 api_handler_fn=
lambda api: api.wlans,
288 control_fn=async_wlan_control_fn,
289 device_info_fn=async_wlan_device_info_fn,
290 is_on_fn=
lambda hub, wlan: wlan.enabled,
291 object_fn=
lambda api, obj_id: api.wlans[obj_id],
292 unique_id_fn=
lambda hub, obj_id: f
"wlan-{obj_id}",
299 """Normalize switch unique ID to have a prefix rather than midfix.
301 Introduced with release 2023.12.
303 hub = config_entry.runtime_data
304 ent_reg = er.async_get(hass)
308 """Rework unique ID."""
309 new_unique_id = f
"{type_name}-{obj_id}"
310 if ent_reg.async_get_entity_id(SWITCH_DOMAIN, UNIFI_DOMAIN, new_unique_id):
313 prefix, _, suffix = obj_id.partition(
"_")
314 unique_id = f
"{prefix}-{type_name}-{suffix}"
315 if entity_id := ent_reg.async_get_entity_id(
316 SWITCH_DOMAIN, UNIFI_DOMAIN, unique_id
318 ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)
320 for obj_id
in hub.api.outlets:
323 for obj_id
in hub.api.ports:
329 config_entry: UnifiConfigEntry,
330 async_add_entities: AddEntitiesCallback,
332 """Set up switches for UniFi Network integration."""
334 config_entry.runtime_data.entity_loader.register_platform(
343 """Base representation of a UniFi switch."""
345 entity_description: UnifiSwitchEntityDescription[HandlerT, ApiItemT]
349 """Initiate entity state."""
353 """Turn on switch."""
357 """Turn off switch."""
362 self, event: ItemEvent, obj_id: str, first_update: bool =
False
364 """Update entity state.
368 if not first_update
and self.
entity_descriptionentity_description.only_event_for_state_change:
372 obj = description.object_fn(self.
apiapi, self.
_obj_id_obj_id)
373 if (is_on := description.is_on_fn(self.
hubhub, obj)) != self.
is_onis_on:
378 """Event subscription callback."""
379 if event.mac != self.
_obj_id_obj_id:
384 assert description.event_to_subscribe
is not None
385 assert description.event_is_on
is not None
387 if event.key
in description.event_to_subscribe:
388 self.
_attr_is_on_attr_is_on = event.key
in description.event_is_on
393 """Register callbacks."""
None async_signalling_callback(self, ItemEvent event, str obj_id)
None async_update_state(self, ItemEvent event, str obj_id)
None async_added_to_hass(self)
None async_initiate_state(self)
None async_turn_off(self, **Any kwargs)
None async_event_callback(self, Event event)
None async_turn_on(self, **Any kwargs)
None async_update_state(self, ItemEvent event, str obj_id, bool first_update=False)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
dict[str, str]|None update_unique_id(er.RegistryEntry entity_entry, str unique_id)
bool async_outlet_switching_supported_fn(UnifiHub hub, str obj_id)
None async_dpi_group_control_fn(UnifiHub hub, str obj_id, bool target)
None async_port_forward_control_fn(UnifiHub hub, str obj_id, bool target)
None async_block_client_control_fn(UnifiHub hub, str obj_id, bool target)
None async_wlan_control_fn(UnifiHub hub, str obj_id, bool target)
None async_poe_port_control_fn(UnifiHub hub, str obj_id, bool target)
DeviceInfo async_unifi_network_device_info_fn(UnifiHub hub, str obj_id)
None async_setup_entry(HomeAssistant hass, UnifiConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None async_update_unique_id(HomeAssistant hass, UnifiConfigEntry config_entry)
bool async_dpi_group_is_on_fn(UnifiHub hub, DPIRestrictionGroup dpi_group)
None async_outlet_control_fn(UnifiHub hub, str obj_id, bool target)
bool async_block_client_allowed_fn(UnifiHub hub, str obj_id)
DeviceInfo async_dpi_group_device_info_fn(UnifiHub hub, str obj_id)
None async_traffic_rule_control_fn(UnifiHub hub, str obj_id, bool target)