diff --git a/homeassistant/components/unifiprotect/binary_sensor.py b/homeassistant/components/unifiprotect/binary_sensor.py index 4218d3108e5..19ae4504109 100644 --- a/homeassistant/components/unifiprotect/binary_sensor.py +++ b/homeassistant/components/unifiprotect/binary_sensor.py @@ -4,7 +4,6 @@ from __future__ import annotations from collections.abc import Sequence import dataclasses -import logging from uiprotect.data import ( NVR, @@ -35,15 +34,14 @@ from .entity import ( ProtectNVREntity, async_all_device_entities, ) -from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin +from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin -_LOGGER = logging.getLogger(__name__) _KEY_DOOR = "door" @dataclasses.dataclass(frozen=True, kw_only=True) class ProtectBinaryEntityDescription( - ProtectRequiredKeysMixin, BinarySensorEntityDescription + ProtectEntityDescription, BinarySensorEntityDescription ): """Describes UniFi Protect Binary Sensor entity.""" @@ -613,7 +611,7 @@ DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = ( ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA_SENSORS, ModelType.LIGHT: LIGHT_SENSORS, ModelType.SENSOR: SENSE_SENSORS, @@ -621,7 +619,7 @@ _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { ModelType.VIEWPORT: VIEWER_SENSORS, } -_MOUNTABLE_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MOUNTABLE_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.SENSOR: MOUNTABLE_SENSE_SENSORS, } @@ -652,10 +650,9 @@ class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - updated_device = self.device # UP Sense can be any of the 3 contact sensor device classes self._attr_device_class = MOUNT_DEVICE_CLASS_MAP.get( - updated_device.mount_type, BinarySensorDeviceClass.DOOR + self.device.mount_type, BinarySensorDeviceClass.DOOR ) @@ -688,7 +685,6 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) - slot = self._disk.slot self._attr_available = False @@ -714,7 +710,7 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity): def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) is_on = self.entity_description.get_is_on(self.device, self._event) - self._attr_is_on: bool | None = is_on + self._attr_is_on = is_on if not is_on: self._event = None self._attr_extra_state_attributes = {} diff --git a/homeassistant/components/unifiprotect/button.py b/homeassistant/components/unifiprotect/button.py index 7866dd5b183..8a6c4b38ea5 100644 --- a/homeassistant/components/unifiprotect/button.py +++ b/homeassistant/components/unifiprotect/button.py @@ -23,7 +23,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import DEVICES_THAT_ADOPT, DISPATCH_ADD, DOMAIN from .data import ProtectData, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities -from .models import PermRequired, ProtectRequiredKeysMixin, ProtectSetableKeysMixin, T +from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .utils import async_dispatch_id as _ufpd _LOGGER = logging.getLogger(__name__) @@ -95,7 +95,7 @@ CHIME_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = ( ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CHIME: CHIME_BUTTONS, ModelType.SENSOR: SENSOR_BUTTONS, } diff --git a/homeassistant/components/unifiprotect/camera.py b/homeassistant/components/unifiprotect/camera.py index b4596582cd6..a08e0f03e65 100644 --- a/homeassistant/components/unifiprotect/camera.py +++ b/homeassistant/components/unifiprotect/camera.py @@ -158,6 +158,9 @@ async def async_setup_entry( async_add_entities(_async_camera_entities(hass, entry, data)) +_EMPTY_CAMERA_FEATURES = CameraEntityFeature(0) + + class ProtectCamera(ProtectDeviceEntity, Camera): """A Ubiquiti UniFi Protect Camera.""" @@ -206,13 +209,12 @@ class ProtectCamera(ProtectDeviceEntity, Camera): rtsp_url = channel.rtsps_url if self._secure else channel.rtsp_url # _async_set_stream_source called by __init__ - self._stream_source = ( # pylint: disable=attribute-defined-outside-init - None if disable_stream else rtsp_url - ) + # pylint: disable-next=attribute-defined-outside-init + self._stream_source = None if disable_stream else rtsp_url if self._stream_source: self._attr_supported_features = CameraEntityFeature.STREAM else: - self._attr_supported_features = CameraEntityFeature(0) + self._attr_supported_features = _EMPTY_CAMERA_FEATURES @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: diff --git a/homeassistant/components/unifiprotect/entity.py b/homeassistant/components/unifiprotect/entity.py index d1b82dd218f..1a89b7c06d1 100644 --- a/homeassistant/components/unifiprotect/entity.py +++ b/homeassistant/components/unifiprotect/entity.py @@ -6,7 +6,7 @@ from collections.abc import Callable, Sequence from functools import partial import logging from operator import attrgetter -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from uiprotect.data import ( NVR, @@ -31,7 +31,7 @@ from .const import ( DOMAIN, ) from .data import ProtectData -from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin +from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin _LOGGER = logging.getLogger(__name__) @@ -41,8 +41,8 @@ def _async_device_entities( data: ProtectData, klass: type[BaseProtectEntity], model_type: ModelType, - descs: Sequence[ProtectRequiredKeysMixin], - unadopted_descs: Sequence[ProtectRequiredKeysMixin] | None = None, + descs: Sequence[ProtectEntityDescription], + unadopted_descs: Sequence[ProtectEntityDescription] | None = None, ufp_device: ProtectAdoptableDeviceModel | None = None, ) -> list[BaseProtectEntity]: if not descs and not unadopted_descs: @@ -119,11 +119,11 @@ _ALL_MODEL_TYPES = ( @callback def _combine_model_descs( model_type: ModelType, - model_descriptions: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] | None, - all_descs: Sequence[ProtectRequiredKeysMixin] | None, -) -> list[ProtectRequiredKeysMixin]: + model_descriptions: dict[ModelType, Sequence[ProtectEntityDescription]] | None, + all_descs: Sequence[ProtectEntityDescription] | None, +) -> list[ProtectEntityDescription]: """Combine all the descriptions with descriptions a model type.""" - descs: list[ProtectRequiredKeysMixin] = list(all_descs) if all_descs else [] + descs: list[ProtectEntityDescription] = list(all_descs) if all_descs else [] if model_descriptions and (model_descs := model_descriptions.get(model_type)): descs.extend(model_descs) return descs @@ -133,10 +133,10 @@ def _combine_model_descs( def async_all_device_entities( data: ProtectData, klass: type[BaseProtectEntity], - model_descriptions: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] + model_descriptions: dict[ModelType, Sequence[ProtectEntityDescription]] | None = None, - all_descs: Sequence[ProtectRequiredKeysMixin] | None = None, - unadopted_descs: list[ProtectRequiredKeysMixin] | None = None, + all_descs: Sequence[ProtectEntityDescription] | None = None, + unadopted_descs: list[ProtectEntityDescription] | None = None, ufp_device: ProtectAdoptableDeviceModel | None = None, ) -> list[BaseProtectEntity]: """Generate a list of all the device entities.""" @@ -163,6 +163,7 @@ class BaseProtectEntity(Entity): device: ProtectAdoptableDeviceModel | NVR _attr_should_poll = False + _attr_attribution = DEFAULT_ATTRIBUTION _state_attrs: tuple[str, ...] = ("_attr_available",) def __init__( @@ -191,10 +192,9 @@ class BaseProtectEntity(Entity): else "" ) self._attr_name = f"{self.device.display_name} {name.title()}" - if isinstance(description, ProtectRequiredKeysMixin): + if isinstance(description, ProtectEntityDescription): self._async_get_ufp_enabled = description.get_ufp_enabled - self._attr_attribution = DEFAULT_ATTRIBUTION self._async_set_device_info() self._async_update_device_from_protect(device) self._state_getters = tuple( @@ -301,8 +301,7 @@ class ProtectNVREntity(BaseProtectEntity): @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: data = self.data - last_update_success = data.last_update_success - if last_update_success: + if last_update_success := data.last_update_success: self.device = data.api.bootstrap.nvr self._attr_available = last_update_success @@ -311,28 +310,18 @@ class ProtectNVREntity(BaseProtectEntity): class EventEntityMixin(ProtectDeviceEntity): """Adds motion event attributes to sensor.""" - _unrecorded_attributes = frozenset({ATTR_EVENT_ID, ATTR_EVENT_SCORE}) - entity_description: ProtectEventMixin - - def __init__( - self, - *args: Any, - **kwarg: Any, - ) -> None: - """Init an sensor that has event thumbnails.""" - super().__init__(*args, **kwarg) - self._event: Event | None = None + _unrecorded_attributes = frozenset({ATTR_EVENT_ID, ATTR_EVENT_SCORE}) + _event: Event | None = None @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: - event = self.entity_description.get_event_obj(device) - if event is not None: + if (event := self.entity_description.get_event_obj(device)) is None: + self._attr_extra_state_attributes = {} + else: self._attr_extra_state_attributes = { ATTR_EVENT_ID: event.id, ATTR_EVENT_SCORE: event.score, } - else: - self._attr_extra_state_attributes = {} self._event = event super()._async_update_device_from_protect(device) diff --git a/homeassistant/components/unifiprotect/models.py b/homeassistant/components/unifiprotect/models.py index d2ab31d672d..36db9a847c7 100644 --- a/homeassistant/components/unifiprotect/models.py +++ b/homeassistant/components/unifiprotect/models.py @@ -5,8 +5,10 @@ from __future__ import annotations from collections.abc import Callable, Coroutine from dataclasses import dataclass from enum import Enum +from functools import partial import logging -from typing import TYPE_CHECKING, Any, Generic, TypeVar +from operator import attrgetter +from typing import Any, Generic, TypeVar from uiprotect.data import NVR, Event, ProtectAdoptableDeviceModel @@ -19,15 +21,6 @@ _LOGGER = logging.getLogger(__name__) T = TypeVar("T", bound=ProtectAdoptableDeviceModel | NVR) -def split_tuple(value: tuple[str, ...] | str | None) -> tuple[str, ...] | None: - """Split string to tuple.""" - if value is None: - return None - if TYPE_CHECKING: - assert isinstance(value, str) - return tuple(value.split(".")) - - class PermRequired(int, Enum): """Type of permission level required for entity.""" @@ -37,92 +30,83 @@ class PermRequired(int, Enum): @dataclass(frozen=True, kw_only=True) -class ProtectRequiredKeysMixin(EntityDescription, Generic[T]): - """Mixin for required keys.""" +class ProtectEntityDescription(EntityDescription, Generic[T]): + """Base class for protect entity descriptions.""" - # `ufp_required_field`, `ufp_value`, and `ufp_enabled` are defined as - # a `str` in the dataclass, but `__post_init__` converts it to a - # `tuple[str, ...]` to avoid doing it at run time in `get_nested_attr` - # which is usually called millions of times per day. - ufp_required_field: tuple[str, ...] | str | None = None - ufp_value: tuple[str, ...] | str | None = None + ufp_required_field: str | None = None + ufp_value: str | None = None ufp_value_fn: Callable[[T], Any] | None = None - ufp_enabled: tuple[str, ...] | str | None = None + ufp_enabled: str | None = None ufp_perm: PermRequired | None = None - def __post_init__(self) -> None: - """Pre-convert strings to tuples for faster get_nested_attr.""" - object.__setattr__( - self, "ufp_required_field", split_tuple(self.ufp_required_field) - ) - object.__setattr__(self, "ufp_value", split_tuple(self.ufp_value)) - object.__setattr__(self, "ufp_enabled", split_tuple(self.ufp_enabled)) - def get_ufp_value(self, obj: T) -> Any: - """Return value from UniFi Protect device.""" - if (ufp_value := self.ufp_value) is not None: - if TYPE_CHECKING: - # `ufp_value` is defined as a `str` in the dataclass, but - # `__post_init__` converts it to a `tuple[str, ...]` to avoid - # doing it at run time in `get_nested_attr` which is usually called - # millions of times per day. This tells mypy that it's a tuple. - assert isinstance(ufp_value, tuple) - return get_nested_attr(obj, ufp_value) - if (ufp_value_fn := self.ufp_value_fn) is not None: - return ufp_value_fn(obj) + """Return value from UniFi Protect device. - # reminder for future that one is required + May be overridden by ufp_value or ufp_value_fn. + """ + # ufp_value or ufp_value_fn is required, the + # RuntimeError is to catch any issues in the code + # with new descriptions. raise RuntimeError( # pragma: no cover "`ufp_value` or `ufp_value_fn` is required" ) - def get_ufp_enabled(self, obj: T) -> bool: - """Return value from UniFi Protect device.""" - if (ufp_enabled := self.ufp_enabled) is not None: - if TYPE_CHECKING: - # `ufp_enabled` is defined as a `str` in the dataclass, but - # `__post_init__` converts it to a `tuple[str, ...]` to avoid - # doing it at run time in `get_nested_attr` which is usually called - # millions of times per day. This tells mypy that it's a tuple. - assert isinstance(ufp_enabled, tuple) - return bool(get_nested_attr(obj, ufp_enabled)) + def has_required(self, obj: T) -> bool: + """Return if required field is set. + + May be overridden by ufp_required_field. + """ return True - def has_required(self, obj: T) -> bool: - """Return if has required field.""" - if (ufp_required_field := self.ufp_required_field) is None: - return True - if TYPE_CHECKING: - # `ufp_required_field` is defined as a `str` in the dataclass, but - # `__post_init__` converts it to a `tuple[str, ...]` to avoid - # doing it at run time in `get_nested_attr` which is usually called - # millions of times per day. This tells mypy that it's a tuple. - assert isinstance(ufp_required_field, tuple) - return bool(get_nested_attr(obj, ufp_required_field)) + def get_ufp_enabled(self, obj: T) -> bool: + """Return if entity is enabled. + + May be overridden by ufp_enabled. + """ + return True + + def __post_init__(self) -> None: + """Override get_ufp_value, has_required, and get_ufp_enabled if required.""" + _setter = partial(object.__setattr__, self) + if (_ufp_value := self.ufp_value) is not None: + ufp_value = tuple(_ufp_value.split(".")) + _setter("get_ufp_value", partial(get_nested_attr, attrs=ufp_value)) + elif (ufp_value_fn := self.ufp_value_fn) is not None: + _setter("get_ufp_value", ufp_value_fn) + if (_ufp_enabled := self.ufp_enabled) is not None: + ufp_enabled = tuple(_ufp_enabled.split(".")) + _setter("get_ufp_enabled", partial(get_nested_attr, attrs=ufp_enabled)) + if (_ufp_required_field := self.ufp_required_field) is not None: + ufp_required_field = tuple(_ufp_required_field.split(".")) + _setter( + "has_required", + lambda obj: bool(get_nested_attr(obj, ufp_required_field)), + ) @dataclass(frozen=True, kw_only=True) -class ProtectEventMixin(ProtectRequiredKeysMixin[T]): +class ProtectEventMixin(ProtectEntityDescription[T]): """Mixin for events.""" ufp_event_obj: str | None = None def get_event_obj(self, obj: T) -> Event | None: """Return value from UniFi Protect device.""" - - if self.ufp_event_obj is not None: - event: Event | None = getattr(obj, self.ufp_event_obj, None) - return event return None + def __post_init__(self) -> None: + """Override get_event_obj if ufp_event_obj is set.""" + if (_ufp_event_obj := self.ufp_event_obj) is not None: + object.__setattr__(self, "get_event_obj", attrgetter(_ufp_event_obj)) + super().__post_init__() + def get_is_on(self, obj: T, event: Event | None) -> bool: """Return value if event is active.""" - return event is not None and self.get_ufp_value(obj) @dataclass(frozen=True, kw_only=True) -class ProtectSetableKeysMixin(ProtectRequiredKeysMixin[T]): +class ProtectSetableKeysMixin(ProtectEntityDescription[T]): """Mixin for settable values.""" ufp_set_method: str | None = None diff --git a/homeassistant/components/unifiprotect/number.py b/homeassistant/components/unifiprotect/number.py index 44f965e4796..3719dcbd4ac 100644 --- a/homeassistant/components/unifiprotect/number.py +++ b/homeassistant/components/unifiprotect/number.py @@ -5,7 +5,6 @@ from __future__ import annotations from collections.abc import Sequence from dataclasses import dataclass from datetime import timedelta -import logging from uiprotect.data import ( Camera, @@ -23,9 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .data import ProtectData, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities -from .models import PermRequired, ProtectRequiredKeysMixin, ProtectSetableKeysMixin, T - -_LOGGER = logging.getLogger(__name__) +from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T @dataclass(frozen=True, kw_only=True) @@ -213,7 +210,7 @@ CHIME_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = ( ufp_perm=PermRequired.WRITE, ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA_NUMBERS, ModelType.LIGHT: LIGHT_NUMBERS, ModelType.SENSOR: SENSE_NUMBERS, diff --git a/homeassistant/components/unifiprotect/select.py b/homeassistant/components/unifiprotect/select.py index 2dd52fac774..d6e0f638d2d 100644 --- a/homeassistant/components/unifiprotect/select.py +++ b/homeassistant/components/unifiprotect/select.py @@ -35,7 +35,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import TYPE_EMPTY_VALUE from .data import ProtectData, UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities -from .models import PermRequired, ProtectRequiredKeysMixin, ProtectSetableKeysMixin, T +from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .utils import async_get_light_motion_current _LOGGER = logging.getLogger(__name__) @@ -319,7 +319,7 @@ VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = ( ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA_SELECTS, ModelType.LIGHT: LIGHT_SELECTS, ModelType.SENSOR: SENSE_SELECTS, diff --git a/homeassistant/components/unifiprotect/sensor.py b/homeassistant/components/unifiprotect/sensor.py index 56b7ef7f9a4..3e849bc1ad1 100644 --- a/homeassistant/components/unifiprotect/sensor.py +++ b/homeassistant/components/unifiprotect/sensor.py @@ -47,7 +47,7 @@ from .entity import ( ProtectNVREntity, async_all_device_entities, ) -from .models import PermRequired, ProtectEventMixin, ProtectRequiredKeysMixin, T +from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin, T from .utils import async_get_light_motion_current _LOGGER = logging.getLogger(__name__) @@ -56,7 +56,7 @@ OBJECT_TYPE_NONE = "none" @dataclass(frozen=True, kw_only=True) class ProtectSensorEntityDescription( - ProtectRequiredKeysMixin[T], SensorEntityDescription + ProtectEntityDescription[T], SensorEntityDescription ): """Describes UniFi Protect Sensor entity.""" @@ -608,7 +608,7 @@ VIEWER_SENSORS: tuple[ProtectSensorEntityDescription, ...] = ( ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA_SENSORS + CAMERA_DISABLED_SENSORS, ModelType.SENSOR: SENSE_SENSORS, ModelType.LIGHT: LIGHT_SENSORS, diff --git a/homeassistant/components/unifiprotect/switch.py b/homeassistant/components/unifiprotect/switch.py index 36c2c497b57..32dc5808005 100644 --- a/homeassistant/components/unifiprotect/switch.py +++ b/homeassistant/components/unifiprotect/switch.py @@ -30,7 +30,7 @@ from .entity import ( ProtectNVREntity, async_all_device_entities, ) -from .models import PermRequired, ProtectRequiredKeysMixin, ProtectSetableKeysMixin, T +from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T _LOGGER = logging.getLogger(__name__) ATTR_PREV_MIC = "prev_mic_level" @@ -459,7 +459,7 @@ NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA_SWITCHES, ModelType.LIGHT: LIGHT_SWITCHES, ModelType.SENSOR: SENSE_SWITCHES, @@ -467,7 +467,7 @@ _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { ModelType.VIEWPORT: VIEWER_SWITCHES, } -_PRIVACY_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_PRIVACY_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: [PRIVACY_MODE_SWITCH] } @@ -487,7 +487,6 @@ class ProtectSwitch(ProtectDeviceEntity, SwitchEntity): """Initialize an UniFi Protect Switch.""" super().__init__(data, device, description) self._attr_name = f"{self.device.display_name} {self.entity_description.name}" - self._switch_type = self.entity_description.key def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device) @@ -539,21 +538,20 @@ class ProtectPrivacyModeSwitch(RestoreEntity, ProtectSwitch): def __init__( self, data: ProtectData, - device: ProtectAdoptableDeviceModel, + device: Camera, description: ProtectSwitchEntityDescription, ) -> None: """Initialize an UniFi Protect Switch.""" super().__init__(data, device, description) - - if self.device.is_privacy_on: + if device.is_privacy_on: extra_state = self.extra_state_attributes or {} self._previous_mic_level = extra_state.get(ATTR_PREV_MIC, 100) self._previous_record_mode = extra_state.get( ATTR_PREV_RECORD, RecordingMode.ALWAYS ) else: - self._previous_mic_level = self.device.mic_volume - self._previous_record_mode = self.device.recording_settings.mode + self._previous_mic_level = device.mic_volume + self._previous_record_mode = device.recording_settings.mode @callback def _update_previous_attr(self) -> None: diff --git a/homeassistant/components/unifiprotect/text.py b/homeassistant/components/unifiprotect/text.py index 009e013ee51..e01a6b31f11 100644 --- a/homeassistant/components/unifiprotect/text.py +++ b/homeassistant/components/unifiprotect/text.py @@ -18,9 +18,9 @@ from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .data import ProtectData, UFPConfigEntry +from .data import UFPConfigEntry from .entity import ProtectDeviceEntity, async_all_device_entities -from .models import PermRequired, ProtectRequiredKeysMixin, ProtectSetableKeysMixin, T +from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T @dataclass(frozen=True, kw_only=True) @@ -50,7 +50,7 @@ CAMERA: tuple[ProtectTextEntityDescription, ...] = ( ), ) -_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectRequiredKeysMixin]] = { +_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = { ModelType.CAMERA: CAMERA, } @@ -88,15 +88,6 @@ class ProtectDeviceText(ProtectDeviceEntity, TextEntity): entity_description: ProtectTextEntityDescription _state_attrs = ("_attr_available", "_attr_native_value") - def __init__( - self, - data: ProtectData, - device: ProtectAdoptableDeviceModel, - description: ProtectTextEntityDescription, - ) -> None: - """Initialize an UniFi Protect sensor.""" - super().__init__(data, device, description) - @callback def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: super()._async_update_device_from_protect(device)