77import logging
88from typing import Final , cast
99
10- from pymiele import MieleDevice
10+ from pymiele import MieleDevice , MieleTemperature
1111
1212from homeassistant .components .sensor import (
1313 SensorDeviceClass ,
2525 UnitOfVolume ,
2626)
2727from homeassistant .core import HomeAssistant
28+ from homeassistant .helpers import entity_registry as er
2829from homeassistant .helpers .entity_platform import AddConfigEntryEntitiesCallback
2930from homeassistant .helpers .typing import StateType
3031
3132from .const import (
33+ DISABLED_TEMP_ENTITIES ,
34+ DOMAIN ,
3235 STATE_PROGRAM_ID ,
3336 STATE_PROGRAM_PHASE ,
3437 STATE_STATUS_TAGS ,
4548
4649_LOGGER = logging .getLogger (__name__ )
4750
48- DISABLED_TEMPERATURE = - 32768
49-
5051DEFAULT_PLATE_COUNT = 4
5152
5253PLATE_COUNT = {
@@ -75,12 +76,25 @@ def _convert_duration(value_list: list[int]) -> int | None:
7576 return value_list [0 ] * 60 + value_list [1 ] if value_list else None
7677
7778
79+ def _convert_temperature (
80+ value_list : list [MieleTemperature ], index : int
81+ ) -> float | None :
82+ """Convert temperature object to readable value."""
83+ if index >= len (value_list ):
84+ return None
85+ raw_value = cast (int , value_list [index ].temperature ) / 100.0
86+ if raw_value in DISABLED_TEMP_ENTITIES :
87+ return None
88+ return raw_value
89+
90+
7891@dataclass (frozen = True , kw_only = True )
7992class MieleSensorDescription (SensorEntityDescription ):
8093 """Class describing Miele sensor entities."""
8194
8295 value_fn : Callable [[MieleDevice ], StateType ]
83- zone : int = 1
96+ zone : int | None = None
97+ unique_id_fn : Callable [[str , MieleSensorDescription ], str ] | None = None
8498
8599
86100@dataclass
@@ -404,32 +418,20 @@ class MieleSensorDefinition:
404418 ),
405419 description = MieleSensorDescription (
406420 key = "state_temperature_1" ,
421+ zone = 1 ,
407422 device_class = SensorDeviceClass .TEMPERATURE ,
408423 native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
409424 state_class = SensorStateClass .MEASUREMENT ,
410- value_fn = lambda value : cast (int , value .state_temperatures [0 ].temperature )
411- / 100.0 ,
425+ value_fn = lambda value : _convert_temperature (value .state_temperatures , 0 ),
412426 ),
413427 ),
414428 MieleSensorDefinition (
415429 types = (
416- MieleAppliance .TUMBLE_DRYER_SEMI_PROFESSIONAL ,
417- MieleAppliance .OVEN ,
418- MieleAppliance .OVEN_MICROWAVE ,
419- MieleAppliance .DISH_WARMER ,
420- MieleAppliance .STEAM_OVEN ,
421- MieleAppliance .MICROWAVE ,
422- MieleAppliance .FRIDGE ,
423- MieleAppliance .FREEZER ,
424430 MieleAppliance .FRIDGE_FREEZER ,
425- MieleAppliance .STEAM_OVEN_COMBI ,
426431 MieleAppliance .WINE_CABINET ,
427432 MieleAppliance .WINE_CONDITIONING_UNIT ,
428433 MieleAppliance .WINE_STORAGE_CONDITIONING_UNIT ,
429- MieleAppliance .STEAM_OVEN_MICRO ,
430- MieleAppliance .DIALOG_OVEN ,
431434 MieleAppliance .WINE_CABINET_FREEZER ,
432- MieleAppliance .STEAM_OVEN_MK2 ,
433435 ),
434436 description = MieleSensorDescription (
435437 key = "state_temperature_2" ,
@@ -438,7 +440,24 @@ class MieleSensorDefinition:
438440 translation_key = "temperature_zone_2" ,
439441 native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
440442 state_class = SensorStateClass .MEASUREMENT ,
441- value_fn = lambda value : value .state_temperatures [1 ].temperature / 100.0 , # type: ignore [operator]
443+ value_fn = lambda value : _convert_temperature (value .state_temperatures , 1 ),
444+ ),
445+ ),
446+ MieleSensorDefinition (
447+ types = (
448+ MieleAppliance .WINE_CABINET ,
449+ MieleAppliance .WINE_CONDITIONING_UNIT ,
450+ MieleAppliance .WINE_STORAGE_CONDITIONING_UNIT ,
451+ MieleAppliance .WINE_CABINET_FREEZER ,
452+ ),
453+ description = MieleSensorDescription (
454+ key = "state_temperature_3" ,
455+ zone = 3 ,
456+ device_class = SensorDeviceClass .TEMPERATURE ,
457+ translation_key = "temperature_zone_3" ,
458+ native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
459+ state_class = SensorStateClass .MEASUREMENT ,
460+ value_fn = lambda value : _convert_temperature (value .state_temperatures , 2 ),
442461 ),
443462 ),
444463 MieleSensorDefinition (
@@ -454,11 +473,8 @@ class MieleSensorDefinition:
454473 device_class = SensorDeviceClass .TEMPERATURE ,
455474 native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
456475 state_class = SensorStateClass .MEASUREMENT ,
457- value_fn = (
458- lambda value : cast (
459- int , value .state_core_target_temperature [0 ].temperature
460- )
461- / 100.0
476+ value_fn = lambda value : _convert_temperature (
477+ value .state_core_target_temperature , 0
462478 ),
463479 ),
464480 ),
@@ -479,9 +495,8 @@ class MieleSensorDefinition:
479495 device_class = SensorDeviceClass .TEMPERATURE ,
480496 native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
481497 state_class = SensorStateClass .MEASUREMENT ,
482- value_fn = (
483- lambda value : cast (int , value .state_target_temperature [0 ].temperature )
484- / 100.0
498+ value_fn = lambda value : _convert_temperature (
499+ value .state_target_temperature , 0
485500 ),
486501 ),
487502 ),
@@ -497,9 +512,8 @@ class MieleSensorDefinition:
497512 device_class = SensorDeviceClass .TEMPERATURE ,
498513 native_unit_of_measurement = UnitOfTemperature .CELSIUS ,
499514 state_class = SensorStateClass .MEASUREMENT ,
500- value_fn = (
501- lambda value : cast (int , value .state_core_temperature [0 ].temperature )
502- / 100.0
515+ value_fn = lambda value : _convert_temperature (
516+ value .state_core_temperature , 0
503517 ),
504518 ),
505519 ),
@@ -518,6 +532,8 @@ class MieleSensorDefinition:
518532 device_class = SensorDeviceClass .ENUM ,
519533 options = sorted (PlatePowerStep .keys ()),
520534 value_fn = lambda value : None ,
535+ unique_id_fn = lambda device_id ,
536+ description : f"{ device_id } -{ description .key } -{ description .zone } " ,
521537 ),
522538 )
523539 for i in range (1 , 7 )
@@ -559,51 +575,88 @@ async def async_setup_entry(
559575) -> None :
560576 """Set up the sensor platform."""
561577 coordinator = config_entry .runtime_data
562- added_devices : set [str ] = set ()
578+ added_devices : set [str ] = set () # device_id
579+ added_entities : set [str ] = set () # unique_id
580+
581+ def _get_entity_class (definition : MieleSensorDefinition ) -> type [MieleSensor ]:
582+ """Get the entity class for the sensor."""
583+ return {
584+ "state_status" : MieleStatusSensor ,
585+ "state_program_id" : MieleProgramIdSensor ,
586+ "state_program_phase" : MielePhaseSensor ,
587+ "state_plate_step" : MielePlateSensor ,
588+ }.get (definition .description .key , MieleSensor )
589+
590+ def _is_entity_registered (unique_id : str ) -> bool :
591+ """Check if the entity is already registered."""
592+ entity_registry = er .async_get (hass )
593+ return any (
594+ entry .platform == DOMAIN and entry .unique_id == unique_id
595+ for entry in entity_registry .entities .values ()
596+ )
597+
598+ def _is_sensor_enabled (
599+ definition : MieleSensorDefinition ,
600+ device : MieleDevice ,
601+ unique_id : str ,
602+ ) -> bool :
603+ """Check if the sensor is enabled."""
604+ if (
605+ definition .description .device_class == SensorDeviceClass .TEMPERATURE
606+ and definition .description .value_fn (device ) is None
607+ and definition .description .zone != 1
608+ ):
609+ # all appliances supporting temperature have at least zone 1, for other zones
610+ # don't create entity if API signals that datapoint is disabled, unless the sensor
611+ # already appeared in the past (= it provided a valid value)
612+ return _is_entity_registered (unique_id )
613+ if (
614+ definition .description .key == "state_plate_step"
615+ and definition .description .zone is not None
616+ and definition .description .zone > _get_plate_count (device .tech_type )
617+ ):
618+ # don't create plate entity if not expected by the appliance tech type
619+ return False
620+ return True
563621
564- def _async_add_new_devices () -> None :
565- nonlocal added_devices
622+ def _async_add_devices () -> None :
623+ nonlocal added_devices , added_entities
566624 entities : list = []
567625 entity_class : type [MieleSensor ]
568626 new_devices_set , current_devices = coordinator .async_add_devices (added_devices )
569627 added_devices = current_devices
570628
571629 for device_id , device in coordinator .data .devices .items ():
572630 for definition in SENSOR_TYPES :
573- if (
574- device_id in new_devices_set
575- and device .device_type in definition .types
576- ):
577- match definition .description .key :
578- case "state_status" :
579- entity_class = MieleStatusSensor
580- case "state_program_id" :
581- entity_class = MieleProgramIdSensor
582- case "state_program_phase" :
583- entity_class = MielePhaseSensor
584- case "state_plate_step" :
585- entity_class = MielePlateSensor
586- case _:
587- entity_class = MieleSensor
588- if (
589- definition .description .device_class
590- == SensorDeviceClass .TEMPERATURE
591- and definition .description .value_fn (device )
592- == DISABLED_TEMPERATURE / 100
593- ) or (
594- definition .description .key == "state_plate_step"
595- and definition .description .zone
596- > _get_plate_count (device .tech_type )
597- ):
598- # Don't create entity if API signals that datapoint is disabled
599- continue
600- entities .append (
601- entity_class (coordinator , device_id , definition .description )
631+ # device is not supported, skip
632+ if device .device_type not in definition .types :
633+ continue
634+
635+ entity_class = _get_entity_class (definition )
636+ unique_id = (
637+ definition .description .unique_id_fn (
638+ device_id , definition .description
602639 )
640+ if definition .description .unique_id_fn is not None
641+ else MieleEntity .get_unique_id (device_id , definition .description )
642+ )
643+
644+ # entity was already added, skip
645+ if device_id not in new_devices_set and unique_id in added_entities :
646+ continue
647+
648+ # sensors is not enabled, skip
649+ if not _is_sensor_enabled (definition , device , unique_id ):
650+ continue
651+
652+ added_entities .add (unique_id )
653+ entities .append (
654+ entity_class (coordinator , device_id , definition .description )
655+ )
603656 async_add_entities (entities )
604657
605- config_entry .async_on_unload (coordinator .async_add_listener (_async_add_new_devices ))
606- _async_add_new_devices ()
658+ config_entry .async_on_unload (coordinator .async_add_listener (_async_add_devices ))
659+ _async_add_devices ()
607660
608661
609662APPLIANCE_ICONS = {
@@ -641,6 +694,17 @@ class MieleSensor(MieleEntity, SensorEntity):
641694
642695 entity_description : MieleSensorDescription
643696
697+ def __init__ (
698+ self ,
699+ coordinator : MieleDataUpdateCoordinator ,
700+ device_id : str ,
701+ description : MieleSensorDescription ,
702+ ) -> None :
703+ """Initialize the sensor."""
704+ super ().__init__ (coordinator , device_id , description )
705+ if description .unique_id_fn is not None :
706+ self ._attr_unique_id = description .unique_id_fn (device_id , description )
707+
644708 @property
645709 def native_value (self ) -> StateType :
646710 """Return the state of the sensor."""
@@ -652,16 +716,6 @@ class MielePlateSensor(MieleSensor):
652716
653717 entity_description : MieleSensorDescription
654718
655- def __init__ (
656- self ,
657- coordinator : MieleDataUpdateCoordinator ,
658- device_id : str ,
659- description : MieleSensorDescription ,
660- ) -> None :
661- """Initialize the plate sensor."""
662- super ().__init__ (coordinator , device_id , description )
663- self ._attr_unique_id = f"{ device_id } -{ description .key } -{ description .zone } "
664-
665719 @property
666720 def native_value (self ) -> StateType :
667721 """Return the state of the plate sensor."""
@@ -672,7 +726,7 @@ def native_value(self) -> StateType:
672726 cast (
673727 int ,
674728 self .device .state_plate_step [
675- self .entity_description .zone - 1
729+ cast ( int , self .entity_description .zone ) - 1
676730 ].value_raw ,
677731 )
678732 ).name
0 commit comments