Small speedups to unifi (#122684)

- Use a set for event_is_on to avoid linear search
- Avoid many duplicate property lookups
This commit is contained in:
J. Nick Koston 2024-07-27 03:19:53 -05:00 committed by GitHub
parent 1a5706a693
commit 482cf261c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 40 additions and 35 deletions

View file

@ -149,7 +149,7 @@ class UnifiButtonEntity(UnifiEntity[HandlerT, ApiItemT], ButtonEntity):
async def async_press(self) -> None:
"""Press the button."""
await self.entity_description.control_fn(self.hub.api, self._obj_id)
await self.entity_description.control_fn(self.api, self._obj_id)
@callback
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:

View file

@ -153,7 +153,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiTrackerEntityDescription, ...] = (
allowed_fn=async_client_allowed_fn,
api_handler_fn=lambda api: api.clients,
device_info_fn=lambda api, obj_id: None,
event_is_on=(WIRED_CONNECTION + WIRELESS_CONNECTION),
event_is_on=set(WIRED_CONNECTION + WIRELESS_CONNECTION),
event_to_subscribe=(
WIRED_CONNECTION
+ WIRED_DISCONNECTION
@ -226,7 +226,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
entity_description: UnifiTrackerEntityDescription
_event_is_on: tuple[EventKey, ...]
_event_is_on: set[EventKey]
_ignore_events: bool
_is_connected: bool
@ -237,7 +237,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
Initiate is_connected.
"""
description = self.entity_description
self._event_is_on = description.event_is_on or ()
self._event_is_on = description.event_is_on or set()
self._ignore_events = False
self._is_connected = description.is_connected_fn(self.hub, self._obj_id)
if self.is_connected:
@ -255,12 +255,12 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
@property
def hostname(self) -> str | None:
"""Return hostname of the device."""
return self.entity_description.hostname_fn(self.hub.api, self._obj_id)
return self.entity_description.hostname_fn(self.api, self._obj_id)
@property
def ip_address(self) -> str | None:
"""Return the primary ip address of the device."""
return self.entity_description.ip_address_fn(self.hub.api, self._obj_id)
return self.entity_description.ip_address_fn(self.api, self._obj_id)
@cached_property
def mac_address(self) -> str:
@ -293,42 +293,45 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
Schedule new heartbeat check if connected.
"""
description = self.entity_description
hub = self.hub
if event == ItemEvent.CHANGED:
if event is ItemEvent.CHANGED:
# Prioritize normal data updates over events
self._ignore_events = True
elif event == ItemEvent.ADDED and not self.available:
elif event is ItemEvent.ADDED and not self.available:
# From unifi.entity.async_signal_reachable_callback
# Controller connection state has changed and entity is unavailable
# Cancel heartbeat
self.hub.remove_heartbeat(self.unique_id)
hub.remove_heartbeat(self.unique_id)
return
if is_connected := description.is_connected_fn(self.hub, self._obj_id):
obj_id = self._obj_id
if is_connected := description.is_connected_fn(hub, obj_id):
self._is_connected = is_connected
self.hub.update_heartbeat(
self.unique_id,
dt_util.utcnow()
+ description.heartbeat_timedelta_fn(self.hub, self._obj_id),
dt_util.utcnow() + description.heartbeat_timedelta_fn(hub, obj_id),
)
@callback
def async_event_callback(self, event: Event) -> None:
"""Event subscription callback."""
if event.mac != self._obj_id or self._ignore_events:
obj_id = self._obj_id
if event.mac != obj_id or self._ignore_events:
return
hub = self.hub
if event.key in self._event_is_on:
self.hub.remove_heartbeat(self.unique_id)
hub.remove_heartbeat(self.unique_id)
self._is_connected = True
self.async_write_ha_state()
return
self.hub.update_heartbeat(
hub.update_heartbeat(
self.unique_id,
dt_util.utcnow()
+ self.entity_description.heartbeat_timedelta_fn(self.hub, self._obj_id),
+ self.entity_description.heartbeat_timedelta_fn(hub, obj_id),
)
async def async_added_to_hass(self) -> None:
@ -353,7 +356,7 @@ class UnifiScannerEntity(UnifiEntity[HandlerT, ApiItemT], ScannerEntity):
if self.entity_description.key != "Client device scanner":
return None
client = self.entity_description.object_fn(self.hub.api, self._obj_id)
client = self.entity_description.object_fn(self.api, self._obj_id)
raw = client.raw
attributes_to_check = CLIENT_STATIC_ATTRIBUTES

View file

@ -120,7 +120,7 @@ class UnifiEntityDescription(EntityDescription, Generic[HandlerT, ApiItemT]):
# Optional constants
has_entity_name = True # Part of EntityDescription
"""Has entity name defaults to true."""
event_is_on: tuple[EventKey, ...] | None = None
event_is_on: set[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."""
@ -143,6 +143,7 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):
"""Set up UniFi switch entity."""
self._obj_id = obj_id
self.hub = hub
self.api = hub.api
self.entity_description = description
hub.entity_loader.known_objects.add((description.key, obj_id))
@ -154,14 +155,14 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):
self._attr_should_poll = description.should_poll
self._attr_unique_id = description.unique_id_fn(hub, obj_id)
obj = description.object_fn(self.hub.api, obj_id)
obj = description.object_fn(self.api, obj_id)
self._attr_name = description.name_fn(obj)
self.async_initiate_state()
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
description = self.entity_description
handler = description.api_handler_fn(self.hub.api)
handler = description.api_handler_fn(self.api)
@callback
def unregister_object() -> None:
@ -201,7 +202,7 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):
# Subscribe to events if defined
if description.event_to_subscribe is not None:
self.async_on_remove(
self.hub.api.events.subscribe(
self.api.events.subscribe(
self.async_event_callback,
description.event_to_subscribe,
)
@ -210,8 +211,8 @@ class UnifiEntity(Entity, Generic[HandlerT, ApiItemT]):
@callback
def async_signalling_callback(self, event: ItemEvent, obj_id: str) -> None:
"""Update the entity state."""
if event == ItemEvent.DELETED and obj_id == self._obj_id:
self.hass.async_create_task(self.remove_item({self._obj_id}))
if event is ItemEvent.DELETED and obj_id == self._obj_id:
self.hass.async_create_task(self.remove_item({obj_id}))
return
description = self.entity_description

View file

@ -97,7 +97,7 @@ class UnifiImageEntity(UnifiEntity[HandlerT, ApiItemT], ImageEntity):
"""Return bytes of image."""
if self.current_image is None:
description = self.entity_description
obj = description.object_fn(self.hub.api, self._obj_id)
obj = description.object_fn(self.api, self._obj_id)
self.current_image = description.image_fn(self.hub, obj)
return self.current_image
@ -105,7 +105,7 @@ class UnifiImageEntity(UnifiEntity[HandlerT, ApiItemT], ImageEntity):
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:
"""Update entity state."""
description = self.entity_description
obj = description.object_fn(self.hub.api, self._obj_id)
obj = description.object_fn(self.api, self._obj_id)
if (value := description.value_fn(obj)) != self.previous_value:
self.previous_value = value
self.current_image = None

View file

@ -490,7 +490,7 @@ class UnifiSensorEntity(UnifiEntity[HandlerT, ApiItemT], SensorEntity):
Update native_value.
"""
description = self.entity_description
obj = description.object_fn(self.hub.api, self._obj_id)
obj = description.object_fn(self.api, self._obj_id)
# Update the value only if value is considered to have changed relative to its previous state
if description.value_changed_fn(
self.native_value, (value := description.value_fn(self.hub, obj))

View file

@ -11,7 +11,7 @@ from __future__ import annotations
import asyncio
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from typing import TYPE_CHECKING, Any
import aiounifi
from aiounifi.interfaces.api_handlers import ItemEvent
@ -189,7 +189,7 @@ ENTITY_DESCRIPTIONS: tuple[UnifiSwitchEntityDescription, ...] = (
api_handler_fn=lambda api: api.clients,
control_fn=async_block_client_control_fn,
device_info_fn=async_client_device_info_fn,
event_is_on=CLIENT_UNBLOCKED,
event_is_on=set(CLIENT_UNBLOCKED),
event_to_subscribe=CLIENT_BLOCKED + CLIENT_UNBLOCKED,
is_on_fn=lambda hub, client: not client.blocked,
object_fn=lambda api, obj_id: api.clients[obj_id],
@ -342,7 +342,7 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity):
return
description = self.entity_description
obj = description.object_fn(self.hub.api, self._obj_id)
obj = description.object_fn(self.api, self._obj_id)
if (is_on := description.is_on_fn(self.hub, obj)) != self.is_on:
self._attr_is_on = is_on
@ -353,8 +353,9 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity):
return
description = self.entity_description
assert isinstance(description.event_to_subscribe, tuple)
assert isinstance(description.event_is_on, tuple)
if TYPE_CHECKING:
assert description.event_to_subscribe is not None
assert description.event_is_on is not None
if event.key in description.event_to_subscribe:
self._attr_is_on = event.key in description.event_is_on
@ -367,7 +368,7 @@ class UnifiSwitchEntity(UnifiEntity[HandlerT, ApiItemT], SwitchEntity):
if self.entity_description.custom_subscribe is not None:
self.async_on_remove(
self.entity_description.custom_subscribe(self.hub.api)(
self.entity_description.custom_subscribe(self.api)(
self.async_signalling_callback, ItemEvent.CHANGED
),
)

View file

@ -96,7 +96,7 @@ class UnifiDeviceUpdateEntity(UnifiEntity[_HandlerT, _DataT], UpdateEntity):
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
await self.entity_description.control_fn(self.hub.api, self._obj_id)
await self.entity_description.control_fn(self.api, self._obj_id)
@callback
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:
@ -106,7 +106,7 @@ class UnifiDeviceUpdateEntity(UnifiEntity[_HandlerT, _DataT], UpdateEntity):
"""
description = self.entity_description
obj = description.object_fn(self.hub.api, self._obj_id)
self._attr_in_progress = description.state_fn(self.hub.api, obj)
obj = description.object_fn(self.api, self._obj_id)
self._attr_in_progress = description.state_fn(self.api, obj)
self._attr_installed_version = obj.version
self._attr_latest_version = obj.upgrade_to_firmware or obj.version