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 collections.abc import Callable, Coroutine
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Generic from typing import Any
import aiounifi import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent 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))) await api.request(DevicePowerCyclePortRequest.create(mac, int(index)))
@dataclass(frozen=True) @dataclass(frozen=True, kw_only=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)
class UnifiButtonEntityDescription( class UnifiButtonEntityDescription(
ButtonEntityDescription, ButtonEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
UnifiEntityDescription[HandlerT, ApiItemT],
UnifiButtonEntityDescriptionMixin[HandlerT, ApiItemT],
): ):
"""Class describing UniFi button entity.""" """Class describing UniFi button entity."""
control_fn: Callable[[aiounifi.Controller, str], Coroutine[Any, Any, None]]
ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = ( ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
UnifiButtonEntityDescription[Devices, Device]( UnifiButtonEntityDescription[Devices, Device](
@ -84,11 +77,8 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
available_fn=async_device_available_fn, available_fn=async_device_available_fn,
control_fn=async_restart_device_control_fn, control_fn=async_restart_device_control_fn,
device_info_fn=async_device_device_info_fn, device_info_fn=async_device_device_info_fn,
event_is_on=None,
event_to_subscribe=None,
name_fn=lambda _: "Restart", name_fn=lambda _: "Restart",
object_fn=lambda api, obj_id: api.devices[obj_id], object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True, supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"device_restart-{obj_id}", unique_id_fn=lambda hub, obj_id: f"device_restart-{obj_id}",
), ),
@ -106,7 +96,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiButtonEntityDescription, ...] = (
event_to_subscribe=None, event_to_subscribe=None,
name_fn=lambda port: f"{port.name} Power Cycle", name_fn=lambda port: f"{port.name} Power Cycle",
object_fn=lambda api, obj_id: api.ports[obj_id], 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, 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}", 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 dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
import logging import logging
from typing import Any, Generic from typing import Any
import aiounifi import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent 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) return timedelta(seconds=device.next_interval + 60)
@dataclass(frozen=True) @dataclass(frozen=True, kw_only=True)
class UnifiEntityTrackerDescriptionMixin(Generic[HandlerT, ApiItemT]): class UnifiTrackerEntityDescription(UnifiEntityDescription[HandlerT, ApiItemT]):
"""Device tracker local functions.""" """Class describing UniFi device tracker entity."""
heartbeat_timedelta_fn: Callable[[UnifiHub, str], timedelta] heartbeat_timedelta_fn: Callable[[UnifiHub, str], timedelta]
ip_address_fn: Callable[[aiounifi.Controller, str], str | None] 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] 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, ...] = ( ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
UnifiTrackerEntityDescription[Clients, Client]( UnifiTrackerEntityDescription[Clients, Client](
key="Client device scanner", key="Client device scanner",
@ -173,7 +165,6 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
is_connected_fn=async_client_is_connected_fn, is_connected_fn=async_client_is_connected_fn,
name_fn=lambda client: client.name or client.hostname, name_fn=lambda client: client.name or client.hostname,
object_fn=lambda api, obj_id: api.clients[obj_id], object_fn=lambda api, obj_id: api.clients[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True, supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: f"{hub.site}-{obj_id}", unique_id_fn=lambda hub, obj_id: f"{hub.site}-{obj_id}",
ip_address_fn=lambda api, obj_id: api.clients[obj_id].ip, 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, api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn, available_fn=async_device_available_fn,
device_info_fn=lambda api, obj_id: None, device_info_fn=lambda api, obj_id: None,
event_is_on=None,
event_to_subscribe=None,
heartbeat_timedelta_fn=async_device_heartbeat_timedelta_fn, heartbeat_timedelta_fn=async_device_heartbeat_timedelta_fn,
is_connected_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].state == 1, is_connected_fn=lambda ctrlr, obj_id: ctrlr.api.devices[obj_id].state == 1,
name_fn=lambda device: device.name or device.model, name_fn=lambda device: device.name or device.model,
object_fn=lambda api, obj_id: api.devices[obj_id], object_fn=lambda api, obj_id: api.devices[obj_id],
should_poll=False,
supported_fn=lambda hub, obj_id: True, supported_fn=lambda hub, obj_id: True,
unique_id_fn=lambda hub, obj_id: obj_id, unique_id_fn=lambda hub, obj_id: obj_id,
ip_address_fn=lambda api, obj_id: api.devices[obj_id].ip, 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) @dataclass(frozen=True, kw_only=True)
class UnifiDescription(Generic[HandlerT, ApiItemT]): class UnifiEntityDescription(EntityDescription, Generic[HandlerT, ApiItemT]):
"""Validate and load entities from different UniFi handlers.""" """UniFi Entity Description."""
allowed_fn: Callable[[UnifiHub, str], bool] allowed_fn: Callable[[UnifiHub, str], bool]
api_handler_fn: Callable[[aiounifi.Controller], HandlerT] api_handler_fn: Callable[[aiounifi.Controller], HandlerT]
available_fn: Callable[[UnifiHub, str], bool] available_fn: Callable[[UnifiHub, str], bool]
device_info_fn: Callable[[UnifiHub, str], DeviceInfo | None] 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] name_fn: Callable[[ApiItemT], str | None]
object_fn: Callable[[aiounifi.Controller, str], ApiItemT] object_fn: Callable[[aiounifi.Controller, str], ApiItemT]
should_poll: bool
supported_fn: Callable[[UnifiHub, str], bool | None] supported_fn: Callable[[UnifiHub, str], bool | None]
unique_id_fn: Callable[[UnifiHub, str], str] unique_id_fn: Callable[[UnifiHub, str], str]
# Optional
@dataclass(frozen=True) event_is_on: tuple[EventKey, ...] | None = None
class UnifiEntityDescription(EntityDescription, UnifiDescription[HandlerT, ApiItemT]): """Which UniFi events should be used to consider state 'on'."""
"""UniFi Entity Description.""" 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]): class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):

View File

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

View File

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

View File

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

View File

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