diff --git a/homeassistant/components/duco/icons.json b/homeassistant/components/duco/icons.json index 67947d2f87398..909e5936a79e4 100644 --- a/homeassistant/components/duco/icons.json +++ b/homeassistant/components/duco/icons.json @@ -7,6 +7,12 @@ "iaq_rh": { "default": "mdi:water-percent" }, + "target_flow_level": { + "default": "mdi:gauge" + }, + "time_state_end": { + "default": "mdi:timer-outline" + }, "ventilation_state": { "default": "mdi:tune-variant" } diff --git a/homeassistant/components/duco/sensor.py b/homeassistant/components/duco/sensor.py index a08ba23ddcd34..825349a19ffee 100644 --- a/homeassistant/components/duco/sensor.py +++ b/homeassistant/components/duco/sensor.py @@ -4,6 +4,7 @@ from collections.abc import Callable from dataclasses import dataclass +from datetime import datetime import logging from duco.models import Node, NodeType, VentilationState @@ -24,6 +25,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.util import dt as dt_util from .const import DOMAIN from .coordinator import DucoConfigEntry, DucoCoordinator @@ -38,7 +40,7 @@ class DucoSensorEntityDescription(SensorEntityDescription): """Duco sensor entity description.""" - value_fn: Callable[[Node], int | float | str | None] + value_fn: Callable[[Node], datetime | int | float | str | None] node_types: tuple[NodeType, ...] @@ -68,6 +70,30 @@ class DucoBoxSensorEntityDescription(SensorEntityDescription): value_fn=lambda node: node.sensor.temp if node.sensor else None, node_types=(NodeType.UCCO2, NodeType.BSRH, NodeType.UCRH), ), + DucoSensorEntityDescription( + key="target_flow_level", + translation_key="target_flow_level", + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=PERCENTAGE, + suggested_display_precision=0, + value_fn=lambda node: ( + node.ventilation.flow_lvl_tgt if node.ventilation else None + ), + node_types=(NodeType.BOX,), + ), + DucoSensorEntityDescription( + key="time_state_end", + translation_key="time_state_end", + device_class=SensorDeviceClass.TIMESTAMP, + value_fn=lambda node: ( + dt_util.utc_from_timestamp(node.ventilation.time_state_end).replace( + second=0, microsecond=0 + ) + if node.ventilation and node.ventilation.time_state_end != 0 + else None + ), + node_types=(NodeType.BOX,), + ), DucoSensorEntityDescription( key="box_temperature", translation_key="box_temperature", @@ -210,7 +236,7 @@ def __init__( ) @property - def native_value(self) -> int | float | str | None: + def native_value(self) -> datetime | int | float | str | None: """Return the sensor value.""" return self.entity_description.value_fn(self._node) diff --git a/homeassistant/components/duco/strings.json b/homeassistant/components/duco/strings.json index da70af200e10e..d44cbef2d044b 100644 --- a/homeassistant/components/duco/strings.json +++ b/homeassistant/components/duco/strings.json @@ -56,6 +56,12 @@ "iaq_rh": { "name": "Humidity air quality index" }, + "target_flow_level": { + "name": "Target flow level" + }, + "time_state_end": { + "name": "Mode end time" + }, "ventilation_state": { "name": "Ventilation state", "state": { diff --git a/tests/components/duco/snapshots/test_sensor.ambr b/tests/components/duco/snapshots/test_sensor.ambr index 99816c86d8ce4..6b0201bc0afa6 100644 --- a/tests/components/duco/snapshots/test_sensor.ambr +++ b/tests/components/duco/snapshots/test_sensor.ambr @@ -391,6 +391,57 @@ 'state': '27.9', }) # --- +# name: test_sensor_entities_state[sensor.living_mode_end_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.living_mode_end_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Mode end time', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Mode end time', + 'platform': 'duco', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'time_state_end', + 'unique_id': 'aa:bb:cc:dd:ee:ff_1_time_state_end', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_entities_state[sensor.living_mode_end_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Living Mode end time', + }), + 'context': , + 'entity_id': 'sensor.living_mode_end_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_sensor_entities_state[sensor.living_signal_strength-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -446,6 +497,63 @@ 'state': '-60', }) # --- +# name: test_sensor_entities_state[sensor.living_target_flow_level-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.living_target_flow_level', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Target flow level', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Target flow level', + 'platform': 'duco', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'target_flow_level', + 'unique_id': 'aa:bb:cc:dd:ee:ff_1_target_flow_level', + 'unit_of_measurement': '%', + }) +# --- +# name: test_sensor_entities_state[sensor.living_target_flow_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Living Target flow level', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.living_target_flow_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- # name: test_sensor_entities_state[sensor.living_ventilation_state-entry] EntityRegistryEntrySnapshot({ 'aliases': list([