Add "profile" extra attribute to Miele program sensor on coffee machines (#145073)

This commit is contained in:
Andrea Turri
2025-08-21 10:19:13 +02:00
committed by GitHub
parent bbde98bc9f
commit f9575a3b2f
6 changed files with 550 additions and 3 deletions

View File

@@ -850,6 +850,14 @@ COFFEE_SYSTEM_PROGRAM_ID: dict[int, str] = {
24813: "appliance_settings", # modify profile name
}
COFFEE_SYSTEM_PROFILE: dict[range, str] = {
range(24000, 24032): "profile_1",
range(24032, 24064): "profile_2",
range(24064, 24096): "profile_3",
range(24096, 24128): "profile_4",
range(24128, 24160): "profile_5",
}
STEAM_OVEN_MICRO_PROGRAM_ID: dict[int, str] = {
8: "steam_cooking",
19: "microwave",

View File

@@ -2,10 +2,10 @@
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Mapping
from dataclasses import dataclass
import logging
from typing import Final, cast
from typing import Any, Final, cast
from pymiele import MieleDevice, MieleTemperature
@@ -30,6 +30,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import (
COFFEE_SYSTEM_PROFILE,
DISABLED_TEMP_ENTITIES,
DOMAIN,
STATE_PROGRAM_ID,
@@ -61,6 +62,8 @@ PLATE_COUNT = {
"KMX": 6,
}
ATTRIBUTE_PROFILE = "profile"
def _get_plate_count(tech_type: str) -> int:
"""Get number of zones for hob."""
@@ -88,11 +91,21 @@ def _convert_temperature(
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]
extra_attributes: dict[str, Callable[[MieleDevice], StateType]] | None = None
zone: int | None = None
unique_id_fn: Callable[[str, MieleSensorDescription], str] | None = None
@@ -157,7 +170,6 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
MieleAppliance.OVEN_MICROWAVE,
MieleAppliance.STEAM_OVEN,
MieleAppliance.MICROWAVE,
MieleAppliance.COFFEE_SYSTEM,
MieleAppliance.ROBOT_VACUUM_CLEANER,
MieleAppliance.WASHER_DRYER,
MieleAppliance.STEAM_OVEN_COMBI,
@@ -172,6 +184,18 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
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,
@@ -710,6 +734,16 @@ class MieleSensor(MieleEntity, SensorEntity):
"""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 MielePlateSensor(MieleSensor):
"""Representation of a Sensor."""
@@ -792,6 +826,8 @@ class MielePhaseSensor(MieleSensor):
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."""

View File

@@ -991,6 +991,18 @@
"yom_tov": "Yom tov",
"yorkshire_pudding": "Yorkshire pudding",
"zander_fillet": "Zander (fillet)"
},
"state_attributes": {
"profile": {
"name": "Profile",
"state": {
"profile_1": "Profile 1",
"profile_2": "Profile 2",
"profile_3": "Profile 3",
"profile_4": "Profile 4",
"profile_5": "Profile 5"
}
}
}
},
"spin_speed": {

View File

@@ -0,0 +1,126 @@
{
"DummyAppliance_CoffeeSystem": {
"ident": {
"type": {
"key_localized": "Device type",
"value_raw": 17,
"value_localized": "Coffee machine"
},
"deviceName": "",
"protocolVersion": 4,
"deviceIdentLabel": {
"fabNumber": "**REDACTED**",
"fabIndex": "11",
"techType": "CM6160",
"matNumber": "11488670",
"swids": []
},
"xkmIdentLabel": {
"techType": "EK037",
"releaseVersion": "04.05"
}
},
"state": {
"ProgramID": {
"value_raw": 24001,
"value_localized": "espresso",
"key_localized": "Program name"
},
"status": {
"value_raw": 5,
"value_localized": "In use",
"key_localized": "status"
},
"programType": {
"value_raw": 1,
"value_localized": "Program",
"key_localized": "Program type"
},
"programPhase": {
"value_raw": 4353,
"value_localized": "Espresso",
"key_localized": "Program phase"
},
"remainingTime": [0, 0],
"startTime": [0, 0],
"targetTemperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"coreTargetTemperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"temperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
},
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"coreTemperature": [
{
"value_raw": -32768,
"value_localized": null,
"unit": "Celsius"
}
],
"signalInfo": false,
"signalFailure": false,
"signalDoor": false,
"remoteEnable": {
"fullRemoteControl": true,
"smartGrid": false,
"mobileStart": false
},
"ambientLight": null,
"light": 1,
"elapsedTime": [],
"spinningSpeed": {
"unit": "rpm",
"value_raw": null,
"value_localized": null,
"key_localized": "Spin speed"
},
"dryingStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Drying level"
},
"ventilationStep": {
"value_raw": null,
"value_localized": "",
"key_localized": "Fan level"
},
"plateStep": [],
"ecoFeedback": null,
"batteryLevel": null
}
}
}

View File

@@ -1,4 +1,354 @@
# serializer version: 1
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'autocleaning',
'failure',
'idle',
'in_use',
'not_connected',
'off',
'on',
'pause',
'program_ended',
'program_interrupted',
'programmed',
'rinse_hold',
'service',
'supercooling',
'supercooling_superfreezing',
'superfreezing',
'superheating',
'waiting_to_start',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.coffee_system',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': 'mdi:coffee-maker',
'original_name': None,
'platform': 'miele',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'status',
'unique_id': 'DummyAppliance_CoffeeSystem-state_status',
'unit_of_measurement': None,
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Coffee system',
'icon': 'mdi:coffee-maker',
'options': list([
'autocleaning',
'failure',
'idle',
'in_use',
'not_connected',
'off',
'on',
'pause',
'program_ended',
'program_interrupted',
'programmed',
'rinse_hold',
'service',
'supercooling',
'supercooling_superfreezing',
'superfreezing',
'superheating',
'waiting_to_start',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.coffee_system',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'in_use',
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'appliance_rinse',
'appliance_settings',
'barista_assistant',
'black_tea',
'brewing_unit_degrease',
'cafe_au_lait',
'caffe_latte',
'cappuccino',
'cappuccino_italiano',
'check_appliance',
'coffee',
'coffee_pot',
'descaling',
'espresso',
'espresso_macchiato',
'flat_white',
'fruit_tea',
'green_tea',
'herbal_tea',
'hot_milk',
'hot_water',
'japanese_tea',
'latte_macchiato',
'long_coffee',
'milk_foam',
'milk_pipework_clean',
'milk_pipework_rinse',
'no_program',
'ristretto',
'very_hot_water',
'white_tea',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.coffee_system_program',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Program',
'platform': 'miele',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'program_id',
'unique_id': 'DummyAppliance_CoffeeSystem-state_program_id',
'unit_of_measurement': None,
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Coffee system Program',
'options': list([
'appliance_rinse',
'appliance_settings',
'barista_assistant',
'black_tea',
'brewing_unit_degrease',
'cafe_au_lait',
'caffe_latte',
'cappuccino',
'cappuccino_italiano',
'check_appliance',
'coffee',
'coffee_pot',
'descaling',
'espresso',
'espresso_macchiato',
'flat_white',
'fruit_tea',
'green_tea',
'herbal_tea',
'hot_milk',
'hot_water',
'japanese_tea',
'latte_macchiato',
'long_coffee',
'milk_foam',
'milk_pipework_clean',
'milk_pipework_rinse',
'no_program',
'ristretto',
'very_hot_water',
'white_tea',
]),
'profile': 'profile_1',
}),
'context': <ANY>,
'entity_id': 'sensor.coffee_system_program',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'espresso',
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program_phase-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'2nd_espresso',
'2nd_grinding',
'2nd_pre_brewing',
'dispensing',
'espresso',
'grinding',
'heating_up',
'hot_milk',
'milk_foam',
'not_running',
'pre_brewing',
'rinse',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.coffee_system_program_phase',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Program phase',
'platform': 'miele',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'program_phase',
'unique_id': 'DummyAppliance_CoffeeSystem-state_program_phase',
'unit_of_measurement': None,
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program_phase-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Coffee system Program phase',
'options': list([
'2nd_espresso',
'2nd_grinding',
'2nd_pre_brewing',
'dispensing',
'espresso',
'grinding',
'heating_up',
'hot_milk',
'milk_foam',
'not_running',
'pre_brewing',
'rinse',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.coffee_system_program_phase',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'espresso',
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program_type-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'automatic_program',
'cleaning_care_program',
'maintenance_program',
'normal_operation_mode',
'own_program',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.coffee_system_program_type',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Program type',
'platform': 'miele',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'program_type',
'unique_id': 'DummyAppliance_CoffeeSystem-state_program_type',
'unit_of_measurement': None,
})
# ---
# name: test_coffee_system_sensor_states[platforms0-coffee_system.json][sensor.coffee_system_program_type-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Coffee system Program type',
'options': list([
'automatic_program',
'cleaning_care_program',
'maintenance_program',
'normal_operation_mode',
'own_program',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.coffee_system_program_type',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'own_program',
})
# ---
# name: test_fan_hob_sensor_states[platforms0-fan_devices.json][sensor.hob_with_extraction-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -271,3 +271,18 @@ async def test_fan_hob_sensor_states(
"""Test robot fan / hob sensor state."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
@pytest.mark.parametrize("load_device_file", ["coffee_system.json"])
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_coffee_system_sensor_states(
hass: HomeAssistant,
mock_miele_client: MagicMock,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
setup_platform: None,
) -> None:
"""Test coffee system sensor state."""
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)