Add UniFi Protect sensor platform (#63524)

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
Christopher Bailey 2022-01-05 20:47:47 -05:00 committed by GitHub
parent da71e89f53
commit 7d442122c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 728 additions and 0 deletions

View file

@ -61,6 +61,7 @@ PLATFORMS = [
Platform.MEDIA_PLAYER, Platform.MEDIA_PLAYER,
Platform.NUMBER, Platform.NUMBER,
Platform.SELECT, Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
] ]

View file

@ -0,0 +1,444 @@
"""This component provides sensors for UniFi Protect."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
from typing import Any
from pyunifiprotect.data import NVR
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
DATA_BYTES,
DATA_RATE_BYTES_PER_SECOND,
DATA_RATE_MEGABITS_PER_SECOND,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_TIMESTAMP,
DEVICE_CLASS_VOLTAGE,
ELECTRIC_POTENTIAL_VOLT,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
TEMP_CELSIUS,
TIME_SECONDS,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import Entity, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .data import ProtectData
from .entity import ProtectDeviceEntity, ProtectNVREntity, async_all_device_entities
from .models import ProtectRequiredKeysMixin
from .utils import get_nested_attr
_LOGGER = logging.getLogger(__name__)
@dataclass
class ProtectSensorEntityDescription(ProtectRequiredKeysMixin, SensorEntityDescription):
"""Describes UniFi Protect Sensor entity."""
precision: int | None = None
_KEY_UPTIME = "uptime"
_KEY_BLE = "ble_signal"
_KEY_WIRED = "phy_rate"
_KEY_WIFI = "wifi_signal"
_KEY_RX = "stats_rx"
_KEY_TX = "stats_tx"
_KEY_OLDEST = "oldest_recording"
_KEY_USED = "storage_used"
_KEY_WRITE_RATE = "write_rate"
_KEY_VOLTAGE = "voltage"
_KEY_BATTERY = "battery_level"
_KEY_LIGHT = "light_level"
_KEY_HUMIDITY = "humidity_level"
_KEY_TEMP = "temperature_level"
_KEY_CPU = "cpu_utilization"
_KEY_CPU_TEMP = "cpu_temperature"
_KEY_MEMORY = "memory_utilization"
_KEY_DISK = "storage_utilization"
_KEY_RECORD_ROTATE = "record_rotating"
_KEY_RECORD_TIMELAPSE = "record_timelapse"
_KEY_RECORD_DETECTIONS = "record_detections"
_KEY_RES_HD = "resolution_HD"
_KEY_RES_4K = "resolution_4K"
_KEY_RES_FREE = "resolution_free"
_KEY_CAPACITY = "record_capacity"
ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key=_KEY_UPTIME,
name="Uptime",
icon="mdi:clock",
device_class=DEVICE_CLASS_TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
ufp_value="up_since",
),
ProtectSensorEntityDescription(
key=_KEY_BLE,
name="Bluetooth Signal Strength",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="bluetooth_connection_state.signal_strength",
ufp_required_field="bluetooth_connection_state.signal_strength",
),
ProtectSensorEntityDescription(
key=_KEY_WIRED,
name="Link Speed",
native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="wired_connection_state.phy_rate",
ufp_required_field="wired_connection_state.phy_rate",
),
ProtectSensorEntityDescription(
key=_KEY_WIFI,
name="WiFi Signal Strength",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="wifi_connection_state.signal_strength",
ufp_required_field="wifi_connection_state.signal_strength",
),
)
CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key=_KEY_RX,
name="Received Data",
native_unit_of_measurement=DATA_BYTES,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
ufp_value="stats.rx_bytes",
),
ProtectSensorEntityDescription(
key=_KEY_TX,
name="Transferred Data",
native_unit_of_measurement=DATA_BYTES,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.TOTAL_INCREASING,
ufp_value="stats.tx_bytes",
),
ProtectSensorEntityDescription(
key=_KEY_OLDEST,
name="Oldest Recording",
device_class=DEVICE_CLASS_TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="stats.video.recording_start",
),
ProtectSensorEntityDescription(
key=_KEY_USED,
name="Storage Used",
native_unit_of_measurement=DATA_BYTES,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="stats.storage.used",
),
ProtectSensorEntityDescription(
key=_KEY_WRITE_RATE,
name="Disk Write Rate",
native_unit_of_measurement=DATA_RATE_BYTES_PER_SECOND,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="stats.storage.rate",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_VOLTAGE,
name="Voltage",
device_class=DEVICE_CLASS_VOLTAGE,
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="voltage",
# no feature flag, but voltage will be null if device does not have voltage sensor
# (i.e. is not G4 Doorbell or not on 1.20.1+)
ufp_required_field="voltage",
precision=2,
),
)
SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key=_KEY_BATTERY,
name="Battery Level",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="battery_status.percentage",
),
ProtectSensorEntityDescription(
key=_KEY_LIGHT,
name="Light Level",
native_unit_of_measurement=LIGHT_LUX,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="stats.light.value",
),
ProtectSensorEntityDescription(
key=_KEY_HUMIDITY,
name="Humidity Level",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="stats.humidity.value",
),
ProtectSensorEntityDescription(
key=_KEY_TEMP,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="stats.temperature.value",
),
)
NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key=_KEY_UPTIME,
name="Uptime",
icon="mdi:clock",
device_class=DEVICE_CLASS_TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="up_since",
),
ProtectSensorEntityDescription(
key=_KEY_CPU,
name="CPU Utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:speedometer",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="system_info.cpu.average_load",
),
ProtectSensorEntityDescription(
key=_KEY_CPU_TEMP,
name="CPU Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="system_info.cpu.temperature",
),
ProtectSensorEntityDescription(
key=_KEY_MEMORY,
name="Memory Utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:memory",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_DISK,
name="Storage Utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:harddisk",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.utilization",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RECORD_TIMELAPSE,
name="Type: Timelapse Video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RECORD_ROTATE,
name="Type: Continuous Video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RECORD_DETECTIONS,
name="Type: Detections Video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RES_HD,
name="Resolution: HD Video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RES_4K,
name="Resolution: 4K Video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_RES_FREE,
name="Resolution: Free Space",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.free.percentage",
precision=2,
),
ProtectSensorEntityDescription(
key=_KEY_CAPACITY,
name="Recording Capacity",
native_unit_of_measurement=TIME_SECONDS,
icon="mdi:record-rec",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.capacity",
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensors for UniFi Protect integration."""
data: ProtectData = hass.data[DOMAIN][entry.entry_id]
entities: list[ProtectDeviceEntity] = async_all_device_entities(
data,
ProtectDeviceSensor,
all_descs=ALL_DEVICES_SENSORS,
camera_descs=CAMERA_SENSORS,
sense_descs=SENSE_SENSORS,
)
entities += _async_nvr_entities(data)
async_add_entities(entities)
@callback
def _async_nvr_entities(
data: ProtectData,
) -> list[ProtectDeviceEntity]:
entities: list[ProtectDeviceEntity] = []
device = data.api.bootstrap.nvr
for description in NVR_SENSORS:
entities.append(ProtectNVRSensor(data, device, description))
_LOGGER.debug("Adding NVR sensor entity %s", description.name)
return entities
class SensorValueMixin(Entity):
"""A mixin to provide sensor values."""
@callback
def _clean_sensor_value(self, value: Any) -> Any:
if isinstance(value, timedelta):
value = int(value.total_seconds())
elif isinstance(value, datetime):
# UniFi Protect value can vary slightly over time
# truncate to ensure no extra state_change events fire
value = value.replace(second=0, microsecond=0)
assert isinstance(self.entity_description, ProtectSensorEntityDescription)
if isinstance(value, float) and self.entity_description.precision:
value = round(value, self.entity_description.precision)
return value
class ProtectDeviceSensor(SensorValueMixin, ProtectDeviceEntity, SensorEntity):
"""A Ubiquiti UniFi Protect Sensor."""
def __init__(
self,
data: ProtectData,
device: ProtectAdoptableDeviceModel,
description: ProtectSensorEntityDescription,
) -> None:
"""Initialize an UniFi Protect sensor."""
self.entity_description: ProtectSensorEntityDescription = description
super().__init__(data, device)
@callback
def _async_update_device_from_protect(self) -> None:
super()._async_update_device_from_protect()
assert self.entity_description.ufp_value is not None
value = get_nested_attr(self.device, self.entity_description.ufp_value)
self._attr_native_value = self._clean_sensor_value(value)
class ProtectNVRSensor(SensorValueMixin, ProtectNVREntity, SensorEntity):
"""A Ubiquiti UniFi Protect Sensor."""
entity_description: ProtectSensorEntityDescription
def __init__(
self,
data: ProtectData,
device: NVR,
description: ProtectSensorEntityDescription,
) -> None:
"""Initialize an UniFi Protect sensor."""
self.entity_description = description
super().__init__(data, device)
@callback
def _async_update_device_from_protect(self) -> None:
super()._async_update_device_from_protect()
# _KEY_MEMORY
if self.entity_description.ufp_value is None:
memory = self.device.system_info.memory
value = (1 - memory.available / memory.total) * 100
else:
value = get_nested_attr(self.device, self.entity_description.ufp_value)
self._attr_native_value = self._clean_sensor_value(value)

View file

@ -0,0 +1,283 @@
"""Test the UniFi Protect sensor platform."""
# pylint: disable=protected-access
from __future__ import annotations
from datetime import datetime
import pytest
from pyunifiprotect.data.base import WifiConnectionState, WiredConnectionState
from pyunifiprotect.data.devices import Camera, Sensor
from pyunifiprotect.data.nvr import NVR
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
from homeassistant.components.unifiprotect.sensor import (
ALL_DEVICES_SENSORS,
CAMERA_SENSORS,
NVR_SENSORS,
SENSE_SENSORS,
)
from homeassistant.const import ATTR_ATTRIBUTION, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import (
MockEntityFixture,
assert_entity_counts,
enable_entity,
ids_from_device_description,
)
@pytest.fixture(name="sensor")
async def sensor_fixture(
hass: HomeAssistant,
mock_entry: MockEntityFixture,
mock_sensor: Sensor,
now: datetime,
):
"""Fixture for a single sensor for testing the sensor platform."""
# disable pydantic validation so mocking can happen
Sensor.__config__.validate_assignment = False
sensor_obj = mock_sensor.copy(deep=True)
sensor_obj._api = mock_entry.api
sensor_obj.name = "Test Sensor"
sensor_obj.battery_status.percentage = 10.0
sensor_obj.stats.light.value = 10.0
sensor_obj.stats.humidity.value = 10.0
sensor_obj.stats.temperature.value = 10.0
sensor_obj.up_since = now
sensor_obj.bluetooth_connection_state.signal_strength = -50.0
mock_entry.api.bootstrap.reset_objects()
mock_entry.api.bootstrap.sensors = {
sensor_obj.id: sensor_obj,
}
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()
# 2 from all, 4 from sense, 12 NVR
assert_entity_counts(hass, Platform.SENSOR, 18, 13)
yield sensor_obj
Sensor.__config__.validate_assignment = True
@pytest.fixture(name="camera")
async def camera_fixture(
hass: HomeAssistant,
mock_entry: MockEntityFixture,
mock_camera: Camera,
now: datetime,
):
"""Fixture for a single camera for testing the sensor platform."""
# disable pydantic validation so mocking can happen
Camera.__config__.validate_assignment = False
camera_obj = mock_camera.copy(deep=True)
camera_obj._api = mock_entry.api
camera_obj.channels[0]._api = mock_entry.api
camera_obj.channels[1]._api = mock_entry.api
camera_obj.channels[2]._api = mock_entry.api
camera_obj.name = "Test Camera"
camera_obj.wired_connection_state = WiredConnectionState(phy_rate=1000)
camera_obj.wifi_connection_state = WifiConnectionState(
signal_quality=100, signal_strength=-50
)
camera_obj.stats.rx_bytes = 100.0
camera_obj.stats.tx_bytes = 100.0
camera_obj.stats.video.recording_start = now
camera_obj.stats.storage.used = 100.0
camera_obj.stats.storage.used = 100.0
camera_obj.stats.storage.rate = 100.0
camera_obj.voltage = 20.0
mock_entry.api.bootstrap.reset_objects()
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
mock_entry.api.bootstrap.cameras = {
camera_obj.id: camera_obj,
}
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()
# 3 from all, 6 from camera, 12 NVR
assert_entity_counts(hass, Platform.SENSOR, 21, 13)
yield camera_obj
Camera.__config__.validate_assignment = True
async def test_sensor_setup_sensor(
hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor
):
"""Test sensor entity setup for sensor devices."""
entity_registry = er.async_get(hass)
expected_values = ("10", "10.0", "10.0", "10.0")
for index, description in enumerate(SENSE_SENSORS):
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, sensor, description
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.unique_id == unique_id
state = hass.states.get(entity_id)
assert state
assert state.state == expected_values[index]
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
# BLE signal
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, sensor, ALL_DEVICES_SENSORS[1]
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is True
assert entity.unique_id == unique_id
await enable_entity(hass, mock_entry.entry.entry_id, entity_id)
state = hass.states.get(entity_id)
assert state
assert state.state == "-50"
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
async def test_sensor_setup_nvr(
hass: HomeAssistant, mock_entry: MockEntityFixture, now: datetime
):
"""Test sensor entity setup for NVR device."""
mock_entry.api.bootstrap.reset_objects()
nvr: NVR = mock_entry.api.bootstrap.nvr
nvr.up_since = now
nvr.system_info.cpu.average_load = 50.0
nvr.system_info.cpu.temperature = 50.0
nvr.storage_stats.utilization = 50.0
nvr.system_info.memory.available = 50.0
nvr.system_info.memory.total = 100.0
nvr.storage_stats.storage_distribution.timelapse_recordings.percentage = 50.0
nvr.storage_stats.storage_distribution.continuous_recordings.percentage = 50.0
nvr.storage_stats.storage_distribution.detections_recordings.percentage = 50.0
nvr.storage_stats.storage_distribution.hd_usage.percentage = 50.0
nvr.storage_stats.storage_distribution.uhd_usage.percentage = 50.0
nvr.storage_stats.storage_distribution.free.percentage = 50.0
nvr.storage_stats.capacity = 50.0
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
await hass.async_block_till_done()
# 2 from all, 4 from sense, 12 NVR
assert_entity_counts(hass, Platform.SENSOR, 12, 9)
entity_registry = er.async_get(hass)
expected_values = (
now.replace(second=0, microsecond=0).isoformat(),
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50.0",
"50",
)
for index, description in enumerate(NVR_SENSORS):
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, nvr, description
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is not description.entity_registry_enabled_default
assert entity.unique_id == unique_id
if not description.entity_registry_enabled_default:
await enable_entity(hass, mock_entry.entry.entry_id, entity_id)
state = hass.states.get(entity_id)
assert state
assert state.state == expected_values[index]
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
async def test_sensor_setup_camera(
hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime
):
"""Test sensor entity setup for camera devices."""
entity_registry = er.async_get(hass)
expected_values = (
"100",
"100",
now.replace(second=0, microsecond=0).isoformat(),
"100",
"100.0",
"20.0",
)
for index, description in enumerate(CAMERA_SENSORS):
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, camera, description
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is not description.entity_registry_enabled_default
assert entity.unique_id == unique_id
if not description.entity_registry_enabled_default:
await enable_entity(hass, mock_entry.entry.entry_id, entity_id)
state = hass.states.get(entity_id)
assert state
assert state.state == expected_values[index]
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
# Wired signal
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, camera, ALL_DEVICES_SENSORS[2]
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is True
assert entity.unique_id == unique_id
await enable_entity(hass, mock_entry.entry.entry_id, entity_id)
state = hass.states.get(entity_id)
assert state
assert state.state == "1000"
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
# WiFi signal
unique_id, entity_id = ids_from_device_description(
Platform.SENSOR, camera, ALL_DEVICES_SENSORS[3]
)
entity = entity_registry.async_get(entity_id)
assert entity
assert entity.disabled is True
assert entity.unique_id == unique_id
await enable_entity(hass, mock_entry.entry.entry_id, entity_id)
state = hass.states.get(entity_id)
assert state
assert state.state == "-50"
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION