Skip to content

Commit 6a6054a

Browse files
aturriemontnemerycdce8p
authored
Miele RestoreSensor: restore native value rather than stringified state (#152750)
Co-authored-by: Erik Montnemery <[email protected]> Co-authored-by: Marc Mueller <[email protected]>
1 parent 3377e90 commit 6a6054a

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

homeassistant/components/miele/sensor.py

Lines changed: 28 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from homeassistant.const import (
2020
PERCENTAGE,
2121
REVOLUTIONS_PER_MINUTE,
22-
STATE_UNKNOWN,
2322
EntityCategory,
2423
UnitOfEnergy,
2524
UnitOfTemperature,
@@ -762,40 +761,35 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None:
762761
class MieleRestorableSensor(MieleSensor, RestoreSensor):
763762
"""Representation of a Sensor whose internal state can be restored."""
764763

765-
_last_value: StateType
766-
767-
def __init__(
768-
self,
769-
coordinator: MieleDataUpdateCoordinator,
770-
device_id: str,
771-
description: MieleSensorDescription,
772-
) -> None:
773-
"""Initialize the sensor."""
774-
super().__init__(coordinator, device_id, description)
775-
self._last_value = None
764+
_attr_native_value: StateType
776765

777766
async def async_added_to_hass(self) -> None:
778767
"""When entity is added to hass."""
779768
await super().async_added_to_hass()
780769

781770
# recover last value from cache when adding entity
782-
last_value = await self.async_get_last_state()
783-
if last_value and last_value.state != STATE_UNKNOWN:
784-
self._last_value = last_value.state
771+
last_data = await self.async_get_last_sensor_data()
772+
if last_data:
773+
self._attr_native_value = last_data.native_value # type: ignore[assignment]
785774

786775
@property
787776
def native_value(self) -> StateType:
788-
"""Return the state of the sensor."""
789-
return self._last_value
777+
"""Return the state of the sensor.
790778
791-
def _update_last_value(self) -> None:
792-
"""Update the last value of the sensor."""
793-
self._last_value = self.entity_description.value_fn(self.device)
779+
It is necessary to override `native_value` to fall back to the default
780+
attribute-based implementation, instead of the function-based
781+
implementation in `MieleSensor`.
782+
"""
783+
return self._attr_native_value
784+
785+
def _update_native_value(self) -> None:
786+
"""Update the native value attribute of the sensor."""
787+
self._attr_native_value = self.entity_description.value_fn(self.device)
794788

795789
@callback
796790
def _handle_coordinator_update(self) -> None:
797791
"""Handle updated data from the coordinator."""
798-
self._update_last_value()
792+
self._update_native_value()
799793
super()._handle_coordinator_update()
800794

801795

@@ -912,7 +906,7 @@ def options(self) -> list[str]:
912906
class MieleTimeSensor(MieleRestorableSensor):
913907
"""Representation of time sensors keeping state from cache."""
914908

915-
def _update_last_value(self) -> None:
909+
def _update_native_value(self) -> None:
916910
"""Update the last value of the sensor."""
917911

918912
current_value = self.entity_description.value_fn(self.device)
@@ -923,33 +917,35 @@ def _update_last_value(self) -> None:
923917
current_status == StateStatus.PROGRAM_ENDED
924918
and self.entity_description.end_value_fn is not None
925919
):
926-
self._last_value = self.entity_description.end_value_fn(self._last_value)
920+
self._attr_native_value = self.entity_description.end_value_fn(
921+
self._attr_native_value
922+
)
927923

928924
# keep value when program ends if no function is specified
929925
elif current_status == StateStatus.PROGRAM_ENDED:
930926
pass
931927

932928
# force unknown when appliance is not working (some devices are keeping last value until a new cycle starts)
933929
elif current_status in (StateStatus.OFF, StateStatus.ON, StateStatus.IDLE):
934-
self._last_value = None
930+
self._attr_native_value = None
935931

936932
# otherwise, cache value and return it
937933
else:
938-
self._last_value = current_value
934+
self._attr_native_value = current_value
939935

940936

941937
class MieleConsumptionSensor(MieleRestorableSensor):
942938
"""Representation of consumption sensors keeping state from cache."""
943939

944940
_is_reporting: bool = False
945941

946-
def _update_last_value(self) -> None:
942+
def _update_native_value(self) -> None:
947943
"""Update the last value of the sensor."""
948944
current_value = self.entity_description.value_fn(self.device)
949945
current_status = StateStatus(self.device.state_status)
950946
last_value = (
951-
float(cast(str, self._last_value))
952-
if self._last_value is not None and self._last_value != STATE_UNKNOWN
947+
float(cast(str, self._attr_native_value))
948+
if self._attr_native_value is not None
953949
else 0
954950
)
955951

@@ -963,7 +959,7 @@ def _update_last_value(self) -> None:
963959
StateStatus.SERVICE,
964960
):
965961
self._is_reporting = False
966-
self._last_value = None
962+
self._attr_native_value = None
967963

968964
# appliance might report the last value for consumption of previous cycle and it will report 0
969965
# only after a while, so it is necessary to force 0 until we see the 0 value coming from API, unless
@@ -973,7 +969,7 @@ def _update_last_value(self) -> None:
973969
and not self._is_reporting
974970
and last_value > 0
975971
):
976-
self._last_value = current_value
972+
self._attr_native_value = current_value
977973
self._is_reporting = True
978974

979975
elif (
@@ -982,12 +978,12 @@ def _update_last_value(self) -> None:
982978
and current_value is not None
983979
and cast(int, current_value) > 0
984980
):
985-
self._last_value = 0
981+
self._attr_native_value = 0
986982

987983
# keep value when program ends
988984
elif current_status == StateStatus.PROGRAM_ENDED:
989985
pass
990986

991987
else:
992-
self._last_value = current_value
988+
self._attr_native_value = current_value
993989
self._is_reporting = True

tests/components/miele/test_sensor.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010

1111
from homeassistant.components.miele.const import DOMAIN
1212
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
13-
from homeassistant.core import HomeAssistant
13+
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
14+
from homeassistant.core import HomeAssistant, State
1415
from homeassistant.helpers import entity_registry as er
1516

1617
from tests.common import (
1718
MockConfigEntry,
1819
async_fire_time_changed,
1920
async_load_json_object_fixture,
21+
mock_restore_cache_with_extra_data,
2022
snapshot_platform,
2123
)
2224

@@ -583,6 +585,7 @@ async def test_laundry_dry_scenario(
583585
check_sensor_state(hass, "sensor.tumble_dryer_elapsed_time", "20", step)
584586

585587

588+
@pytest.mark.parametrize("restore_state", ["45", STATE_UNKNOWN, STATE_UNAVAILABLE])
586589
@pytest.mark.parametrize("load_device_file", ["laundry.json"])
587590
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
588591
async def test_elapsed_time_sensor_restored(
@@ -592,6 +595,7 @@ async def test_elapsed_time_sensor_restored(
592595
setup_platform: None,
593596
device_fixture: MieleDevices,
594597
freezer: FrozenDateTimeFactory,
598+
restore_state,
595599
) -> None:
596600
"""Test that elapsed time returns the restored value when program ended."""
597601

@@ -648,6 +652,26 @@ async def test_elapsed_time_sensor_restored(
648652

649653
assert hass.states.get(entity_id).state == "unavailable"
650654

655+
# simulate restore with state different from native value
656+
mock_restore_cache_with_extra_data(
657+
hass,
658+
[
659+
(
660+
State(
661+
entity_id,
662+
restore_state,
663+
{
664+
"unit_of_measurement": "min",
665+
},
666+
),
667+
{
668+
"native_value": "12",
669+
"native_unit_of_measurement": "min",
670+
},
671+
),
672+
],
673+
)
674+
651675
await hass.config_entries.async_reload(mock_config_entry.entry_id)
652676
await hass.async_block_till_done()
653677

0 commit comments

Comments
 (0)