mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add controls to enable and disable a UniFi WLAN (#97204)
This commit is contained in:
parent
a0b61a1188
commit
8d6c4e3306
@ -18,11 +18,14 @@ from aiounifi.models.event import Event, EventKey
|
|||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
from homeassistant.helpers.device_registry import (
|
||||||
|
CONNECTION_NETWORK_MAC,
|
||||||
|
DeviceEntryType,
|
||||||
|
)
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||||
|
|
||||||
from .const import ATTR_MANUFACTURER
|
from .const import ATTR_MANUFACTURER, DOMAIN
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .controller import UniFiController
|
from .controller import UniFiController
|
||||||
@ -58,6 +61,19 @@ def async_device_device_info_fn(api: aiounifi.Controller, obj_id: str) -> Device
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_wlan_device_info_fn(api: aiounifi.Controller, obj_id: str) -> DeviceInfo:
|
||||||
|
"""Create device registry entry for WLAN."""
|
||||||
|
wlan = api.wlans[obj_id]
|
||||||
|
return DeviceInfo(
|
||||||
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
|
identifiers={(DOMAIN, wlan.id)},
|
||||||
|
manufacturer=ATTR_MANUFACTURER,
|
||||||
|
model="UniFi WLAN",
|
||||||
|
name=wlan.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UnifiDescription(Generic[HandlerT, ApiItemT]):
|
class UnifiDescription(Generic[HandlerT, ApiItemT]):
|
||||||
"""Validate and load entities from different UniFi handlers."""
|
"""Validate and load entities from different UniFi handlers."""
|
||||||
|
@ -8,24 +8,26 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
import aiounifi
|
|
||||||
from aiounifi.interfaces.api_handlers import ItemEvent
|
from aiounifi.interfaces.api_handlers import ItemEvent
|
||||||
from aiounifi.interfaces.wlans import Wlans
|
from aiounifi.interfaces.wlans import Wlans
|
||||||
from aiounifi.models.api import ApiItemT
|
from aiounifi.models.api import ApiItemT
|
||||||
from aiounifi.models.wlan import Wlan
|
from aiounifi.models.wlan import Wlan
|
||||||
|
|
||||||
from homeassistant.components.image import DOMAIN, ImageEntity, ImageEntityDescription
|
from homeassistant.components.image import ImageEntity, ImageEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import EntityCategory
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN
|
from .const import DOMAIN as UNIFI_DOMAIN
|
||||||
from .controller import UniFiController
|
from .controller import UniFiController
|
||||||
from .entity import HandlerT, UnifiEntity, UnifiEntityDescription
|
from .entity import (
|
||||||
|
HandlerT,
|
||||||
|
UnifiEntity,
|
||||||
|
UnifiEntityDescription,
|
||||||
|
async_wlan_device_info_fn,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -34,19 +36,6 @@ def async_wlan_qr_code_image_fn(controller: UniFiController, wlan: Wlan) -> byte
|
|||||||
return controller.api.wlans.generate_wlan_qr_code(wlan)
|
return controller.api.wlans.generate_wlan_qr_code(wlan)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_wlan_device_info_fn(api: aiounifi.Controller, obj_id: str) -> DeviceInfo:
|
|
||||||
"""Create device registry entry for WLAN."""
|
|
||||||
wlan = api.wlans[obj_id]
|
|
||||||
return DeviceInfo(
|
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
|
||||||
identifiers={(DOMAIN, wlan.id)},
|
|
||||||
manufacturer=ATTR_MANUFACTURER,
|
|
||||||
model="UniFi Network",
|
|
||||||
name=wlan.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UnifiImageEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
class UnifiImageEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
||||||
"""Validate and load entities from different UniFi handlers."""
|
"""Validate and load entities from different UniFi handlers."""
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
Support for controlling power supply of clients which are powered over Ethernet (POE).
|
Support for controlling power supply of clients which are powered over Ethernet (POE).
|
||||||
Support for controlling network access of clients selected in option flow.
|
Support for controlling network access of clients selected in option flow.
|
||||||
Support for controlling deep packet inspection (DPI) restriction groups.
|
Support for controlling deep packet inspection (DPI) restriction groups.
|
||||||
|
Support for controlling WLAN availability.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ from aiounifi.interfaces.clients import Clients
|
|||||||
from aiounifi.interfaces.dpi_restriction_groups import DPIRestrictionGroups
|
from aiounifi.interfaces.dpi_restriction_groups import DPIRestrictionGroups
|
||||||
from aiounifi.interfaces.outlets import Outlets
|
from aiounifi.interfaces.outlets import Outlets
|
||||||
from aiounifi.interfaces.ports import Ports
|
from aiounifi.interfaces.ports import Ports
|
||||||
|
from aiounifi.interfaces.wlans import Wlans
|
||||||
from aiounifi.models.api import ApiItemT
|
from aiounifi.models.api import ApiItemT
|
||||||
from aiounifi.models.client import Client, ClientBlockRequest
|
from aiounifi.models.client import Client, ClientBlockRequest
|
||||||
from aiounifi.models.device import (
|
from aiounifi.models.device import (
|
||||||
@ -28,6 +30,7 @@ from aiounifi.models.dpi_restriction_group import DPIRestrictionGroup
|
|||||||
from aiounifi.models.event import Event, EventKey
|
from aiounifi.models.event import Event, EventKey
|
||||||
from aiounifi.models.outlet import Outlet
|
from aiounifi.models.outlet import Outlet
|
||||||
from aiounifi.models.port import Port
|
from aiounifi.models.port import Port
|
||||||
|
from aiounifi.models.wlan import Wlan, WlanEnableRequest
|
||||||
|
|
||||||
from homeassistant.components.switch import (
|
from homeassistant.components.switch import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -54,6 +57,7 @@ from .entity import (
|
|||||||
UnifiEntityDescription,
|
UnifiEntityDescription,
|
||||||
async_device_available_fn,
|
async_device_available_fn,
|
||||||
async_device_device_info_fn,
|
async_device_device_info_fn,
|
||||||
|
async_wlan_device_info_fn,
|
||||||
)
|
)
|
||||||
|
|
||||||
CLIENT_BLOCKED = (EventKey.WIRED_CLIENT_BLOCKED, EventKey.WIRELESS_CLIENT_BLOCKED)
|
CLIENT_BLOCKED = (EventKey.WIRED_CLIENT_BLOCKED, EventKey.WIRELESS_CLIENT_BLOCKED)
|
||||||
@ -137,6 +141,13 @@ async def async_poe_port_control_fn(
|
|||||||
await api.request(DeviceSetPoePortModeRequest.create(device, int(index), state))
|
await api.request(DeviceSetPoePortModeRequest.create(device, int(index), state))
|
||||||
|
|
||||||
|
|
||||||
|
async def async_wlan_control_fn(
|
||||||
|
api: aiounifi.Controller, obj_id: str, target: bool
|
||||||
|
) -> None:
|
||||||
|
"""Control outlet relay."""
|
||||||
|
await api.request(WlanEnableRequest.create(obj_id, target))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class UnifiSwitchEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
class UnifiSwitchEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
|
||||||
"""Validate and load entities from different UniFi handlers."""
|
"""Validate and load entities from different UniFi handlers."""
|
||||||
@ -233,6 +244,25 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
|
|||||||
supported_fn=lambda controller, obj_id: controller.api.ports[obj_id].port_poe,
|
supported_fn=lambda controller, obj_id: controller.api.ports[obj_id].port_poe,
|
||||||
unique_id_fn=lambda controller, obj_id: f"{obj_id.split('_', 1)[0]}-poe-{obj_id.split('_', 1)[1]}",
|
unique_id_fn=lambda controller, obj_id: f"{obj_id.split('_', 1)[0]}-poe-{obj_id.split('_', 1)[1]}",
|
||||||
),
|
),
|
||||||
|
UnifiSwitchEntityDescription[Wlans, Wlan](
|
||||||
|
key="WLAN control",
|
||||||
|
device_class=SwitchDeviceClass.SWITCH,
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
has_entity_name=True,
|
||||||
|
icon="mdi:wifi-check",
|
||||||
|
allowed_fn=lambda controller, obj_id: True,
|
||||||
|
api_handler_fn=lambda api: api.wlans,
|
||||||
|
available_fn=lambda controller, _: controller.available,
|
||||||
|
control_fn=async_wlan_control_fn,
|
||||||
|
device_info_fn=async_wlan_device_info_fn,
|
||||||
|
event_is_on=None,
|
||||||
|
event_to_subscribe=None,
|
||||||
|
is_on_fn=lambda controller, wlan: wlan.enabled,
|
||||||
|
name_fn=lambda wlan: None,
|
||||||
|
object_fn=lambda api, obj_id: api.wlans[obj_id],
|
||||||
|
supported_fn=lambda controller, obj_id: True,
|
||||||
|
unique_id_fn=lambda controller, obj_id: f"wlan-{obj_id}",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -580,6 +580,43 @@ OUTLET_UP1 = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WLAN = {
|
||||||
|
"_id": "012345678910111213141516",
|
||||||
|
"bc_filter_enabled": False,
|
||||||
|
"bc_filter_list": [],
|
||||||
|
"dtim_mode": "default",
|
||||||
|
"dtim_na": 1,
|
||||||
|
"dtim_ng": 1,
|
||||||
|
"enabled": True,
|
||||||
|
"group_rekey": 3600,
|
||||||
|
"mac_filter_enabled": False,
|
||||||
|
"mac_filter_list": [],
|
||||||
|
"mac_filter_policy": "allow",
|
||||||
|
"minrate_na_advertising_rates": False,
|
||||||
|
"minrate_na_beacon_rate_kbps": 6000,
|
||||||
|
"minrate_na_data_rate_kbps": 6000,
|
||||||
|
"minrate_na_enabled": False,
|
||||||
|
"minrate_na_mgmt_rate_kbps": 6000,
|
||||||
|
"minrate_ng_advertising_rates": False,
|
||||||
|
"minrate_ng_beacon_rate_kbps": 1000,
|
||||||
|
"minrate_ng_data_rate_kbps": 1000,
|
||||||
|
"minrate_ng_enabled": False,
|
||||||
|
"minrate_ng_mgmt_rate_kbps": 1000,
|
||||||
|
"name": "SSID 1",
|
||||||
|
"no2ghz_oui": False,
|
||||||
|
"schedule": [],
|
||||||
|
"security": "wpapsk",
|
||||||
|
"site_id": "5a32aa4ee4b0412345678910",
|
||||||
|
"usergroup_id": "012345678910111213141518",
|
||||||
|
"wep_idx": 1,
|
||||||
|
"wlangroup_id": "012345678910111213141519",
|
||||||
|
"wpa_enc": "ccmp",
|
||||||
|
"wpa_mode": "wpa2",
|
||||||
|
"x_iapp_key": "01234567891011121314151617181920",
|
||||||
|
"x_passphrase": "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_no_clients(
|
async def test_no_clients(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1230,3 +1267,71 @@ async def test_remove_poe_client_switches(
|
|||||||
for entry in ent_reg.entities.values()
|
for entry in ent_reg.entities.values()
|
||||||
if entry.config_entry_id == config_entry.entry_id
|
if entry.config_entry_id == config_entry.entry_id
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_wlan_switches(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_unifi_websocket
|
||||||
|
) -> None:
|
||||||
|
"""Test control of UniFi WLAN availability."""
|
||||||
|
config_entry = await setup_unifi_integration(
|
||||||
|
hass, aioclient_mock, wlans_response=[WLAN]
|
||||||
|
)
|
||||||
|
controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
|
assert len(hass.states.async_entity_ids(SWITCH_DOMAIN)) == 1
|
||||||
|
|
||||||
|
ent_reg = er.async_get(hass)
|
||||||
|
ent_reg_entry = ent_reg.async_get("switch.ssid_1")
|
||||||
|
assert ent_reg_entry.unique_id == "wlan-012345678910111213141516"
|
||||||
|
assert ent_reg_entry.entity_category is EntityCategory.CONFIG
|
||||||
|
|
||||||
|
# Validate state object
|
||||||
|
switch_1 = hass.states.get("switch.ssid_1")
|
||||||
|
assert switch_1 is not None
|
||||||
|
assert switch_1.state == STATE_ON
|
||||||
|
assert switch_1.attributes.get(ATTR_DEVICE_CLASS) == SwitchDeviceClass.SWITCH
|
||||||
|
|
||||||
|
# Update state object
|
||||||
|
wlan = deepcopy(WLAN)
|
||||||
|
wlan["enabled"] = False
|
||||||
|
mock_unifi_websocket(message=MessageKey.WLAN_CONF_UPDATED, data=wlan)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.ssid_1").state == STATE_OFF
|
||||||
|
|
||||||
|
# Disable WLAN
|
||||||
|
aioclient_mock.clear_requests()
|
||||||
|
aioclient_mock.put(
|
||||||
|
f"https://{controller.host}:1234/api/s/{controller.site}"
|
||||||
|
+ f"/rest/wlanconf/{WLAN['_id']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
"turn_off",
|
||||||
|
{"entity_id": "switch.ssid_1"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert aioclient_mock.call_count == 1
|
||||||
|
assert aioclient_mock.mock_calls[0][2] == {"enabled": False}
|
||||||
|
|
||||||
|
# Enable WLAN
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
"turn_on",
|
||||||
|
{"entity_id": "switch.ssid_1"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert aioclient_mock.call_count == 2
|
||||||
|
assert aioclient_mock.mock_calls[1][2] == {"enabled": True}
|
||||||
|
|
||||||
|
# Availability signalling
|
||||||
|
|
||||||
|
# Controller disconnects
|
||||||
|
mock_unifi_websocket(state=WebsocketState.DISCONNECTED)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.ssid_1").state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Controller reconnects
|
||||||
|
mock_unifi_websocket(state=WebsocketState.RUNNING)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("switch.ssid_1").state == STATE_OFF
|
||||||
|
Loading…
x
Reference in New Issue
Block a user