Improve Home Connect oven cavity temperature sensor (#139355)

* Improve oven cavity temperature translation

* Fetch cavity temperature unit

* Handle generic Home Connect error

* Improve test clarity
This commit is contained in:
J. Diego Rodríguez Royo 2025-02-26 18:11:40 +01:00 committed by GitHub
parent 2694828451
commit 5be7f49146
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 124 additions and 11 deletions

View File

@ -4,6 +4,8 @@ from typing import cast
from aiohomeconnect.model import EventKey, OptionKey, ProgramKey, SettingKey, StatusKey from aiohomeconnect.model import EventKey, OptionKey, ProgramKey, SettingKey, StatusKey
from homeassistant.const import UnitOfTemperature, UnitOfTime, UnitOfVolume
from .utils import bsh_key_to_translation_key from .utils import bsh_key_to_translation_key
DOMAIN = "home_connect" DOMAIN = "home_connect"
@ -21,6 +23,13 @@ APPLIANCES_WITH_PROGRAMS = (
"WasherDryer", "WasherDryer",
) )
UNIT_MAP = {
"seconds": UnitOfTime.SECONDS,
"ml": UnitOfVolume.MILLILITERS,
"°C": UnitOfTemperature.CELSIUS,
"°F": UnitOfTemperature.FAHRENHEIT,
}
BSH_POWER_ON = "BSH.Common.EnumType.PowerState.On" BSH_POWER_ON = "BSH.Common.EnumType.PowerState.On"
BSH_POWER_OFF = "BSH.Common.EnumType.PowerState.Off" BSH_POWER_OFF = "BSH.Common.EnumType.PowerState.Off"

View File

@ -11,7 +11,6 @@ from homeassistant.components.number import (
NumberEntity, NumberEntity,
NumberEntityDescription, NumberEntityDescription,
) )
from homeassistant.const import UnitOfTemperature, UnitOfTime, UnitOfVolume
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@ -23,6 +22,7 @@ from .const import (
SVE_TRANSLATION_PLACEHOLDER_ENTITY_ID, SVE_TRANSLATION_PLACEHOLDER_ENTITY_ID,
SVE_TRANSLATION_PLACEHOLDER_KEY, SVE_TRANSLATION_PLACEHOLDER_KEY,
SVE_TRANSLATION_PLACEHOLDER_VALUE, SVE_TRANSLATION_PLACEHOLDER_VALUE,
UNIT_MAP,
) )
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity, HomeConnectOptionEntity from .entity import HomeConnectEntity, HomeConnectOptionEntity
@ -32,13 +32,6 @@ _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
UNIT_MAP = {
"seconds": UnitOfTime.SECONDS,
"ml": UnitOfVolume.MILLILITERS,
"°C": UnitOfTemperature.CELSIUS,
"°F": UnitOfTemperature.FAHRENHEIT,
}
NUMBERS = ( NUMBERS = (
NumberEntityDescription( NumberEntityDescription(
key=SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_REFRIGERATOR, key=SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_REFRIGERATOR,

View File

@ -1,10 +1,12 @@
"""Provides a sensor for Home Connect.""" """Provides a sensor for Home Connect."""
import contextlib
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
from typing import cast from typing import cast
from aiohomeconnect.model import EventKey, StatusKey from aiohomeconnect.model import EventKey, StatusKey
from aiohomeconnect.model.error import HomeConnectError
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@ -23,6 +25,7 @@ from .const import (
BSH_OPERATION_STATE_FINISHED, BSH_OPERATION_STATE_FINISHED,
BSH_OPERATION_STATE_PAUSE, BSH_OPERATION_STATE_PAUSE,
BSH_OPERATION_STATE_RUN, BSH_OPERATION_STATE_RUN,
UNIT_MAP,
) )
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity from .entity import HomeConnectEntity
@ -40,6 +43,7 @@ class HomeConnectSensorEntityDescription(
default_value: str | None = None default_value: str | None = None
appliance_types: tuple[str, ...] | None = None appliance_types: tuple[str, ...] | None = None
fetch_unit: bool = False
BSH_PROGRAM_SENSORS = ( BSH_PROGRAM_SENSORS = (
@ -183,7 +187,8 @@ SENSORS = (
key=StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE, key=StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
translation_key="current_cavity_temperature", translation_key="oven_current_cavity_temperature",
fetch_unit=True,
), ),
) )
@ -318,6 +323,29 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
case _: case _:
self._attr_native_value = status self._attr_native_value = status
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
if self.entity_description.fetch_unit:
data = self.appliance.status[cast(StatusKey, self.bsh_key)]
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(
data.unit, data.unit
)
else:
await self.fetch_unit()
async def fetch_unit(self) -> None:
"""Fetch the unit of measurement."""
with contextlib.suppress(HomeConnectError):
data = await self.coordinator.client.get_status_value(
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
)
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(
data.unit, data.unit
)
class HomeConnectProgramSensor(HomeConnectSensor): class HomeConnectProgramSensor(HomeConnectSensor):
"""Sensor class for Home Connect sensors that reports information related to the running program.""" """Sensor class for Home Connect sensors that reports information related to the running program."""

View File

@ -1529,8 +1529,8 @@
"map3": "Map 3" "map3": "Map 3"
} }
}, },
"current_cavity_temperature": { "oven_current_cavity_temperature": {
"name": "Current cavity temperature" "name": "Current oven cavity temperature"
}, },
"freezer_door_alarm": { "freezer_door_alarm": {
"name": "Freezer door alarm", "name": "Freezer door alarm",

View File

@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, MagicMock
from aiohomeconnect.model import ( from aiohomeconnect.model import (
ArrayOfEvents, ArrayOfEvents,
ArrayOfStatus,
Event, Event,
EventKey, EventKey,
EventMessage, EventMessage,
@ -565,3 +566,85 @@ async def test_sensors_states(
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.is_state(entity_id, expected) assert hass.states.is_state(entity_id, expected)
@pytest.mark.parametrize(
(
"appliance_ha_id",
"entity_id",
"status_key",
"unit_get_status",
"unit_get_status_value",
"get_status_value_call_count",
),
[
(
"Oven",
"sensor.oven_current_oven_cavity_temperature",
StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
"°C",
None,
0,
),
(
"Oven",
"sensor.oven_current_oven_cavity_temperature",
StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
None,
"°C",
1,
),
],
indirect=["appliance_ha_id"],
)
async def test_sensor_unit_fetching(
appliance_ha_id: str,
entity_id: str,
status_key: StatusKey,
unit_get_status: str | None,
unit_get_status_value: str | None,
get_status_value_call_count: int,
hass: HomeAssistant,
config_entry: MockConfigEntry,
integration_setup: Callable[[MagicMock], Awaitable[bool]],
setup_credentials: None,
client: MagicMock,
) -> None:
"""Test that the sensor entities are capable of fetching units."""
async def get_status_mock(ha_id: str) -> ArrayOfStatus:
if ha_id != appliance_ha_id:
return ArrayOfStatus([])
return ArrayOfStatus(
[
Status(
key=status_key,
raw_key=status_key.value,
value=0,
unit=unit_get_status,
)
]
)
client.get_status = AsyncMock(side_effect=get_status_mock)
client.get_status_value = AsyncMock(
return_value=Status(
key=status_key,
raw_key=status_key.value,
value=0,
unit=unit_get_status_value,
)
)
assert config_entry.state == ConfigEntryState.NOT_LOADED
assert await integration_setup(client)
assert config_entry.state == ConfigEntryState.LOADED
entity_state = hass.states.get(entity_id)
assert entity_state
assert (
entity_state.attributes["unit_of_measurement"] == unit_get_status
or unit_get_status_value
)
assert client.get_status_value.call_count == get_status_value_call_count