Streamline UniFi entity descriptions (#112136)

* Use kw_only=True to get rid of Mixins

* Clarify which inputs are optional and make them have default values
Add doc strings to optional inputs
This commit is contained in:
Robert Svensson 2024-03-04 06:00:17 +01:00 committed by GitHub
parent d7507fd8a3
commit 99414d8b85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 51 additions and 154 deletions

View File

@ -6,7 +6,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any, Generic
from typing import Any
import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent
@ -57,21 +57,14 @@ async def async_power_cycle_port_control_fn(
await api.request(DevicePowerCyclePortRequest.create(mac, int(index)))
@dataclass(frozen=True)
class UnifiButtonEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers."""
control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]]
@dataclass(frozen=True)
@dataclass(frozen=True, kw_only=True)
class UnifiButtonEntityDescription(
ButtonEntityDescription,
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiButtonEntityDescriptionMixin[HandlerT, ApiItemT],
ButtonEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
):
"""Class describing UniFi button entity."""
control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]]
ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
UnifiButtonEntityDescription[Devices, Device](
@ -84,11 +77,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
available_fn=async_device_available_fn,
control_fn=async_restart_device_control_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda _: "Restart",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"device_restart-{obj_id}",
),
@ -106,7 +96,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
event_to_subscribe=None,
name_fn=lambda port: f"{port.name} Power Cycle",
object_fn=lambda api, obj_id: api.ports[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe,
unique_id_fn=lambda hub, obj_id: f"power_cycle-{obj_id}",
),

View File

@ -6,7 +6,7 @@ from collections.abc import Callable, Mapping
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Any, Generic
from typing import Any
import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent
@ -136,9 +136,9 @@ def async_device_heartbeat_timedelta_fn(hub: UnifiHub, obj_id: str) -> timedelta
return timedelta(seconds=device.next_interval + 60)
@dataclass(frozen=True)
class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]):
"""Device tracker local functions."""
@dataclass(frozen=True, kw_only=True)
class UnifiTrackerEntityDescription(UnifiEntityDescription[HandlerT, ApiItemT]):
"""Class describing UniFi device tracker entity."""
heartbeat_timedelta_fn: Callable[[UnifiHub, str], timedelta]
ip_address_fn: Callable[[aiounifi.Controller, str], str | None]
@ -146,14 +146,6 @@ class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]):
hostname_fn: Callable[[aiounifi.Controller, str], str | None]
@dataclass(frozen=True)
class UnifiTrackerEntityDescription(
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiEntityTrackerDescriptionMixin[HandlerT, ApiItemT],
):
"""Class describing UniFi device tracker entity."""
ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
UnifiTrackerEntityDescription[Clients, Client](
key="Client device scanner",
@ -173,7 +165,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
is_connected_fn=async_client_is_connected_fn,
name_fn=lambda client: client.name or client.hostname,
object_fn=lambda api, obj_id: api.clients[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"{hub.site}-{obj_id}",
ip_address_fn=lambda api, obj_id: api.clients[obj_id].ip,
@ -186,13 +177,10 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=lambda api, obj_id: None,
event_is_on=None,
event_to_subscribe=None,
heartbeat_timedelta_fn=async_device_heartbeat_timedelta_fn,
is_connected_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].state == 1,
name_fn=lambda device: device.name or device.model,
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: obj_id,
ip_address_fn=lambda api, obj_id: api.devices[obj_id].ip,

View File

@ -93,26 +93,26 @@ def async_client_device_info_fn(hub: UnifiHub, obj_id: str) -> DeviceInfo:
)
@dataclass(frozen=True)
class UnifiDescription(Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers."""
@dataclass(frozen=True, kw_only=True)
class UnifiEntityDescription(EntityDescription, Generic[HandlerT, ApiItemT]):
"""UniFi Entity Description."""
allowed_fn: Callable[[UnifiHub, str], bool]
api_handler_fn: Callable[[aiounifi.Controller], HandlerT]
available_fn: Callable[[UnifiHub, str], bool]
device_info_fn: Callable[[UnifiHub, str], DeviceInfo | None]
event_is_on: tuple[EventKey, ...] | None
event_to_subscribe: tuple[EventKey, ...] | None
name_fn: Callable[[ApiItemT], str | None]
object_fn: Callable[[aiounifi.Controller, str], ApiItemT]
should_poll: bool
supported_fn: Callable[[UnifiHub, str], bool | None]
unique_id_fn: Callable[[UnifiHub, str], str]
@dataclass(frozen=True)
class UnifiEntityDescription(EntityDescription, UnifiDescription[HandlerT, ApiItemT]):
"""UniFi Entity Description."""
# Optional
event_is_on: tuple[EventKey, ...] | None = None
"""Which UniFi events should be used to consider state 'on'."""
event_to_subscribe: tuple[EventKey, ...] | None = None
"""Which UniFi events to listen on."""
should_poll: bool = False
"""If entity needs to do regular checks on state."""
class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):

View File

@ -6,7 +6,6 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Generic
from aiounifi.interfaces.api_handlers import ItemEvent
from aiounifi.interfaces.wlans import Wlans
@ -36,23 +35,16 @@ def async_wlan_qr_code_image_fn(hub: UnifiHub, wlan: Wlan) -> bytes:
return hub.api.wlans.generate_wlan_qr_code(wlan)
@dataclass(frozen=True)
class UnifiImageEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers."""
@dataclass(frozen=True, kw_only=True)
class UnifiImageEntityDescription(
ImageEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
):
"""Class describing UniFi image entity."""
image_fn: Callable[[UnifiHub, ApiItemT], bytes]
value_fn: Callable[[ApiItemT], str | None]
@dataclass(frozen=True)
class UnifiImageEntityDescription(
ImageEntityDescription,
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiImageEntityDescriptionMixin[HandlerT, ApiItemT],
):
"""Class describing UniFi image entity."""
ENTITY_DESCRIPTIONS: tuple[UnifiImageEntityDescription, ...] = (
UnifiImageEntityDescription[Wlans, Wlan](
key="WLAN QR Code",
@ -63,11 +55,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiImageEntityDescription, ...] = (
api_handler_fn=lambda api: api.wlans,
available_fn=async_wlan_available_fn,
device_info_fn=async_wlan_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda wlan: "QR Code",
object_fn=lambda api, obj_id: api.wlans[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"qr_code-{obj_id}",
image_fn=async_wlan_qr_code_image_fn,

View File

@ -9,7 +9,6 @@ from collections.abc import Callable
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from decimal import Decimal
from typing import Generic
from aiounifi.interfaces.api_handlers import ItemEvent
from aiounifi.interfaces.clients import Clients
@ -154,33 +153,28 @@ def async_client_is_connected_fn(hub: UnifiHub, obj_id: str) -> bool:
return True
@dataclass(frozen=True)
class UnifiSensorEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers."""
value_fn: Callable[[UnifiHub, ApiItemT], datetime | float | str | None]
@callback
def async_device_state_value_fn(hub: UnifiHub, device: Device) -> str:
"""Retrieve the state of the device."""
return DEVICE_STATES[device.state]
@dataclass(frozen=True)
@dataclass(frozen=True, kw_only=True)
class UnifiSensorEntityDescription(
SensorEntityDescription,
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiSensorEntityDescriptionMixin[HandlerT, ApiItemT],
SensorEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
):
"""Class describing UniFi sensor entity."""
value_fn: Callable[[UnifiHub, ApiItemT], datetime | float | str | None]
# Optional
is_connected_fn: Callable[[UnifiHub, str], bool] | None = None
# Custom function to determine whether a state change should be recorded
"""Calculate if source is connected."""
value_changed_fn: Callable[
[StateType | date | datetime | Decimal, datetime | float | str | None],
bool,
] = lambda old, new: old != new
"""Calculate whether a state change should be recorded."""
ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
@ -196,12 +190,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.clients,
available_fn=lambda hub, _: hub.available,
device_info_fn=async_client_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
is_connected_fn=async_client_is_connected_fn,
name_fn=lambda _: "RX",
object_fn=lambda api, obj_id: api.clients[obj_id],
should_poll=False,
supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors,
unique_id_fn=lambda hub, obj_id: f"rx-{obj_id}",
value_fn=async_client_rx_value_fn,
@ -218,12 +209,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.clients,
available_fn=lambda hub, _: hub.available,
device_info_fn=async_client_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
is_connected_fn=async_client_is_connected_fn,
name_fn=lambda _: "TX",
object_fn=lambda api, obj_id: api.clients[obj_id],
should_poll=False,
supported_fn=lambda hub, _: hub.option_allow_bandwidth_sensors,
unique_id_fn=lambda hub, obj_id: f"tx-{obj_id}",
value_fn=async_client_tx_value_fn,
@ -239,11 +227,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.ports,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda port: f"{port.name} PoE Power",
object_fn=lambda api, obj_id: api.ports[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe,
unique_id_fn=lambda hub, obj_id: f"poe_power-{obj_id}",
value_fn=lambda _, obj: obj.poe_power if obj.poe_mode != "off" else "0",
@ -258,11 +243,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.clients,
available_fn=lambda hub, obj_id: hub.available,
device_info_fn=async_client_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda client: "Uptime",
object_fn=lambda api, obj_id: api.clients[obj_id],
should_poll=False,
supported_fn=lambda hub, _: hub.option_allow_uptime_sensors,
unique_id_fn=lambda hub, obj_id: f"uptime-{obj_id}",
value_fn=async_client_uptime_value_fn,
@ -276,8 +258,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.wlans,
available_fn=async_wlan_available_fn,
device_info_fn=async_wlan_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda wlan: None,
object_fn=lambda api, obj_id: api.wlans[obj_id],
should_poll=True,
@ -295,8 +275,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.outlets,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda outlet: f"{outlet.name} Outlet Power",
object_fn=lambda api, obj_id: api.outlets[obj_id],
should_poll=True,
@ -315,11 +293,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "AC Power Budget",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=async_device_outlet_supported_fn,
unique_id_fn=lambda hub, obj_id: f"ac_power_budget-{obj_id}",
value_fn=lambda hub, device: device.outlet_ac_power_budget,
@ -335,11 +310,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "AC Power Consumption",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=async_device_outlet_supported_fn,
unique_id_fn=lambda hub, obj_id: f"ac_power_conumption-{obj_id}",
value_fn=lambda hub, device: device.outlet_ac_power_consumption,
@ -353,11 +325,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "Uptime",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"device_uptime-{obj_id}",
value_fn=async_device_uptime_value_fn,
@ -373,11 +342,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "Temperature",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].has_temperature,
unique_id_fn=lambda hub, obj_id: f"device_temperature-{obj_id}",
value_fn=lambda ctrlr, device: device.general_temperature,
@ -391,11 +357,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSensorEntityDescription, ...] = (
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: "State",
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"device_state-{obj_id}",
value_fn=async_device_state_value_fn,

View File

@ -10,7 +10,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any, Generic
from typing import Any
import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent
@ -162,24 +162,20 @@ async def async_wlan_control_fn(hub: UnifiHub, obj_id: str, target: bool) -> Non
await hub.api.request(WlanEnableRequest.create(obj_id, target))
@dataclass(frozen=True)
class UnifiSwitchEntityDescriptionMixin(Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers."""
@dataclass(frozen=True, kw_only=True)
class UnifiSwitchEntityDescription(
SwitchEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
):
"""Class describing UniFi switch entity."""
control_fn: Callable[[UnifiHub, str, bool], Coroutine[Any, Any, None]]
is_on_fn: Callable[[UnifiHub, ApiItemT], bool]
@dataclass(frozen=True)
class UnifiSwitchEntityDescription(
SwitchEntityDescription,
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiSwitchEntityDescriptionMixin[HandlerT, ApiItemT],
):
"""Class describing UniFi switch entity."""
# Optional
custom_subscribe: Callable[[aiounifi.Controller], SubscriptionT] | None = None
"""Callback for additional subscriptions to any UniFi handler."""
only_event_for_state_change: bool = False
"""Use only UniFi events to trigger state changes."""
ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
@ -200,7 +196,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
name_fn=lambda client: None,
object_fn=lambda api, obj_id: api.clients[obj_id],
only_event_for_state_change=True,
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"block-{obj_id}",
),
@ -214,12 +209,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
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],
should_poll=False,
supported_fn=lambda c, obj_id: bool(c.api.dpi_groups[obj_id].dpiapp_ids),
unique_id_fn=lambda hub, obj_id: obj_id,
),
@ -232,12 +224,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
available_fn=async_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 hub, outlet: outlet.relay_state,
name_fn=lambda outlet: outlet.name,
object_fn=lambda api, obj_id: api.outlets[obj_id],
should_poll=False,
supported_fn=async_outlet_supports_switching_fn,
unique_id_fn=lambda hub, obj_id: f"outlet-{obj_id}",
),
@ -252,12 +241,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
available_fn=lambda hub, obj_id: hub.available,
control_fn=async_port_forward_control_fn,
device_info_fn=async_port_forward_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
is_on_fn=lambda hub, port_forward: port_forward.enabled,
name_fn=lambda port_forward: f"{port_forward.name}",
object_fn=lambda api, obj_id: api.port_forwarding[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"port_forward-{obj_id}",
),
@ -273,12 +259,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
available_fn=async_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 hub, port: port.poe_mode != "off",
name_fn=lambda port: f"{port.name} PoE",
object_fn=lambda api, obj_id: api.ports[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: hub.api.ports[obj_id].port_poe,
unique_id_fn=lambda hub, obj_id: f"poe-{obj_id}",
),
@ -293,12 +276,9 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
available_fn=lambda hub, _: hub.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 hub, wlan: wlan.enabled,
name_fn=lambda wlan: None,
object_fn=lambda api, obj_id: api.wlans[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"wlan-{obj_id}",
),
@ -354,15 +334,11 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity):
"""Base representation of a UniFi switch."""
entity_description: UnifiSwitchEntityDescription[HandlerT, ApiItemT]
only_event_for_state_change = False
@callback
def async_initiate_state(self) -> None:
"""Initiate entity state."""
self.async_update_state(ItemEvent.ADDED, self._obj_id)
self.only_event_for_state_change = (
self.entity_description.only_event_for_state_change
)
self.async_update_state(ItemEvent.ADDED, self._obj_id, first_update=True)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on switch."""
@ -373,12 +349,14 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity):
await self.entity_description.control_fn(self.hub, self._obj_id, False)
@callback
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:
def async_update_state(
self, event: ItemEvent, obj_id: str, first_update: bool = False
) -> None:
"""Update entity state.
Update attr_is_on.
"""
if self.only_event_for_state_change:
if not first_update and self.entity_description.only_event_for_state_change:
return
description = self.entity_description

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
import logging
from typing import Any, Generic, TypeVar
from typing import Any, TypeVar
import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent
@ -40,23 +40,16 @@ async def async_device_control_fn(api: aiounifi.Controller, obj_id: str) -> None
await api.request(DeviceUpgradeRequest.create(obj_id))
@dataclass(frozen=True)
class UnifiUpdateEntityDescriptionMixin(Generic[_HandlerT, _DataT]):
"""Validate and load entities from different UniFi handlers."""
@dataclass(frozen=True, kw_only=True)
class UnifiUpdateEntityDescription(
UpdateEntityDescription, UnifiEntityDescription[_HandlerT, _DataT]
):
"""Class describing UniFi update entity."""
control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]]
state_fn: Callable[[aiounifi.Controller, _DataT], bool]
@dataclass(frozen=True)
class UnifiUpdateEntityDescription(
UpdateEntityDescription,
UnifiEntityDescription[_HandlerT, _DataT],
UnifiUpdateEntityDescriptionMixin[_HandlerT, _DataT],
):
"""Class describing UniFi update entity."""
ENTITY_DESCRIPTIONS: tuple[UnifiUpdateEntityDescription, ...] = (
UnifiUpdateEntityDescription[Devices, Device](
key="Upgrade device",
@ -67,11 +60,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiUpdateEntityDescription, ...] = (
available_fn=async_device_available_fn,
control_fn=async_device_control_fn,
device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda device: None,
object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
state_fn=lambda api, device: device.state == 4,
supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"device_update-{obj_id}",