Add sensor platform for tedee integration (#106722)

* add sensors

* requested changes

* remove translation key from battery

* fix pullspring test

* loop instead of parametrize

* name snapshots

* fix snapshots
This commit is contained in:
Josef Zweck 2023-12-31 12:24:44 +01:00 committed by GitHub
parent f7154cff9d
commit 0549c9e113
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 233 additions and 0 deletions

View File

@ -10,6 +10,7 @@ from .coordinator import TedeeApiCoordinator
PLATFORMS = [
Platform.LOCK,
Platform.SENSOR,
]
_LOGGER = logging.getLogger(__name__)

View File

@ -4,6 +4,7 @@ from pytedee_async.lock import TedeeLock
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@ -38,3 +39,19 @@ class TedeeEntity(CoordinatorEntity[TedeeApiCoordinator]):
"""Handle updated data from the coordinator."""
self._lock = self.coordinator.data[self._lock.lock_id]
super()._handle_coordinator_update()
class TedeeDescriptionEntity(TedeeEntity):
"""Base class for Tedee device entities."""
entity_description: EntityDescription
def __init__(
self,
lock: TedeeLock,
coordinator: TedeeApiCoordinator,
entity_description: EntityDescription,
) -> None:
"""Initialize Tedee device entity."""
super().__init__(lock, coordinator, entity_description.key)
self.entity_description = entity_description

View File

@ -0,0 +1,74 @@
"""Tedee sensor entities."""
from collections.abc import Callable
from dataclasses import dataclass
from pytedee_async import TedeeLock
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .entity import TedeeDescriptionEntity
@dataclass(frozen=True, kw_only=True)
class TedeeSensorEntityDescription(SensorEntityDescription):
"""Describes Tedee sensor entity."""
value_fn: Callable[[TedeeLock], float | None]
ENTITIES: tuple[TedeeSensorEntityDescription, ...] = (
TedeeSensorEntityDescription(
key="battery_sensor",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda lock: lock.battery_level,
),
TedeeSensorEntityDescription(
key="pullspring_duration",
translation_key="pullspring_duration",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.SECONDS,
state_class=SensorStateClass.TOTAL,
icon="mdi:timer-lock-open",
value_fn=lambda lock: lock.duration_pullspring,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Tedee sensor entity."""
coordinator = hass.data[DOMAIN][entry.entry_id]
for entity_description in ENTITIES:
async_add_entities(
[
TedeeSensorEntity(lock, coordinator, entity_description)
for lock in coordinator.data.values()
]
)
class TedeeSensorEntity(TedeeDescriptionEntity, SensorEntity):
"""Tedee sensor entity."""
entity_description: TedeeSensorEntityDescription
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self._lock)

View File

@ -21,5 +21,12 @@
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"entity": {
"sensor": {
"pullspring_duration": {
"name": "Pullspring duration"
}
}
}
}

View File

@ -0,0 +1,98 @@
# serializer version: 1
# name: test_sensors[entry-battery]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.lock_1a2b_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery',
'platform': 'tedee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345-battery_sensor',
'unit_of_measurement': '%',
})
# ---
# name: test_sensors[entry-pullspring_duration]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.lock_1a2b_pullspring_duration',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'original_icon': 'mdi:timer-lock-open',
'original_name': 'Pullspring duration',
'platform': 'tedee',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'pullspring_duration',
'unique_id': '12345-pullspring_duration',
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
})
# ---
# name: test_sensors[state-battery]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'Lock-1A2B Battery',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.lock_1a2b_battery',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '70',
})
# ---
# name: test_sensors[state-pullspring_duration]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'Lock-1A2B Pullspring duration',
'icon': 'mdi:timer-lock-open',
'state_class': <SensorStateClass.TOTAL: 'total'>,
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
}),
'context': <ANY>,
'entity_id': 'sensor.lock_1a2b_pullspring_duration',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '2',
})
# ---

View File

@ -0,0 +1,36 @@
"""Tests for the Tedee Sensors."""
from unittest.mock import MagicMock
import pytest
from syrupy import SnapshotAssertion
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
pytestmark = pytest.mark.usefixtures("init_integration")
SENSORS = (
"battery",
"pullspring_duration",
)
async def test_sensors(
hass: HomeAssistant,
mock_tedee: MagicMock,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test tedee sensors."""
for key in SENSORS:
state = hass.states.get(f"sensor.lock_1a2b_{key}")
assert state
assert state == snapshot(name=f"state-{key}")
entry = entity_registry.async_get(state.entity_id)
assert entry
assert entry.device_id
assert entry == snapshot(name=f"entry-{key}")