mirror of
https://github.com/home-assistant/core.git
synced 2025-10-20 09:09:29 +00:00
994 lines
35 KiB
Python
994 lines
35 KiB
Python
"""Sensor platform for Miele integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable, Mapping
|
|
from dataclasses import dataclass
|
|
import logging
|
|
from typing import Any, Final, cast
|
|
|
|
from pymiele import MieleDevice, MieleTemperature
|
|
|
|
from homeassistant.components.sensor import (
|
|
RestoreSensor,
|
|
SensorDeviceClass,
|
|
SensorEntity,
|
|
SensorEntityDescription,
|
|
SensorStateClass,
|
|
)
|
|
from homeassistant.const import (
|
|
PERCENTAGE,
|
|
REVOLUTIONS_PER_MINUTE,
|
|
STATE_UNKNOWN,
|
|
EntityCategory,
|
|
UnitOfEnergy,
|
|
UnitOfTemperature,
|
|
UnitOfTime,
|
|
UnitOfVolume,
|
|
)
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers import entity_registry as er
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
from homeassistant.helpers.typing import StateType
|
|
|
|
from .const import (
|
|
COFFEE_SYSTEM_PROFILE,
|
|
DISABLED_TEMP_ENTITIES,
|
|
DOMAIN,
|
|
PROGRAM_PHASE,
|
|
STATE_PROGRAM_ID,
|
|
STATE_STATUS_TAGS,
|
|
MieleAppliance,
|
|
PlatePowerStep,
|
|
StateDryingStep,
|
|
StateProgramType,
|
|
StateStatus,
|
|
)
|
|
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
|
|
from .entity import MieleEntity
|
|
|
|
PARALLEL_UPDATES = 0
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DEFAULT_PLATE_COUNT = 4
|
|
|
|
PLATE_COUNT = {
|
|
"KM7575": 6,
|
|
"KM7678": 6,
|
|
"KM7697": 6,
|
|
"KM7878": 6,
|
|
"KM7897": 6,
|
|
"KMDA7633": 5,
|
|
"KMDA7634": 5,
|
|
"KMDA7774": 5,
|
|
"KMX": 6,
|
|
}
|
|
|
|
ATTRIBUTE_PROFILE = "profile"
|
|
|
|
|
|
def _get_plate_count(tech_type: str) -> int:
|
|
"""Get number of zones for hob."""
|
|
stripped = tech_type.replace(" ", "")
|
|
for prefix, plates in PLATE_COUNT.items():
|
|
if stripped.startswith(prefix):
|
|
return plates
|
|
return DEFAULT_PLATE_COUNT
|
|
|
|
|
|
def _convert_duration(value_list: list[int]) -> int | None:
|
|
"""Convert duration to minutes."""
|
|
return value_list[0] * 60 + value_list[1] if value_list else None
|
|
|
|
|
|
def _convert_temperature(
|
|
value_list: list[MieleTemperature], index: int
|
|
) -> float | None:
|
|
"""Convert temperature object to readable value."""
|
|
if index >= len(value_list):
|
|
return None
|
|
raw_value = cast(int, value_list[index].temperature) / 100.0
|
|
if raw_value in DISABLED_TEMP_ENTITIES:
|
|
return None
|
|
return raw_value
|
|
|
|
|
|
def _get_coffee_profile(value: MieleDevice) -> str | None:
|
|
"""Get coffee profile from value."""
|
|
if value.state_program_id is not None:
|
|
for key_range, profile in COFFEE_SYSTEM_PROFILE.items():
|
|
if value.state_program_id in key_range:
|
|
return profile
|
|
return None
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class MieleSensorDescription(SensorEntityDescription):
|
|
"""Class describing Miele sensor entities."""
|
|
|
|
value_fn: Callable[[MieleDevice], StateType]
|
|
end_value_fn: Callable[[StateType], StateType] | None = None
|
|
extra_attributes: dict[str, Callable[[MieleDevice], StateType]] | None = None
|
|
zone: int | None = None
|
|
unique_id_fn: Callable[[str, MieleSensorDescription], str] | None = None
|
|
|
|
|
|
@dataclass
|
|
class MieleSensorDefinition:
|
|
"""Class for defining sensor entities."""
|
|
|
|
types: tuple[MieleAppliance, ...]
|
|
description: MieleSensorDescription
|
|
|
|
|
|
SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.HOB_HIGHLIGHT,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.COFFEE_SYSTEM,
|
|
MieleAppliance.HOOD,
|
|
MieleAppliance.FRIDGE,
|
|
MieleAppliance.FREEZER,
|
|
MieleAppliance.FRIDGE_FREEZER,
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.HOB_INDUCTION,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.WINE_CABINET,
|
|
MieleAppliance.WINE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.WINE_CABINET_FREEZER,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
MieleAppliance.HOB_INDUCT_EXTR,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_status",
|
|
translation_key="status",
|
|
value_fn=lambda value: value.state_status,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=sorted(set(STATE_STATUS_TAGS.values())),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_program_id",
|
|
translation_key="program_id",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
value_fn=lambda value: value.state_program_id,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(MieleAppliance.COFFEE_SYSTEM,),
|
|
description=MieleSensorDescription(
|
|
key="state_program_id",
|
|
translation_key="program_id",
|
|
device_class=SensorDeviceClass.ENUM,
|
|
value_fn=lambda value: value.state_program_id,
|
|
extra_attributes={
|
|
ATTRIBUTE_PROFILE: _get_coffee_profile,
|
|
},
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.COFFEE_SYSTEM,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_program_phase",
|
|
translation_key="program_phase",
|
|
value_fn=lambda value: value.state_program_phase,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.COFFEE_SYSTEM,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_program_type",
|
|
translation_key="program_type",
|
|
value_fn=lambda value: StateProgramType(value.state_program_type).name,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=sorted(set(StateProgramType.keys())),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="current_energy_consumption",
|
|
translation_key="energy_consumption",
|
|
value_fn=lambda value: value.current_energy_consumption,
|
|
device_class=SensorDeviceClass.ENERGY,
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
|
suggested_display_precision=1,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="energy_forecast",
|
|
translation_key="energy_forecast",
|
|
value_fn=(
|
|
lambda value: value.energy_forecast * 100
|
|
if value.energy_forecast is not None
|
|
else None
|
|
),
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="current_water_consumption",
|
|
translation_key="water_consumption",
|
|
value_fn=lambda value: value.current_water_consumption,
|
|
device_class=SensorDeviceClass.WATER,
|
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
|
native_unit_of_measurement=UnitOfVolume.LITERS,
|
|
suggested_display_precision=0,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="water_forecast",
|
|
translation_key="water_forecast",
|
|
value_fn=(
|
|
lambda value: value.water_forecast * 100
|
|
if value.water_forecast is not None
|
|
else None
|
|
),
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.WASHER_DRYER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_spinning_speed",
|
|
translation_key="spin_speed",
|
|
value_fn=lambda value: value.state_spinning_speed,
|
|
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_remaining_time",
|
|
translation_key="remaining_time",
|
|
value_fn=lambda value: _convert_duration(value.state_remaining_time),
|
|
end_value_fn=lambda last_value: 0,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_elapsed_time",
|
|
translation_key="elapsed_time",
|
|
value_fn=lambda value: _convert_duration(value.state_elapsed_time),
|
|
end_value_fn=lambda last_value: last_value,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHING_MACHINE_SEMI_PROFESSIONAL,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.DISHWASHER,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_start_time",
|
|
translation_key="start_time",
|
|
value_fn=lambda value: _convert_duration(value.state_start_time),
|
|
end_value_fn=lambda last_value: None,
|
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
|
device_class=SensorDeviceClass.DURATION,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
suggested_display_precision=2,
|
|
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.DISH_WARMER,
|
|
MieleAppliance.STEAM_OVEN,
|
|
MieleAppliance.MICROWAVE,
|
|
MieleAppliance.FRIDGE,
|
|
MieleAppliance.FREEZER,
|
|
MieleAppliance.FRIDGE_FREEZER,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.WINE_CABINET,
|
|
MieleAppliance.WINE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.DIALOG_OVEN,
|
|
MieleAppliance.WINE_CABINET_FREEZER,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_temperature_1",
|
|
zone=1,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(value.state_temperatures, 0),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.FRIDGE_FREEZER,
|
|
MieleAppliance.WINE_CABINET,
|
|
MieleAppliance.WINE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_CABINET_FREEZER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_temperature_2",
|
|
zone=2,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
translation_key="temperature_zone_2",
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(value.state_temperatures, 1),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WINE_CABINET,
|
|
MieleAppliance.WINE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT,
|
|
MieleAppliance.WINE_CABINET_FREEZER,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_temperature_3",
|
|
zone=3,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
translation_key="temperature_zone_3",
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(value.state_temperatures, 2),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_core_target_temperature",
|
|
translation_key="core_target_temperature",
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(
|
|
value.state_core_target_temperature, 0
|
|
),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHING_MACHINE,
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN_MICRO,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
MieleAppliance.STEAM_OVEN_MK2,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_target_temperature",
|
|
translation_key="target_temperature",
|
|
zone=1,
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(
|
|
value.state_target_temperature, 0
|
|
),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.OVEN,
|
|
MieleAppliance.OVEN_MICROWAVE,
|
|
MieleAppliance.STEAM_OVEN_COMBI,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_core_temperature",
|
|
translation_key="core_temperature",
|
|
device_class=SensorDeviceClass.TEMPERATURE,
|
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
|
state_class=SensorStateClass.MEASUREMENT,
|
|
value_fn=lambda value: _convert_temperature(
|
|
value.state_core_temperature, 0
|
|
),
|
|
),
|
|
),
|
|
*(
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.HOB_HIGHLIGHT,
|
|
MieleAppliance.HOB_INDUCT_EXTR,
|
|
MieleAppliance.HOB_INDUCTION,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_plate_step",
|
|
translation_key="plate",
|
|
translation_placeholders={"plate_no": str(i)},
|
|
zone=i,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=sorted(PlatePowerStep.keys()),
|
|
value_fn=lambda value: None,
|
|
unique_id_fn=lambda device_id,
|
|
description: f"{device_id}-{description.key}-{description.zone}",
|
|
),
|
|
)
|
|
for i in range(1, 7)
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(
|
|
MieleAppliance.WASHER_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER,
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL,
|
|
),
|
|
description=MieleSensorDescription(
|
|
key="state_drying_step",
|
|
translation_key="drying_step",
|
|
value_fn=lambda value: StateDryingStep(
|
|
cast(int, value.state_drying_step)
|
|
).name,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.ENUM,
|
|
options=sorted(StateDryingStep.keys()),
|
|
),
|
|
),
|
|
MieleSensorDefinition(
|
|
types=(MieleAppliance.ROBOT_VACUUM_CLEANER,),
|
|
description=MieleSensorDescription(
|
|
key="state_battery",
|
|
value_fn=lambda value: value.state_battery_level,
|
|
native_unit_of_measurement=PERCENTAGE,
|
|
entity_category=EntityCategory.DIAGNOSTIC,
|
|
device_class=SensorDeviceClass.BATTERY,
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
config_entry: MieleConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Set up the sensor platform."""
|
|
coordinator = config_entry.runtime_data
|
|
added_devices: set[str] = set() # device_id
|
|
added_entities: set[str] = set() # unique_id
|
|
|
|
def _get_entity_class(definition: MieleSensorDefinition) -> type[MieleSensor]:
|
|
"""Get the entity class for the sensor."""
|
|
return {
|
|
"state_status": MieleStatusSensor,
|
|
"state_program_id": MieleProgramIdSensor,
|
|
"state_program_phase": MielePhaseSensor,
|
|
"state_plate_step": MielePlateSensor,
|
|
"state_elapsed_time": MieleTimeSensor,
|
|
"state_remaining_time": MieleTimeSensor,
|
|
"state_start_time": MieleTimeSensor,
|
|
"current_energy_consumption": MieleConsumptionSensor,
|
|
"current_water_consumption": MieleConsumptionSensor,
|
|
}.get(definition.description.key, MieleSensor)
|
|
|
|
def _is_entity_registered(unique_id: str) -> bool:
|
|
"""Check if the entity is already registered."""
|
|
entity_registry = er.async_get(hass)
|
|
return any(
|
|
entry.platform == DOMAIN and entry.unique_id == unique_id
|
|
for entry in entity_registry.entities.values()
|
|
)
|
|
|
|
def _is_sensor_enabled(
|
|
definition: MieleSensorDefinition,
|
|
device: MieleDevice,
|
|
unique_id: str,
|
|
) -> bool:
|
|
"""Check if the sensor is enabled."""
|
|
if (
|
|
definition.description.device_class == SensorDeviceClass.TEMPERATURE
|
|
and definition.description.value_fn(device) is None
|
|
and definition.description.zone != 1
|
|
):
|
|
# all appliances supporting temperature have at least zone 1, for other zones
|
|
# don't create entity if API signals that datapoint is disabled, unless the sensor
|
|
# already appeared in the past (= it provided a valid value)
|
|
return _is_entity_registered(unique_id)
|
|
if (
|
|
definition.description.key == "state_plate_step"
|
|
and definition.description.zone is not None
|
|
and definition.description.zone > _get_plate_count(device.tech_type)
|
|
):
|
|
# don't create plate entity if not expected by the appliance tech type
|
|
return False
|
|
return True
|
|
|
|
def _async_add_devices() -> None:
|
|
nonlocal added_devices, added_entities
|
|
entities: list = []
|
|
entity_class: type[MieleSensor]
|
|
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
|
|
added_devices = current_devices
|
|
|
|
for device_id, device in coordinator.data.devices.items():
|
|
for definition in SENSOR_TYPES:
|
|
# device is not supported, skip
|
|
if device.device_type not in definition.types:
|
|
continue
|
|
|
|
entity_class = _get_entity_class(definition)
|
|
unique_id = (
|
|
definition.description.unique_id_fn(
|
|
device_id, definition.description
|
|
)
|
|
if definition.description.unique_id_fn is not None
|
|
else MieleEntity.get_unique_id(device_id, definition.description)
|
|
)
|
|
|
|
# entity was already added, skip
|
|
if device_id not in new_devices_set and unique_id in added_entities:
|
|
continue
|
|
|
|
# sensors is not enabled, skip
|
|
if not _is_sensor_enabled(definition, device, unique_id):
|
|
continue
|
|
|
|
added_entities.add(unique_id)
|
|
entities.append(
|
|
entity_class(coordinator, device_id, definition.description)
|
|
)
|
|
async_add_entities(entities)
|
|
|
|
config_entry.async_on_unload(coordinator.async_add_listener(_async_add_devices))
|
|
_async_add_devices()
|
|
|
|
|
|
APPLIANCE_ICONS = {
|
|
MieleAppliance.WASHING_MACHINE: "mdi:washing-machine",
|
|
MieleAppliance.TUMBLE_DRYER: "mdi:tumble-dryer",
|
|
MieleAppliance.TUMBLE_DRYER_SEMI_PROFESSIONAL: "mdi:tumble-dryer",
|
|
MieleAppliance.DISHWASHER: "mdi:dishwasher",
|
|
MieleAppliance.OVEN: "mdi:chef-hat",
|
|
MieleAppliance.OVEN_MICROWAVE: "mdi:chef-hat",
|
|
MieleAppliance.HOB_HIGHLIGHT: "mdi:pot-steam-outline",
|
|
MieleAppliance.STEAM_OVEN: "mdi:chef-hat",
|
|
MieleAppliance.MICROWAVE: "mdi:microwave",
|
|
MieleAppliance.COFFEE_SYSTEM: "mdi:coffee-maker",
|
|
MieleAppliance.HOOD: "mdi:turbine",
|
|
MieleAppliance.FRIDGE: "mdi:fridge-industrial-outline",
|
|
MieleAppliance.FREEZER: "mdi:fridge-industrial-outline",
|
|
MieleAppliance.FRIDGE_FREEZER: "mdi:fridge-outline",
|
|
MieleAppliance.ROBOT_VACUUM_CLEANER: "mdi:robot-vacuum",
|
|
MieleAppliance.WASHER_DRYER: "mdi:washing-machine",
|
|
MieleAppliance.DISH_WARMER: "mdi:heat-wave",
|
|
MieleAppliance.HOB_INDUCTION: "mdi:pot-steam-outline",
|
|
MieleAppliance.STEAM_OVEN_COMBI: "mdi:chef-hat",
|
|
MieleAppliance.WINE_CABINET: "mdi:glass-wine",
|
|
MieleAppliance.WINE_CONDITIONING_UNIT: "mdi:glass-wine",
|
|
MieleAppliance.WINE_STORAGE_CONDITIONING_UNIT: "mdi:glass-wine",
|
|
MieleAppliance.STEAM_OVEN_MICRO: "mdi:chef-hat",
|
|
MieleAppliance.DIALOG_OVEN: "mdi:chef-hat",
|
|
MieleAppliance.WINE_CABINET_FREEZER: "mdi:glass-wine",
|
|
MieleAppliance.HOB_INDUCT_EXTR: "mdi:pot-steam-outline",
|
|
}
|
|
|
|
|
|
class MieleSensor(MieleEntity, SensorEntity):
|
|
"""Representation of a Sensor."""
|
|
|
|
entity_description: MieleSensorDescription
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: MieleDataUpdateCoordinator,
|
|
device_id: str,
|
|
description: MieleSensorDescription,
|
|
) -> None:
|
|
"""Initialize the sensor."""
|
|
super().__init__(coordinator, device_id, description)
|
|
if description.unique_id_fn is not None:
|
|
self._attr_unique_id = description.unique_id_fn(device_id, description)
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the sensor."""
|
|
return self.entity_description.value_fn(self.device)
|
|
|
|
@property
|
|
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
|
"""Return extra_state_attributes."""
|
|
if self.entity_description.extra_attributes is None:
|
|
return None
|
|
attr = {}
|
|
for key, value in self.entity_description.extra_attributes.items():
|
|
attr[key] = value(self.device)
|
|
return attr
|
|
|
|
|
|
class MieleRestorableSensor(MieleSensor, RestoreSensor):
|
|
"""Representation of a Sensor whose internal state can be restored."""
|
|
|
|
_last_value: StateType
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: MieleDataUpdateCoordinator,
|
|
device_id: str,
|
|
description: MieleSensorDescription,
|
|
) -> None:
|
|
"""Initialize the sensor."""
|
|
super().__init__(coordinator, device_id, description)
|
|
self._last_value = None
|
|
|
|
async def async_added_to_hass(self) -> None:
|
|
"""When entity is added to hass."""
|
|
await super().async_added_to_hass()
|
|
|
|
# recover last value from cache when adding entity
|
|
last_value = await self.async_get_last_state()
|
|
if last_value and last_value.state != STATE_UNKNOWN:
|
|
self._last_value = last_value.state
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the sensor."""
|
|
return self._last_value
|
|
|
|
def _update_last_value(self) -> None:
|
|
"""Update the last value of the sensor."""
|
|
self._last_value = self.entity_description.value_fn(self.device)
|
|
|
|
@callback
|
|
def _handle_coordinator_update(self) -> None:
|
|
"""Handle updated data from the coordinator."""
|
|
self._update_last_value()
|
|
super()._handle_coordinator_update()
|
|
|
|
|
|
class MielePlateSensor(MieleSensor):
|
|
"""Representation of a Sensor."""
|
|
|
|
entity_description: MieleSensorDescription
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the plate sensor."""
|
|
# state_plate_step is [] if all zones are off
|
|
|
|
return (
|
|
PlatePowerStep(
|
|
cast(
|
|
int,
|
|
self.device.state_plate_step[
|
|
cast(int, self.entity_description.zone) - 1
|
|
].value_raw,
|
|
)
|
|
).name
|
|
if self.device.state_plate_step
|
|
else PlatePowerStep.plate_step_0.name
|
|
)
|
|
|
|
|
|
class MieleStatusSensor(MieleSensor):
|
|
"""Representation of the status sensor."""
|
|
|
|
def __init__(
|
|
self,
|
|
coordinator: MieleDataUpdateCoordinator,
|
|
device_id: str,
|
|
description: MieleSensorDescription,
|
|
) -> None:
|
|
"""Initialize the sensor."""
|
|
super().__init__(coordinator, device_id, description)
|
|
self._attr_name = None
|
|
self._attr_icon = APPLIANCE_ICONS.get(
|
|
MieleAppliance(self.device.device_type),
|
|
"mdi:state-machine",
|
|
)
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the sensor."""
|
|
return STATE_STATUS_TAGS.get(StateStatus(self.device.state_status))
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
"""Return the availability of the entity."""
|
|
# This sensor should always be available
|
|
return True
|
|
|
|
|
|
# Some phases have names that are not valid python identifiers, so we need to translate
|
|
# them in order to avoid breaking changes
|
|
PROGRAM_PHASE_TRANSLATION = {
|
|
"second_espresso": "2nd_espresso",
|
|
"second_grinding": "2nd_grinding",
|
|
"second_pre_brewing": "2nd_pre_brewing",
|
|
}
|
|
|
|
|
|
class MielePhaseSensor(MieleSensor):
|
|
"""Representation of the program phase sensor."""
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the phase sensor."""
|
|
program_phase = PROGRAM_PHASE[self.device.device_type](
|
|
self.device.state_program_phase
|
|
).name
|
|
|
|
return (
|
|
PROGRAM_PHASE_TRANSLATION.get(program_phase, program_phase)
|
|
if program_phase is not None
|
|
else None
|
|
)
|
|
|
|
@property
|
|
def options(self) -> list[str]:
|
|
"""Return the options list for the actual device type."""
|
|
phases = PROGRAM_PHASE[self.device.device_type].keys()
|
|
return sorted([PROGRAM_PHASE_TRANSLATION.get(phase, phase) for phase in phases])
|
|
|
|
|
|
class MieleProgramIdSensor(MieleSensor):
|
|
"""Representation of the program id sensor."""
|
|
|
|
_unrecorded_attributes = frozenset({ATTRIBUTE_PROFILE})
|
|
|
|
@property
|
|
def native_value(self) -> StateType:
|
|
"""Return the state of the sensor."""
|
|
ret_val = STATE_PROGRAM_ID.get(self.device.device_type, {}).get(
|
|
self.device.state_program_id
|
|
)
|
|
if ret_val is None:
|
|
_LOGGER.debug(
|
|
"Unknown program id: %s on device type: %s",
|
|
self.device.state_program_id,
|
|
self.device.device_type,
|
|
)
|
|
return ret_val
|
|
|
|
@property
|
|
def options(self) -> list[str]:
|
|
"""Return the options list for the actual device type."""
|
|
return sorted(set(STATE_PROGRAM_ID.get(self.device.device_type, {}).values()))
|
|
|
|
|
|
class MieleTimeSensor(MieleRestorableSensor):
|
|
"""Representation of time sensors keeping state from cache."""
|
|
|
|
def _update_last_value(self) -> None:
|
|
"""Update the last value of the sensor."""
|
|
|
|
current_value = self.entity_description.value_fn(self.device)
|
|
current_status = StateStatus(self.device.state_status)
|
|
|
|
# report end-specific value when program ends (some devices are immediately reporting 0...)
|
|
if (
|
|
current_status == StateStatus.PROGRAM_ENDED
|
|
and self.entity_description.end_value_fn is not None
|
|
):
|
|
self._last_value = self.entity_description.end_value_fn(self._last_value)
|
|
|
|
# keep value when program ends if no function is specified
|
|
elif current_status == StateStatus.PROGRAM_ENDED:
|
|
pass
|
|
|
|
# force unknown when appliance is not working (some devices are keeping last value until a new cycle starts)
|
|
elif current_status in (StateStatus.OFF, StateStatus.ON, StateStatus.IDLE):
|
|
self._last_value = None
|
|
|
|
# otherwise, cache value and return it
|
|
else:
|
|
self._last_value = current_value
|
|
|
|
|
|
class MieleConsumptionSensor(MieleRestorableSensor):
|
|
"""Representation of consumption sensors keeping state from cache."""
|
|
|
|
_is_reporting: bool = False
|
|
|
|
def _update_last_value(self) -> None:
|
|
"""Update the last value of the sensor."""
|
|
current_value = self.entity_description.value_fn(self.device)
|
|
current_status = StateStatus(self.device.state_status)
|
|
last_value = (
|
|
float(cast(str, self._last_value))
|
|
if self._last_value is not None and self._last_value != STATE_UNKNOWN
|
|
else 0
|
|
)
|
|
|
|
# force unknown when appliance is not able to report consumption
|
|
if current_status in (
|
|
StateStatus.ON,
|
|
StateStatus.OFF,
|
|
StateStatus.PROGRAMMED,
|
|
StateStatus.WAITING_TO_START,
|
|
StateStatus.IDLE,
|
|
StateStatus.SERVICE,
|
|
):
|
|
self._is_reporting = False
|
|
self._last_value = None
|
|
|
|
# appliance might report the last value for consumption of previous cycle and it will report 0
|
|
# only after a while, so it is necessary to force 0 until we see the 0 value coming from API, unless
|
|
# we already saw a valid value in this cycle from cache
|
|
elif (
|
|
current_status in (StateStatus.IN_USE, StateStatus.PAUSE)
|
|
and not self._is_reporting
|
|
and last_value > 0
|
|
):
|
|
self._last_value = current_value
|
|
self._is_reporting = True
|
|
|
|
elif (
|
|
current_status in (StateStatus.IN_USE, StateStatus.PAUSE)
|
|
and not self._is_reporting
|
|
and current_value is not None
|
|
and cast(int, current_value) > 0
|
|
):
|
|
self._last_value = 0
|
|
|
|
# keep value when program ends
|
|
elif current_status == StateStatus.PROGRAM_ENDED:
|
|
pass
|
|
|
|
else:
|
|
self._last_value = current_value
|
|
self._is_reporting = True
|