2021-11-26 21:44:49 +00:00
|
|
|
"""Switch platform for UniFi Network integration.
|
2021-02-01 16:55:16 +00:00
|
|
|
|
|
|
|
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 deep packet inspection (DPI) restriction groups.
|
|
|
|
"""
|
2022-10-25 17:26:56 +00:00
|
|
|
from __future__ import annotations
|
2022-01-12 16:11:05 +00:00
|
|
|
|
|
|
|
import asyncio
|
2022-11-08 06:38:31 +00:00
|
|
|
from collections.abc import Callable, Coroutine
|
2022-10-25 17:26:56 +00:00
|
|
|
from dataclasses import dataclass
|
2022-12-11 15:41:58 +00:00
|
|
|
from typing import Any, Generic, TypeVar, Union
|
2018-10-16 08:35:35 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
import aiounifi
|
2022-12-11 15:41:58 +00:00
|
|
|
from aiounifi.interfaces.api_handlers import (
|
|
|
|
APIHandler,
|
|
|
|
CallbackType,
|
|
|
|
ItemEvent,
|
|
|
|
UnsubscribeType,
|
|
|
|
)
|
2022-10-25 20:36:51 +00:00
|
|
|
from aiounifi.interfaces.clients import Clients
|
2022-10-25 17:26:56 +00:00
|
|
|
from aiounifi.interfaces.dpi_restriction_groups import DPIRestrictionGroups
|
|
|
|
from aiounifi.interfaces.outlets import Outlets
|
|
|
|
from aiounifi.interfaces.ports import Ports
|
2022-12-11 15:41:58 +00:00
|
|
|
from aiounifi.models.api import APIItem
|
2022-11-08 06:38:31 +00:00
|
|
|
from aiounifi.models.client import Client, ClientBlockRequest
|
2022-09-25 18:08:56 +00:00
|
|
|
from aiounifi.models.device import (
|
|
|
|
DeviceSetOutletRelayRequest,
|
|
|
|
DeviceSetPoePortModeRequest,
|
2020-05-08 20:19:27 +00:00
|
|
|
)
|
2022-09-25 18:08:56 +00:00
|
|
|
from aiounifi.models.dpi_restriction_app import DPIRestrictionAppEnableRequest
|
2022-11-08 06:38:31 +00:00
|
|
|
from aiounifi.models.dpi_restriction_group import DPIRestrictionGroup
|
2022-10-25 20:36:51 +00:00
|
|
|
from aiounifi.models.event import Event, EventKey
|
2022-11-08 06:38:31 +00:00
|
|
|
from aiounifi.models.outlet import Outlet
|
|
|
|
from aiounifi.models.port import Port
|
|
|
|
|
|
|
|
from homeassistant.components.switch import (
|
|
|
|
DOMAIN,
|
|
|
|
SwitchDeviceClass,
|
|
|
|
SwitchEntity,
|
|
|
|
SwitchEntityDescription,
|
|
|
|
)
|
2021-12-27 22:42:24 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2022-05-17 14:40:45 +00:00
|
|
|
from homeassistant.helpers import entity_registry as er
|
2022-01-25 06:49:02 +00:00
|
|
|
from homeassistant.helpers.device_registry import (
|
|
|
|
CONNECTION_NETWORK_MAC,
|
|
|
|
DeviceEntryType,
|
|
|
|
)
|
2019-06-15 15:38:22 +00:00
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
2021-12-16 11:53:01 +00:00
|
|
|
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
2021-12-27 22:42:24 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
2018-10-16 08:35:35 +00:00
|
|
|
|
2022-11-08 06:48:54 +00:00
|
|
|
from .const import ATTR_MANUFACTURER, DOMAIN as UNIFI_DOMAIN
|
2022-10-25 17:26:56 +00:00
|
|
|
from .controller import UniFiController
|
2020-01-31 19:23:25 +00:00
|
|
|
|
2022-09-25 18:08:56 +00:00
|
|
|
CLIENT_BLOCKED = (EventKey.WIRED_CLIENT_BLOCKED, EventKey.WIRELESS_CLIENT_BLOCKED)
|
|
|
|
CLIENT_UNBLOCKED = (EventKey.WIRED_CLIENT_UNBLOCKED, EventKey.WIRELESS_CLIENT_UNBLOCKED)
|
2020-05-08 20:19:27 +00:00
|
|
|
|
2022-12-11 15:41:58 +00:00
|
|
|
_DataT = TypeVar("_DataT", bound=Union[APIItem, Outlet, Port])
|
|
|
|
_HandlerT = TypeVar("_HandlerT", bound=Union[APIHandler, Outlets, Ports])
|
2022-11-08 06:38:31 +00:00
|
|
|
|
|
|
|
Subscription = Callable[[CallbackType, ItemEvent], UnsubscribeType]
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_dpi_group_is_on_fn(
|
|
|
|
api: aiounifi.Controller, dpi_group: DPIRestrictionGroup
|
|
|
|
) -> bool:
|
|
|
|
"""Calculate if all apps are enabled."""
|
|
|
|
return all(
|
|
|
|
api.dpi_apps[app_id].enabled
|
|
|
|
for app_id in dpi_group.dpiapp_ids or []
|
|
|
|
if app_id in api.dpi_apps
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_sub_device_available_fn(controller: UniFiController, obj_id: str) -> bool:
|
|
|
|
"""Check if sub device object is disabled."""
|
|
|
|
device_id = obj_id.partition("_")[0]
|
|
|
|
device = controller.api.devices[device_id]
|
|
|
|
return controller.available and not device.disabled
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_client_device_info_fn(api: aiounifi.Controller, obj_id: str) -> DeviceInfo:
|
|
|
|
"""Create device registry entry for client."""
|
|
|
|
client = api.clients[obj_id]
|
|
|
|
return DeviceInfo(
|
|
|
|
connections={(CONNECTION_NETWORK_MAC, obj_id)},
|
|
|
|
default_manufacturer=client.oui,
|
|
|
|
default_name=client.name or client.hostname,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_device_device_info_fn(api: aiounifi.Controller, obj_id: str) -> DeviceInfo:
|
|
|
|
"""Create device registry entry for device."""
|
|
|
|
if "_" in obj_id: # Sub device
|
|
|
|
obj_id = obj_id.partition("_")[0]
|
|
|
|
|
|
|
|
device = api.devices[obj_id]
|
|
|
|
return DeviceInfo(
|
|
|
|
connections={(CONNECTION_NETWORK_MAC, device.mac)},
|
|
|
|
manufacturer=ATTR_MANUFACTURER,
|
|
|
|
model=device.model,
|
|
|
|
name=device.name or None,
|
|
|
|
sw_version=device.version,
|
|
|
|
hw_version=str(device.board_revision),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_dpi_group_device_info_fn(api: aiounifi.Controller, obj_id: str) -> DeviceInfo:
|
|
|
|
"""Create device registry entry for DPI group."""
|
|
|
|
return DeviceInfo(
|
|
|
|
entry_type=DeviceEntryType.SERVICE,
|
|
|
|
identifiers={(DOMAIN, f"unifi_controller_{obj_id}")},
|
|
|
|
manufacturer=ATTR_MANUFACTURER,
|
|
|
|
model="UniFi Network",
|
|
|
|
name="UniFi Network",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def async_block_client_control_fn(
|
|
|
|
api: aiounifi.Controller, obj_id: str, target: bool
|
|
|
|
) -> None:
|
|
|
|
"""Control network access of client."""
|
|
|
|
await api.request(ClientBlockRequest.create(obj_id, not target))
|
|
|
|
|
|
|
|
|
|
|
|
async def async_dpi_group_control_fn(
|
|
|
|
api: aiounifi.Controller, obj_id: str, target: bool
|
|
|
|
) -> None:
|
|
|
|
"""Enable or disable DPI group."""
|
|
|
|
dpi_group = api.dpi_groups[obj_id]
|
|
|
|
await asyncio.gather(
|
|
|
|
*[
|
|
|
|
api.request(DPIRestrictionAppEnableRequest.create(app_id, target))
|
|
|
|
for app_id in dpi_group.dpiapp_ids or []
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def async_outlet_control_fn(
|
|
|
|
api: aiounifi.Controller, obj_id: str, target: bool
|
|
|
|
) -> None:
|
|
|
|
"""Control outlet relay."""
|
|
|
|
mac, _, index = obj_id.partition("_")
|
|
|
|
device = api.devices[mac]
|
|
|
|
await api.request(DeviceSetOutletRelayRequest.create(device, int(index), target))
|
|
|
|
|
|
|
|
|
|
|
|
async def async_poe_port_control_fn(
|
|
|
|
api: aiounifi.Controller, obj_id: str, target: bool
|
|
|
|
) -> None:
|
|
|
|
"""Control poe state."""
|
|
|
|
mac, _, index = obj_id.partition("_")
|
|
|
|
device = api.devices[mac]
|
|
|
|
state = "auto" if target else "off"
|
|
|
|
await api.request(DeviceSetPoePortModeRequest.create(device, int(index), state))
|
2022-10-25 17:26:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-12-11 15:41:58 +00:00
|
|
|
class UnifiEntityLoader(Generic[_HandlerT, _DataT]):
|
2022-10-25 17:26:56 +00:00
|
|
|
"""Validate and load entities from different UniFi handlers."""
|
|
|
|
|
2022-10-25 20:36:51 +00:00
|
|
|
allowed_fn: Callable[[UniFiController, str], bool]
|
2022-12-11 15:41:58 +00:00
|
|
|
api_handler_fn: Callable[[aiounifi.Controller], _HandlerT]
|
2022-11-08 06:38:31 +00:00
|
|
|
available_fn: Callable[[UniFiController, str], bool]
|
|
|
|
control_fn: Callable[[aiounifi.Controller, str, bool], Coroutine[Any, Any, None]]
|
|
|
|
device_info_fn: Callable[[aiounifi.Controller, str], DeviceInfo]
|
|
|
|
event_is_on: tuple[EventKey, ...] | None
|
|
|
|
event_to_subscribe: tuple[EventKey, ...] | None
|
2022-12-11 15:41:58 +00:00
|
|
|
is_on_fn: Callable[[aiounifi.Controller, _DataT], bool]
|
|
|
|
name_fn: Callable[[_DataT], str | None]
|
|
|
|
object_fn: Callable[[aiounifi.Controller, str], _DataT]
|
2022-11-08 06:38:31 +00:00
|
|
|
supported_fn: Callable[[aiounifi.Controller, str], bool | None]
|
|
|
|
unique_id_fn: Callable[[str], str]
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-12-11 15:41:58 +00:00
|
|
|
class UnifiEntityDescription(
|
|
|
|
SwitchEntityDescription, UnifiEntityLoader[_HandlerT, _DataT]
|
|
|
|
):
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Class describing UniFi switch entity."""
|
|
|
|
|
|
|
|
custom_subscribe: Callable[[aiounifi.Controller], Subscription] | None = None
|
2022-11-15 17:31:30 +00:00
|
|
|
only_event_for_state_change: bool = False
|
2022-11-08 06:38:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
ENTITY_DESCRIPTIONS: tuple[UnifiEntityDescription, ...] = (
|
|
|
|
UnifiEntityDescription[Clients, Client](
|
|
|
|
key="Block client",
|
|
|
|
device_class=SwitchDeviceClass.SWITCH,
|
|
|
|
entity_category=EntityCategory.CONFIG,
|
|
|
|
has_entity_name=True,
|
|
|
|
icon="mdi:ethernet",
|
|
|
|
allowed_fn=lambda controller, obj_id: obj_id in controller.option_block_clients,
|
|
|
|
api_handler_fn=lambda api: api.clients,
|
|
|
|
available_fn=lambda controller, obj_id: controller.available,
|
|
|
|
control_fn=async_block_client_control_fn,
|
|
|
|
device_info_fn=async_client_device_info_fn,
|
|
|
|
event_is_on=CLIENT_UNBLOCKED,
|
|
|
|
event_to_subscribe=CLIENT_BLOCKED + CLIENT_UNBLOCKED,
|
|
|
|
is_on_fn=lambda api, client: not client.blocked,
|
|
|
|
name_fn=lambda client: None,
|
|
|
|
object_fn=lambda api, obj_id: api.clients[obj_id],
|
2022-11-15 17:31:30 +00:00
|
|
|
only_event_for_state_change=True,
|
2022-11-08 06:38:31 +00:00
|
|
|
supported_fn=lambda api, obj_id: True,
|
|
|
|
unique_id_fn=lambda obj_id: f"block-{obj_id}",
|
|
|
|
),
|
|
|
|
UnifiEntityDescription[DPIRestrictionGroups, DPIRestrictionGroup](
|
|
|
|
key="DPI restriction",
|
|
|
|
entity_category=EntityCategory.CONFIG,
|
|
|
|
icon="mdi:network",
|
|
|
|
allowed_fn=lambda controller, obj_id: controller.option_dpi_restrictions,
|
|
|
|
api_handler_fn=lambda api: api.dpi_groups,
|
|
|
|
available_fn=lambda controller, obj_id: controller.available,
|
|
|
|
control_fn=async_dpi_group_control_fn,
|
|
|
|
custom_subscribe=lambda api: api.dpi_apps.subscribe,
|
|
|
|
device_info_fn=async_dpi_group_device_info_fn,
|
|
|
|
event_is_on=None,
|
|
|
|
event_to_subscribe=None,
|
|
|
|
is_on_fn=async_dpi_group_is_on_fn,
|
|
|
|
name_fn=lambda group: group.name,
|
|
|
|
object_fn=lambda api, obj_id: api.dpi_groups[obj_id],
|
|
|
|
supported_fn=lambda api, obj_id: bool(api.dpi_groups[obj_id].dpiapp_ids),
|
|
|
|
unique_id_fn=lambda obj_id: obj_id,
|
|
|
|
),
|
|
|
|
UnifiEntityDescription[Outlets, Outlet](
|
|
|
|
key="Outlet control",
|
|
|
|
device_class=SwitchDeviceClass.OUTLET,
|
|
|
|
has_entity_name=True,
|
|
|
|
allowed_fn=lambda controller, obj_id: True,
|
|
|
|
api_handler_fn=lambda api: api.outlets,
|
|
|
|
available_fn=async_sub_device_available_fn,
|
|
|
|
control_fn=async_outlet_control_fn,
|
|
|
|
device_info_fn=async_device_device_info_fn,
|
|
|
|
event_is_on=None,
|
|
|
|
event_to_subscribe=None,
|
|
|
|
is_on_fn=lambda api, outlet: outlet.relay_state,
|
|
|
|
name_fn=lambda outlet: outlet.name,
|
|
|
|
object_fn=lambda api, obj_id: api.outlets[obj_id],
|
|
|
|
supported_fn=lambda api, obj_id: api.outlets[obj_id].has_relay,
|
|
|
|
unique_id_fn=lambda obj_id: f"{obj_id.split('_', 1)[0]}-outlet-{obj_id.split('_', 1)[1]}",
|
|
|
|
),
|
|
|
|
UnifiEntityDescription[Ports, Port](
|
|
|
|
key="PoE port control",
|
|
|
|
device_class=SwitchDeviceClass.OUTLET,
|
|
|
|
entity_category=EntityCategory.CONFIG,
|
|
|
|
has_entity_name=True,
|
|
|
|
entity_registry_enabled_default=False,
|
|
|
|
icon="mdi:ethernet",
|
|
|
|
allowed_fn=lambda controller, obj_id: True,
|
|
|
|
api_handler_fn=lambda api: api.ports,
|
|
|
|
available_fn=async_sub_device_available_fn,
|
|
|
|
control_fn=async_poe_port_control_fn,
|
|
|
|
device_info_fn=async_device_device_info_fn,
|
|
|
|
event_is_on=None,
|
|
|
|
event_to_subscribe=None,
|
|
|
|
is_on_fn=lambda api, port: port.poe_mode != "off",
|
|
|
|
name_fn=lambda port: f"{port.name} PoE",
|
|
|
|
object_fn=lambda api, obj_id: api.ports[obj_id],
|
|
|
|
supported_fn=lambda api, obj_id: api.ports[obj_id].port_poe,
|
|
|
|
unique_id_fn=lambda obj_id: f"{obj_id.split('_', 1)[0]}-poe-{obj_id.split('_', 1)[1]}",
|
|
|
|
),
|
|
|
|
)
|
2022-10-25 17:26:56 +00:00
|
|
|
|
2018-10-16 08:35:35 +00:00
|
|
|
|
2021-12-27 22:42:24 +00:00
|
|
|
async def async_setup_entry(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
config_entry: ConfigEntry,
|
|
|
|
async_add_entities: AddEntitiesCallback,
|
|
|
|
) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Set up switches for UniFi Network integration."""
|
2022-10-25 17:26:56 +00:00
|
|
|
controller: UniFiController = hass.data[UNIFI_DOMAIN][config_entry.entry_id]
|
2019-07-14 19:57:09 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if controller.site_role != "admin":
|
2019-07-14 19:57:09 +00:00
|
|
|
return
|
|
|
|
|
2020-04-23 19:29:38 +00:00
|
|
|
for mac in controller.option_block_clients:
|
|
|
|
if mac not in controller.api.clients and mac in controller.api.clients_all:
|
|
|
|
client = controller.api.clients_all[mac]
|
|
|
|
controller.api.clients.process_raw([client.raw])
|
2018-10-16 08:35:35 +00:00
|
|
|
|
2022-10-23 18:28:45 +00:00
|
|
|
@callback
|
2022-11-08 06:38:31 +00:00
|
|
|
def async_load_entities(description: UnifiEntityDescription) -> None:
|
2022-10-25 17:26:56 +00:00
|
|
|
"""Load and subscribe to UniFi devices."""
|
|
|
|
entities: list[SwitchEntity] = []
|
2022-11-08 06:38:31 +00:00
|
|
|
api_handler = description.api_handler_fn(controller.api)
|
2022-10-25 17:26:56 +00:00
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_create_entity(event: ItemEvent, obj_id: str) -> None:
|
|
|
|
"""Create UniFi entity."""
|
2022-11-08 06:38:31 +00:00
|
|
|
if not description.allowed_fn(
|
|
|
|
controller, obj_id
|
|
|
|
) or not description.supported_fn(controller.api, obj_id):
|
2022-10-25 17:26:56 +00:00
|
|
|
return
|
2022-10-23 20:42:24 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
entity = UnifiSwitchEntity(obj_id, controller, description)
|
2022-10-25 17:26:56 +00:00
|
|
|
if event == ItemEvent.ADDED:
|
2022-10-25 20:36:51 +00:00
|
|
|
async_add_entities([entity])
|
2022-10-25 17:26:56 +00:00
|
|
|
return
|
|
|
|
entities.append(entity)
|
2022-10-23 20:42:24 +00:00
|
|
|
|
2022-10-25 17:26:56 +00:00
|
|
|
for obj_id in api_handler:
|
|
|
|
async_create_entity(ItemEvent.CHANGED, obj_id)
|
|
|
|
async_add_entities(entities)
|
2022-10-19 17:54:40 +00:00
|
|
|
|
2022-10-25 17:26:56 +00:00
|
|
|
api_handler.subscribe(async_create_entity, ItemEvent.ADDED)
|
2022-10-19 17:54:40 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
for description in ENTITY_DESCRIPTIONS:
|
|
|
|
async_load_entities(description)
|
2022-10-19 17:54:40 +00:00
|
|
|
|
2018-10-16 08:35:35 +00:00
|
|
|
|
2022-12-11 15:41:58 +00:00
|
|
|
class UnifiSwitchEntity(SwitchEntity, Generic[_HandlerT, _DataT]):
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Base representation of a UniFi switch."""
|
2019-07-25 14:56:56 +00:00
|
|
|
|
2022-12-11 15:41:58 +00:00
|
|
|
entity_description: UnifiEntityDescription[_HandlerT, _DataT]
|
2022-10-25 20:36:51 +00:00
|
|
|
_attr_should_poll = False
|
2021-10-26 18:23:20 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
obj_id: str,
|
|
|
|
controller: UniFiController,
|
2022-12-11 15:41:58 +00:00
|
|
|
description: UnifiEntityDescription[_HandlerT, _DataT],
|
2022-11-08 06:38:31 +00:00
|
|
|
) -> None:
|
|
|
|
"""Set up UniFi switch entity."""
|
2022-10-25 20:36:51 +00:00
|
|
|
self._obj_id = obj_id
|
|
|
|
self.controller = controller
|
2022-11-08 06:38:31 +00:00
|
|
|
self.entity_description = description
|
2022-10-25 20:36:51 +00:00
|
|
|
|
|
|
|
self._removed = False
|
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
self._attr_available = description.available_fn(controller, obj_id)
|
|
|
|
self._attr_device_info = description.device_info_fn(controller.api, obj_id)
|
|
|
|
self._attr_unique_id = description.unique_id_fn(obj_id)
|
2020-05-08 20:19:27 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
obj = description.object_fn(self.controller.api, obj_id)
|
|
|
|
self._attr_is_on = description.is_on_fn(controller.api, obj)
|
|
|
|
self._attr_name = description.name_fn(obj)
|
2019-07-25 14:56:56 +00:00
|
|
|
|
2022-09-06 11:37:00 +00:00
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Turn on switch."""
|
|
|
|
await self.entity_description.control_fn(
|
|
|
|
self.controller.api, self._obj_id, True
|
2022-09-25 18:08:56 +00:00
|
|
|
)
|
2019-07-25 14:56:56 +00:00
|
|
|
|
2022-09-06 11:37:00 +00:00
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Turn off switch."""
|
|
|
|
await self.entity_description.control_fn(
|
|
|
|
self.controller.api, self._obj_id, False
|
2022-10-23 20:42:24 +00:00
|
|
|
)
|
2020-11-03 07:36:37 +00:00
|
|
|
|
2022-01-12 16:11:05 +00:00
|
|
|
async def async_added_to_hass(self) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Register callbacks."""
|
|
|
|
description = self.entity_description
|
|
|
|
handler = description.api_handler_fn(self.controller.api)
|
2022-10-23 20:42:24 +00:00
|
|
|
self.async_on_remove(
|
2022-11-08 06:38:31 +00:00
|
|
|
handler.subscribe(
|
|
|
|
self.async_signalling_callback,
|
|
|
|
)
|
2022-10-23 20:42:24 +00:00
|
|
|
)
|
|
|
|
self.async_on_remove(
|
|
|
|
async_dispatcher_connect(
|
2022-11-08 06:38:31 +00:00
|
|
|
self.hass,
|
|
|
|
self.controller.signal_reachable,
|
|
|
|
self.async_signal_reachable_callback,
|
2022-10-23 20:42:24 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self.async_on_remove(
|
|
|
|
async_dispatcher_connect(
|
2022-11-08 06:38:31 +00:00
|
|
|
self.hass,
|
|
|
|
self.controller.signal_options_update,
|
|
|
|
self.options_updated,
|
2022-10-23 20:42:24 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
self.async_on_remove(
|
|
|
|
async_dispatcher_connect(
|
|
|
|
self.hass,
|
2022-11-08 06:38:31 +00:00
|
|
|
self.controller.signal_remove,
|
|
|
|
self.remove_item,
|
2022-10-23 20:42:24 +00:00
|
|
|
)
|
|
|
|
)
|
2022-11-08 06:38:31 +00:00
|
|
|
if description.event_to_subscribe is not None:
|
|
|
|
self.async_on_remove(
|
|
|
|
self.controller.api.events.subscribe(
|
|
|
|
self.async_event_callback,
|
|
|
|
description.event_to_subscribe,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if description.custom_subscribe is not None:
|
|
|
|
self.async_on_remove(
|
|
|
|
description.custom_subscribe(self.controller.api)(
|
|
|
|
self.async_signalling_callback, ItemEvent.CHANGED
|
|
|
|
),
|
|
|
|
)
|
2022-01-12 16:11:05 +00:00
|
|
|
|
|
|
|
@callback
|
2022-10-23 20:42:24 +00:00
|
|
|
def async_signalling_callback(self, event: ItemEvent, obj_id: str) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Update the switch state."""
|
|
|
|
if event == ItemEvent.DELETED and obj_id == self._obj_id:
|
2022-10-23 20:42:24 +00:00
|
|
|
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
2022-01-12 16:11:05 +00:00
|
|
|
return
|
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
description = self.entity_description
|
|
|
|
if not description.supported_fn(self.controller.api, self._obj_id):
|
2022-10-23 20:42:24 +00:00
|
|
|
self.hass.async_create_task(self.remove_item({self._obj_id}))
|
|
|
|
return
|
2022-01-12 16:11:05 +00:00
|
|
|
|
2022-11-15 17:31:30 +00:00
|
|
|
if not description.only_event_for_state_change:
|
|
|
|
obj = description.object_fn(self.controller.api, self._obj_id)
|
|
|
|
self._attr_is_on = description.is_on_fn(self.controller.api, obj)
|
2022-11-08 06:38:31 +00:00
|
|
|
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
2022-10-23 20:42:24 +00:00
|
|
|
self.async_write_ha_state()
|
2020-11-03 07:36:37 +00:00
|
|
|
|
2022-10-23 20:42:24 +00:00
|
|
|
@callback
|
|
|
|
def async_signal_reachable_callback(self) -> None:
|
|
|
|
"""Call when controller connection state change."""
|
|
|
|
self.async_signalling_callback(ItemEvent.ADDED, self._obj_id)
|
2020-11-03 07:36:37 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
@callback
|
|
|
|
def async_event_callback(self, event: Event) -> None:
|
|
|
|
"""Event subscription callback."""
|
|
|
|
if event.mac != self._obj_id:
|
|
|
|
return
|
2022-01-12 16:11:05 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
description = self.entity_description
|
|
|
|
assert isinstance(description.event_to_subscribe, tuple)
|
|
|
|
assert isinstance(description.event_is_on, tuple)
|
2020-11-03 07:36:37 +00:00
|
|
|
|
2022-11-08 06:38:31 +00:00
|
|
|
if event.key in description.event_to_subscribe:
|
|
|
|
self._attr_is_on = event.key in description.event_is_on
|
|
|
|
self._attr_available = description.available_fn(self.controller, self._obj_id)
|
|
|
|
self.async_write_ha_state()
|
2020-11-03 07:36:37 +00:00
|
|
|
|
|
|
|
async def options_updated(self) -> None:
|
|
|
|
"""Config entry options are updated, remove entity if option is disabled."""
|
2022-11-08 06:38:31 +00:00
|
|
|
if not self.entity_description.allowed_fn(self.controller, self._obj_id):
|
|
|
|
await self.remove_item({self._obj_id})
|
2020-11-03 07:36:37 +00:00
|
|
|
|
2022-10-23 20:42:24 +00:00
|
|
|
async def remove_item(self, keys: set) -> None:
|
2022-11-08 06:38:31 +00:00
|
|
|
"""Remove entity if object ID is part of set."""
|
|
|
|
if self._obj_id not in keys or self._removed:
|
2022-10-23 20:42:24 +00:00
|
|
|
return
|
2022-11-08 06:38:31 +00:00
|
|
|
self._removed = True
|
2022-10-23 20:42:24 +00:00
|
|
|
if self.registry_entry:
|
|
|
|
er.async_get(self.hass).async_remove(self.entity_id)
|
|
|
|
else:
|
|
|
|
await self.async_remove(force_remove=True)
|