Cleanup unifiprotect entity classes (#121184)

This commit is contained in:
J. Nick Koston 2024-07-05 02:31:31 -05:00 committed by GitHub
parent 22718ca32a
commit d3f424227f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 129 additions and 165 deletions

View File

@ -8,11 +8,9 @@ import dataclasses
from uiprotect.data import ( from uiprotect.data import (
NVR, NVR,
Camera, Camera,
Light,
ModelType, ModelType,
MountType, MountType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
Sensor, Sensor,
SmartDetectObjectType, SmartDetectObjectType,
) )
@ -27,11 +25,12 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ( from .entity import (
BaseProtectEntity, BaseProtectEntity,
EventEntityMixin, EventEntityMixin,
ProtectDeviceEntity, ProtectDeviceEntity,
ProtectIsOnEntity,
ProtectNVREntity, ProtectNVREntity,
async_all_device_entities, async_all_device_entities,
) )
@ -623,31 +622,22 @@ _MOUNTABLE_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription
} }
class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity): class ProtectDeviceBinarySensor(
ProtectIsOnEntity, ProtectDeviceEntity, BinarySensorEntity
):
"""A UniFi Protect Device Binary Sensor.""" """A UniFi Protect Device Binary Sensor."""
device: Camera | Light | Sensor
entity_description: ProtectBinaryEntityDescription entity_description: ProtectBinaryEntityDescription
_state_attrs: tuple[str, ...] = ("_attr_available", "_attr_is_on")
@callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
super()._async_update_device_from_protect(device)
self._attr_is_on = self.entity_description.get_ufp_value(self.device)
class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor): class MountableProtectDeviceBinarySensor(ProtectDeviceBinarySensor):
"""A UniFi Protect Device Binary Sensor that can change device class at runtime.""" """A UniFi Protect Device Binary Sensor that can change device class at runtime."""
device: Sensor device: Sensor
_state_attrs: tuple[str, ...] = ( _state_attrs = ("_attr_available", "_attr_is_on", "_attr_device_class")
"_attr_available",
"_attr_is_on",
"_attr_device_class",
)
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
# UP Sense can be any of the 3 contact sensor device classes # UP Sense can be any of the 3 contact sensor device classes
self._attr_device_class = MOUNT_DEVICE_CLASS_MAP.get( self._attr_device_class = MOUNT_DEVICE_CLASS_MAP.get(
@ -673,7 +663,6 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
self._disk = disk self._disk = disk
# backwards compat with old unique IDs # backwards compat with old unique IDs
index = self._disk.slot - 1 index = self._disk.slot - 1
description = dataclasses.replace( description = dataclasses.replace(
description, description,
key=f"{description.key}_{index}", key=f"{description.key}_{index}",
@ -682,7 +671,7 @@ class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
super().__init__(data, device, description) super().__init__(data, device, description)
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
slot = self._disk.slot slot = self._disk.slot
self._attr_available = False self._attr_available = False
@ -712,7 +701,7 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity):
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
description = self.entity_description description = self.entity_description
prev_event = self._event prev_event = self._event

View File

@ -4,10 +4,11 @@ from __future__ import annotations
from collections.abc import Sequence from collections.abc import Sequence
from dataclasses import dataclass from dataclasses import dataclass
from functools import partial
import logging import logging
from typing import Final from typing import TYPE_CHECKING, Final
from uiprotect.data import ModelType, ProtectAdoptableDeviceModel, ProtectModelWithId from uiprotect.data import ModelType, ProtectAdoptableDeviceModel
from homeassistant.components.button import ( from homeassistant.components.button import (
ButtonDeviceClass, ButtonDeviceClass,
@ -21,7 +22,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DEVICES_THAT_ADOPT, DOMAIN from .const import DEVICES_THAT_ADOPT, DOMAIN
from .data import UFPConfigEntry from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
@ -38,7 +39,6 @@ class ProtectButtonEntityDescription(
DEVICE_CLASS_CHIME_BUTTON: Final = "unifiprotect__chime_button" DEVICE_CLASS_CHIME_BUTTON: Final = "unifiprotect__chime_button"
KEY_ADOPT = "adopt"
ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = ( ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
@ -61,7 +61,7 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
) )
ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel]( ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel](
key=KEY_ADOPT, key="adopt",
name="Adopt device", name="Adopt device",
icon="mdi:plus-circle", icon="mdi:plus-circle",
ufp_press="adopt", ufp_press="adopt",
@ -119,17 +119,25 @@ async def async_setup_entry(
"""Discover devices on a UniFi Protect NVR.""" """Discover devices on a UniFi Protect NVR."""
data = entry.runtime_data data = entry.runtime_data
adopt_entities = partial(
async_all_device_entities,
data,
ProtectAdoptButton,
unadopted_descs=[ADOPT_BUTTON],
)
base_entities = partial(
async_all_device_entities,
data,
ProtectButton,
all_descs=ALL_DEVICE_BUTTONS,
model_descriptions=_MODEL_DESCRIPTIONS,
)
@callback @callback
def _add_new_device(device: ProtectAdoptableDeviceModel) -> None: def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
entities = async_all_device_entities( async_add_entities(
data, [*base_entities(ufp_device=device), *adopt_entities(ufp_device=device)]
ProtectButton,
all_descs=ALL_DEVICE_BUTTONS,
unadopted_descs=[ADOPT_BUTTON],
model_descriptions=_MODEL_DESCRIPTIONS,
ufp_device=device,
) )
async_add_entities(entities)
_async_remove_adopt_button(hass, device) _async_remove_adopt_button(hass, device)
@callback @callback
@ -137,29 +145,13 @@ async def async_setup_entry(
if not device.can_adopt or not device.can_create(data.api.bootstrap.auth_user): if not device.can_adopt or not device.can_create(data.api.bootstrap.auth_user):
_LOGGER.debug("Device is not adoptable: %s", device.id) _LOGGER.debug("Device is not adoptable: %s", device.id)
return return
async_add_entities( async_add_entities(adopt_entities(ufp_device=device))
async_all_device_entities(
data,
ProtectButton,
unadopted_descs=[ADOPT_BUTTON],
ufp_device=device,
)
)
data.async_subscribe_adopt(_add_new_device) data.async_subscribe_adopt(_add_new_device)
entry.async_on_unload( entry.async_on_unload(
async_dispatcher_connect(hass, data.add_signal, _async_add_unadopted_device) async_dispatcher_connect(hass, data.add_signal, _async_add_unadopted_device)
) )
async_add_entities([*base_entities(), *adopt_entities()])
async_add_entities(
async_all_device_entities(
data,
ProtectButton,
all_descs=ALL_DEVICE_BUTTONS,
unadopted_descs=[ADOPT_BUTTON],
model_descriptions=_MODEL_DESCRIPTIONS,
)
)
for device in data.get_by_types(DEVICES_THAT_ADOPT): for device in data.get_by_types(DEVICES_THAT_ADOPT):
_async_remove_adopt_button(hass, device) _async_remove_adopt_button(hass, device)
@ -170,16 +162,20 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity):
entity_description: ProtectButtonEntityDescription entity_description: ProtectButtonEntityDescription
@callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
super()._async_update_device_from_protect(device)
if self.entity_description.key == KEY_ADOPT:
device = self.device
self._attr_available = device.can_adopt and device.can_create(
self.data.api.bootstrap.auth_user
)
async def async_press(self) -> None: async def async_press(self) -> None:
"""Press the button.""" """Press the button."""
if self.entity_description.ufp_press is not None: if self.entity_description.ufp_press is not None:
await getattr(self.device, self.entity_description.ufp_press)() await getattr(self.device, self.entity_description.ufp_press)()
class ProtectAdoptButton(ProtectButton):
"""A Ubiquiti UniFi Protect Adopt button."""
@callback
def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device)
if TYPE_CHECKING:
assert isinstance(device, ProtectAdoptableDeviceModel)
self._attr_available = device.can_adopt and device.can_create(
self.data.api.bootstrap.auth_user
)

View File

@ -9,7 +9,6 @@ from uiprotect.data import (
Camera as UFPCamera, Camera as UFPCamera,
CameraChannel, CameraChannel,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
StateType, StateType,
) )
@ -28,7 +27,7 @@ from .const import (
ATTR_WIDTH, ATTR_WIDTH,
DOMAIN, DOMAIN,
) )
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity from .entity import ProtectDeviceEntity
from .utils import get_camera_base_name from .utils import get_camera_base_name
@ -216,7 +215,7 @@ class ProtectCamera(ProtectDeviceEntity, Camera):
self._attr_supported_features = _EMPTY_CAMERA_FEATURES self._attr_supported_features = _EMPTY_CAMERA_FEATURES
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
updated_device = self.device updated_device = self.device
channel = updated_device.channels[self.channel.id] channel = updated_device.channels[self.channel.id]

View File

@ -9,14 +9,7 @@ import logging
from operator import attrgetter from operator import attrgetter
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from uiprotect.data import ( from uiprotect.data import NVR, Event, ModelType, ProtectAdoptableDeviceModel, StateType
NVR,
Event,
ModelType,
ProtectAdoptableDeviceModel,
ProtectModelWithId,
StateType,
)
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.device_registry as dr import homeassistant.helpers.device_registry as dr
@ -30,7 +23,7 @@ from .const import (
DEFAULT_BRAND, DEFAULT_BRAND,
DOMAIN, DOMAIN,
) )
from .data import ProtectData from .data import ProtectData, ProtectDeviceType
from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin from .models import PermRequired, ProtectEntityDescription, ProtectEventMixin
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -160,7 +153,7 @@ def async_all_device_entities(
class BaseProtectEntity(Entity): class BaseProtectEntity(Entity):
"""Base class for UniFi protect entities.""" """Base class for UniFi protect entities."""
device: ProtectAdoptableDeviceModel | NVR device: ProtectDeviceType
_attr_should_poll = False _attr_should_poll = False
_attr_attribution = DEFAULT_ATTRIBUTION _attr_attribution = DEFAULT_ATTRIBUTION
@ -171,7 +164,7 @@ class BaseProtectEntity(Entity):
def __init__( def __init__(
self, self,
data: ProtectData, data: ProtectData,
device: ProtectAdoptableDeviceModel | NVR, device: ProtectDeviceType,
description: EntityDescription | None = None, description: EntityDescription | None = None,
) -> None: ) -> None:
"""Initialize the entity.""" """Initialize the entity."""
@ -203,37 +196,32 @@ class BaseProtectEntity(Entity):
@callback @callback
def _async_set_device_info(self) -> None: def _async_set_device_info(self) -> None:
self._attr_device_info = DeviceInfo( """Set device info."""
name=self.device.display_name,
manufacturer=DEFAULT_BRAND,
model=self.device.type,
via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac),
sw_version=self.device.firmware_version,
connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)},
configuration_url=self.device.protect_url,
)
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
"""Update Entity object from Protect device.""" """Update Entity object from Protect device."""
if TYPE_CHECKING: was_available = self._attr_available
assert isinstance(device, ProtectAdoptableDeviceModel) if last_updated_success := self.data.last_update_success:
if last_update_success := self.data.last_update_success:
self.device = device self.device = device
async_get_ufp_enabled = self._async_get_ufp_enabled if device.model is ModelType.NVR:
self._attr_available = ( available = last_updated_success
last_update_success else:
and ( if TYPE_CHECKING:
device.state is StateType.CONNECTED assert isinstance(device, ProtectAdoptableDeviceModel)
or (not device.is_adopted_by_us and device.can_adopt) connected = device.state is StateType.CONNECTED or (
not device.is_adopted_by_us and device.can_adopt
) )
and (not async_get_ufp_enabled or async_get_ufp_enabled(device)) async_get_ufp_enabled = self._async_get_ufp_enabled
) enabled = not async_get_ufp_enabled or async_get_ufp_enabled(device)
available = last_updated_success and connected and enabled
if available != was_available:
self._attr_available = available
@callback @callback
def _async_updated_event(self, device: ProtectAdoptableDeviceModel | NVR) -> None: def _async_updated_event(self, device: ProtectDeviceType) -> None:
"""When device is updated from Protect.""" """When device is updated from Protect."""
previous_attrs = [getter() for getter in self._state_getters] previous_attrs = [getter() for getter in self._state_getters]
self._async_update_device_from_protect(device) self._async_update_device_from_protect(device)
@ -266,10 +254,36 @@ class BaseProtectEntity(Entity):
) )
class ProtectIsOnEntity(BaseProtectEntity):
"""Base class for entities with is_on property."""
_state_attrs: tuple[str, ...] = ("_attr_available", "_attr_is_on")
_attr_is_on: bool | None
entity_description: ProtectEntityDescription
def _async_update_device_from_protect(
self, device: ProtectAdoptableDeviceModel | NVR
) -> None:
super()._async_update_device_from_protect(device)
was_on = self._attr_is_on
if was_on != (is_on := self.entity_description.get_ufp_value(device) is True):
self._attr_is_on = is_on
class ProtectDeviceEntity(BaseProtectEntity): class ProtectDeviceEntity(BaseProtectEntity):
"""Base class for UniFi protect entities.""" """Base class for UniFi protect entities."""
device: ProtectAdoptableDeviceModel @callback
def _async_set_device_info(self) -> None:
self._attr_device_info = DeviceInfo(
name=self.device.display_name,
manufacturer=DEFAULT_BRAND,
model=self.device.type,
via_device=(DOMAIN, self.data.api.bootstrap.nvr.mac),
sw_version=self.device.firmware_version,
connections={(dr.CONNECTION_NETWORK_MAC, self.device.mac)},
configuration_url=self.device.protect_url,
)
class ProtectNVREntity(BaseProtectEntity): class ProtectNVREntity(BaseProtectEntity):
@ -289,14 +303,6 @@ class ProtectNVREntity(BaseProtectEntity):
configuration_url=self.device.api.base_url, configuration_url=self.device.api.base_url,
) )
@callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
data = self.data
if last_update_success := data.last_update_success:
self.device = data.api.bootstrap.nvr
self._attr_available = last_update_success
class EventEntityMixin(ProtectDeviceEntity): class EventEntityMixin(ProtectDeviceEntity):
"""Adds motion event attributes to sensor.""" """Adds motion event attributes to sensor."""
@ -338,9 +344,8 @@ class EventEntityMixin(ProtectDeviceEntity):
event object so we need to check the datetime object that was event object so we need to check the datetime object that was
saved from the last time the entity was updated. saved from the last time the entity was updated.
""" """
event = self._event
return bool( return bool(
event (event := self._event)
and event.end and event.end
and prev_event and prev_event
and prev_event_end and prev_event_end

View File

@ -4,12 +4,7 @@ from __future__ import annotations
import dataclasses import dataclasses
from uiprotect.data import ( from uiprotect.data import Camera, EventType, ProtectAdoptableDeviceModel
Camera,
EventType,
ProtectAdoptableDeviceModel,
ProtectModelWithId,
)
from homeassistant.components.event import ( from homeassistant.components.event import (
EventDeviceClass, EventDeviceClass,
@ -20,7 +15,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_EVENT_ID from .const import ATTR_EVENT_ID
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import EventEntityMixin, ProtectDeviceEntity from .entity import EventEntityMixin, ProtectDeviceEntity
from .models import ProtectEventMixin from .models import ProtectEventMixin
@ -50,7 +45,7 @@ class ProtectDeviceEventEntity(EventEntityMixin, ProtectDeviceEntity, EventEntit
entity_description: ProtectEventEntityDescription entity_description: ProtectEventEntityDescription
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
description = self.entity_description description = self.entity_description
prev_event = self._event prev_event = self._event

View File

@ -5,18 +5,13 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from uiprotect.data import ( from uiprotect.data import Light, ModelType, ProtectAdoptableDeviceModel
Light,
ModelType,
ProtectAdoptableDeviceModel,
ProtectModelWithId,
)
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import UFPConfigEntry from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -66,7 +61,7 @@ class ProtectLight(ProtectDeviceEntity, LightEntity):
_state_attrs = ("_attr_available", "_attr_is_on", "_attr_brightness") _state_attrs = ("_attr_available", "_attr_is_on", "_attr_brightness")
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
updated_device = self.device updated_device = self.device
self._attr_is_on = updated_device.is_light_on self._attr_is_on = updated_device.is_light_on

View File

@ -10,14 +10,13 @@ from uiprotect.data import (
LockStatusType, LockStatusType,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
) )
from homeassistant.components.lock import LockEntity, LockEntityDescription from homeassistant.components.lock import LockEntity, LockEntityDescription
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import UFPConfigEntry from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -60,7 +59,7 @@ class ProtectLock(ProtectDeviceEntity, LockEntity):
) )
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
lock_status = self.device.lock_status lock_status = self.device.lock_status

View File

@ -5,12 +5,7 @@ from __future__ import annotations
import logging import logging
from typing import Any from typing import Any
from uiprotect.data import ( from uiprotect.data import Camera, ProtectAdoptableDeviceModel, StateType
Camera,
ProtectAdoptableDeviceModel,
ProtectModelWithId,
StateType,
)
from uiprotect.exceptions import StreamError from uiprotect.exceptions import StreamError
from homeassistant.components import media_source from homeassistant.components import media_source
@ -28,7 +23,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import UFPConfigEntry from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity from .entity import ProtectDeviceEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -77,7 +72,7 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
_state_attrs = ("_attr_available", "_attr_state", "_attr_volume_level") _state_attrs = ("_attr_available", "_attr_state", "_attr_volume_level")
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
updated_device = self.device updated_device = self.device
self._attr_volume_level = float(updated_device.speaker_settings.volume / 100) self._attr_volume_level = float(updated_device.speaker_settings.volume / 100)

View File

@ -12,7 +12,6 @@ from uiprotect.data import (
Light, Light,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
) )
from homeassistant.components.number import NumberEntity, NumberEntityDescription from homeassistant.components.number import NumberEntity, NumberEntityDescription
@ -20,7 +19,7 @@ from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTime
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
@ -268,7 +267,7 @@ class ProtectNumbers(ProtectDeviceEntity, NumberEntity):
self._attr_native_step = self.entity_description.ufp_step self._attr_native_step = self.entity_description.ufp_step
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
self._attr_native_value = self.entity_description.get_ufp_value(self.device) self._attr_native_value = self.entity_description.get_ufp_value(self.device)

View File

@ -21,7 +21,6 @@ from uiprotect.data import (
ModelType, ModelType,
MountType, MountType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
RecordingMode, RecordingMode,
Sensor, Sensor,
Viewer, Viewer,
@ -33,7 +32,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import TYPE_EMPTY_VALUE from .const import TYPE_EMPTY_VALUE
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
from .utils import async_get_light_motion_current from .utils import async_get_light_motion_current
@ -371,7 +370,7 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
super().__init__(data, device, description) super().__init__(data, device, description)
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
entity_description = self.entity_description entity_description = self.entity_description
# entities with categories are not exposed for voice # entities with categories are not exposed for voice

View File

@ -16,7 +16,6 @@ from uiprotect.data import (
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectDeviceModel, ProtectDeviceModel,
ProtectModelWithId,
Sensor, Sensor,
SmartDetectObjectType, SmartDetectObjectType,
) )
@ -41,7 +40,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ( from .entity import (
BaseProtectEntity, BaseProtectEntity,
EventEntityMixin, EventEntityMixin,
@ -721,7 +720,7 @@ class BaseProtectSensor(BaseProtectEntity, SensorEntity):
entity_description: ProtectSensorEntityDescription entity_description: ProtectSensorEntityDescription
_state_attrs = ("_attr_available", "_attr_native_value") _state_attrs = ("_attr_available", "_attr_native_value")
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
self._attr_native_value = self.entity_description.get_ufp_value(self.device) self._attr_native_value = self.entity_description.get_ufp_value(self.device)
@ -756,7 +755,7 @@ class ProtectLicensePlateEventSensor(ProtectEventSensor):
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
description = self.entity_description description = self.entity_description
prev_event = self._event prev_event = self._event

View File

@ -5,14 +5,12 @@ from __future__ import annotations
from collections.abc import Sequence from collections.abc import Sequence
from dataclasses import dataclass from dataclasses import dataclass
from functools import partial from functools import partial
import logging
from typing import Any from typing import Any
from uiprotect.data import ( from uiprotect.data import (
Camera, Camera,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
RecordingMode, RecordingMode,
VideoMode, VideoMode,
) )
@ -23,16 +21,16 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from .data import ProtectData, UFPConfigEntry from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
from .entity import ( from .entity import (
BaseProtectEntity, BaseProtectEntity,
ProtectDeviceEntity, ProtectDeviceEntity,
ProtectIsOnEntity,
ProtectNVREntity, ProtectNVREntity,
async_all_device_entities, async_all_device_entities,
) )
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
_LOGGER = logging.getLogger(__name__)
ATTR_PREV_MIC = "prev_mic_level" ATTR_PREV_MIC = "prev_mic_level"
ATTR_PREV_RECORD = "prev_record_mode" ATTR_PREV_RECORD = "prev_record_mode"
@ -45,10 +43,7 @@ class ProtectSwitchEntityDescription(
async def _set_highfps(obj: Camera, value: bool) -> None: async def _set_highfps(obj: Camera, value: bool) -> None:
if value: await obj.set_video_mode(VideoMode.HIGH_FPS if value else VideoMode.DEFAULT)
await obj.set_video_mode(VideoMode.HIGH_FPS)
else:
await obj.set_video_mode(VideoMode.DEFAULT)
CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = ( CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
@ -472,15 +467,10 @@ _PRIVACY_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
} }
class ProtectBaseSwitch(BaseProtectEntity, SwitchEntity): class ProtectBaseSwitch(ProtectIsOnEntity):
"""Base class for UniFi Protect Switch.""" """Base class for UniFi Protect Switch."""
entity_description: ProtectSwitchEntityDescription entity_description: ProtectSwitchEntityDescription
_state_attrs = ("_attr_available", "_attr_is_on")
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
super()._async_update_device_from_protect(device)
self._attr_is_on = self.entity_description.get_ufp_value(self.device) is True
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on.""" """Turn the device on."""
@ -491,18 +481,23 @@ class ProtectBaseSwitch(BaseProtectEntity, SwitchEntity):
await self.entity_description.ufp_set(self.device, False) await self.entity_description.ufp_set(self.device, False)
class ProtectSwitch(ProtectBaseSwitch, ProtectDeviceEntity): class ProtectSwitch(ProtectDeviceEntity, ProtectBaseSwitch, SwitchEntity):
"""A UniFi Protect Switch.""" """A UniFi Protect Switch."""
entity_description: ProtectSwitchEntityDescription
class ProtectNVRSwitch(ProtectBaseSwitch, ProtectNVREntity):
class ProtectNVRSwitch(ProtectNVREntity, ProtectBaseSwitch, SwitchEntity):
"""A UniFi Protect NVR Switch.""" """A UniFi Protect NVR Switch."""
entity_description: ProtectSwitchEntityDescription
class ProtectPrivacyModeSwitch(RestoreEntity, ProtectSwitch): class ProtectPrivacyModeSwitch(RestoreEntity, ProtectSwitch):
"""A UniFi Protect Switch.""" """A UniFi Protect Switch."""
device: Camera device: Camera
entity_description: ProtectSwitchEntityDescription
def __init__( def __init__(
self, self,
@ -533,7 +528,7 @@ class ProtectPrivacyModeSwitch(RestoreEntity, ProtectSwitch):
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
# do not add extra state attribute on initialize # do not add extra state attribute on initialize
if self.entity_id: if self.entity_id:

View File

@ -10,7 +10,6 @@ from uiprotect.data import (
DoorbellMessageType, DoorbellMessageType,
ModelType, ModelType,
ProtectAdoptableDeviceModel, ProtectAdoptableDeviceModel,
ProtectModelWithId,
) )
from homeassistant.components.text import TextEntity, TextEntityDescription from homeassistant.components.text import TextEntity, TextEntityDescription
@ -18,7 +17,7 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .data import UFPConfigEntry from .data import ProtectDeviceType, UFPConfigEntry
from .entity import ProtectDeviceEntity, async_all_device_entities from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T from .models import PermRequired, ProtectEntityDescription, ProtectSetableKeysMixin, T
@ -89,7 +88,7 @@ class ProtectDeviceText(ProtectDeviceEntity, TextEntity):
_state_attrs = ("_attr_available", "_attr_native_value") _state_attrs = ("_attr_available", "_attr_native_value")
@callback @callback
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None: def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
super()._async_update_device_from_protect(device) super()._async_update_device_from_protect(device)
self._attr_native_value = self.entity_description.get_ufp_value(self.device) self._attr_native_value = self.entity_description.get_ufp_value(self.device)