mirror of
https://github.com/home-assistant/core.git
synced 2025-09-01 10:51:47 +02:00
Provide elapsed time sensor consistent in Miele (#145093)
Co-authored-by: Robert Resch <robert@resch.dev>
This commit is contained in:
@@ -10,6 +10,7 @@ from typing import Any, Final, cast
|
||||
from pymiele import MieleDevice, MieleTemperature
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
RestoreSensor,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
@@ -18,13 +19,14 @@ from homeassistant.components.sensor import (
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
REVOLUTIONS_PER_MINUTE,
|
||||
STATE_UNKNOWN,
|
||||
EntityCategory,
|
||||
UnitOfEnergy,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
UnitOfVolume,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
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
|
||||
@@ -105,6 +107,7 @@ 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
|
||||
@@ -386,6 +389,7 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
||||
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,
|
||||
@@ -609,6 +613,7 @@ async def async_setup_entry(
|
||||
"state_program_id": MieleProgramIdSensor,
|
||||
"state_program_phase": MielePhaseSensor,
|
||||
"state_plate_step": MielePlateSensor,
|
||||
"state_elapsed_time": MieleTimeSensor,
|
||||
}.get(definition.description.key, MieleSensor)
|
||||
|
||||
def _is_entity_registered(unique_id: str) -> bool:
|
||||
@@ -745,6 +750,36 @@ class MieleSensor(MieleEntity, SensorEntity):
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
class MielePlateSensor(MieleSensor):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
@@ -846,3 +881,35 @@ class MieleProgramIdSensor(MieleSensor):
|
||||
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."""
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
|
||||
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
|
||||
|
||||
super()._handle_coordinator_update()
|
||||
|
272
tests/components/miele/fixtures/laundry.json
Normal file
272
tests/components/miele/fixtures/laundry.json
Normal file
@@ -0,0 +1,272 @@
|
||||
{
|
||||
"DummyWasher": {
|
||||
"ident": {
|
||||
"type": {
|
||||
"key_localized": "Device type",
|
||||
"value_raw": 1,
|
||||
"value_localized": "Washing machine"
|
||||
},
|
||||
"deviceName": "",
|
||||
"protocolVersion": 4,
|
||||
"deviceIdentLabel": {
|
||||
"fabNumber": "**REDACTED**",
|
||||
"fabIndex": "32",
|
||||
"techType": "WCR870",
|
||||
"matNumber": "10979100",
|
||||
"swids": [
|
||||
"5836",
|
||||
"20457",
|
||||
"20449",
|
||||
"25260",
|
||||
"20450",
|
||||
"5013",
|
||||
"25314",
|
||||
"25205",
|
||||
"25313",
|
||||
"25191"
|
||||
]
|
||||
},
|
||||
"xkmIdentLabel": {
|
||||
"techType": "EK057",
|
||||
"releaseVersion": "08.32"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"ProgramID": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"key_localized": "Program name"
|
||||
},
|
||||
"status": {
|
||||
"value_raw": 1,
|
||||
"value_localized": "Off",
|
||||
"key_localized": "status"
|
||||
},
|
||||
"programType": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"key_localized": "Program type"
|
||||
},
|
||||
"programPhase": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"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": true,
|
||||
"remoteEnable": {
|
||||
"fullRemoteControl": true,
|
||||
"smartGrid": false,
|
||||
"mobileStart": false
|
||||
},
|
||||
"ambientLight": null,
|
||||
"light": null,
|
||||
"elapsedTime": [0, 0],
|
||||
"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
|
||||
}
|
||||
},
|
||||
"DummyDryer": {
|
||||
"ident": {
|
||||
"type": {
|
||||
"key_localized": "Device type",
|
||||
"value_raw": 2,
|
||||
"value_localized": "Tumble dryer"
|
||||
},
|
||||
"deviceName": "",
|
||||
"protocolVersion": 4,
|
||||
"deviceIdentLabel": {
|
||||
"fabNumber": "**REDACTED**",
|
||||
"fabIndex": "13",
|
||||
"techType": "TCJ690WP",
|
||||
"matNumber": "10979980",
|
||||
"swids": [
|
||||
"5213",
|
||||
"25359",
|
||||
"25360",
|
||||
"25002",
|
||||
"20456",
|
||||
"25213",
|
||||
"5136",
|
||||
"20445",
|
||||
"25234",
|
||||
"4174"
|
||||
]
|
||||
},
|
||||
"xkmIdentLabel": {
|
||||
"techType": "EK037",
|
||||
"releaseVersion": "04.05"
|
||||
}
|
||||
},
|
||||
"state": {
|
||||
"ProgramID": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"key_localized": "Program name"
|
||||
},
|
||||
"status": {
|
||||
"value_raw": 1,
|
||||
"value_localized": "Off",
|
||||
"key_localized": "status"
|
||||
},
|
||||
"programType": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"key_localized": "Program type"
|
||||
},
|
||||
"programPhase": {
|
||||
"value_raw": 0,
|
||||
"value_localized": "",
|
||||
"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": true,
|
||||
"mobileStart": false
|
||||
},
|
||||
"ambientLight": null,
|
||||
"light": null,
|
||||
"elapsedTime": [0, 55],
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
@@ -2723,7 +2723,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[platforms0][sensor.oven_program-entry]
|
||||
@@ -3729,7 +3729,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states[platforms0][sensor.washing_machine_energy_consumption-entry]
|
||||
@@ -5875,7 +5875,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_states_api_push[platforms0][sensor.washing_machine_energy_consumption-entry]
|
||||
@@ -6771,7 +6771,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_vacuum_sensor_states[platforms0-vacuum_device.json][sensor.robot_vacuum_cleaner_program-entry]
|
||||
|
@@ -239,7 +239,6 @@ async def test_temperature_sensor_registry_lookup(
|
||||
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity_id).state == "unknown"
|
||||
|
||||
|
||||
@@ -286,3 +285,306 @@ async def test_coffee_system_sensor_states(
|
||||
"""Test coffee system sensor state."""
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, setup_platform.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_device_file", ["laundry.json"])
|
||||
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
|
||||
async def test_laundry_wash_scenario(
|
||||
hass: HomeAssistant,
|
||||
mock_miele_client: MagicMock,
|
||||
setup_platform: None,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_fixture: MieleDevices,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Parametrized test for verifying time sensors for wahsing machine devices when API glitches at program end."""
|
||||
|
||||
step = 0
|
||||
|
||||
# Initial state when the washing machine is off
|
||||
check_sensor_state(hass, "sensor.washing_machine", "off", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program", "no_program", step)
|
||||
check_sensor_state(
|
||||
hass, "sensor.washing_machine_program_phase", "not_running", step
|
||||
)
|
||||
check_sensor_state(
|
||||
hass, "sensor.washing_machine_target_temperature", "unknown", step
|
||||
)
|
||||
check_sensor_state(hass, "sensor.washing_machine_spin_speed", "unknown", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_remaining_time", "0", step)
|
||||
# OFF -> elapsed forced to unknown (some devices continue reporting last value of last cycle)
|
||||
check_sensor_state(hass, "sensor.washing_machine_elapsed_time", "unknown", step)
|
||||
|
||||
# Simulate program started
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 5
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = "In use"
|
||||
device_fixture["DummyWasher"]["state"]["ProgramID"]["value_raw"] = 3
|
||||
device_fixture["DummyWasher"]["state"]["ProgramID"]["value_localized"] = (
|
||||
"Minimum iron"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 260
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Main wash"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 1
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 45
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0]["value_raw"] = 3000
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0][
|
||||
"value_localized"
|
||||
] = 30.0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 12
|
||||
device_fixture["DummyWasher"]["state"]["spinningSpeed"]["value_raw"] = 1200
|
||||
device_fixture["DummyWasher"]["state"]["spinningSpeed"]["value_localized"] = "1200"
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.washing_machine", "in_use", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program", "minimum_iron", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program_phase", "main_wash", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_target_temperature", "30.0", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_spin_speed", "1200", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_remaining_time", "105", step)
|
||||
# IN_USE -> elapsed time from API (normal case)
|
||||
check_sensor_state(hass, "sensor.washing_machine_elapsed_time", "12", step)
|
||||
|
||||
# Simulate rinse hold phase
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 11
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = "Rinse hold"
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 262
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Rinse hold"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 8
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 1
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 49
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.washing_machine", "rinse_hold", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program", "minimum_iron", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program_phase", "rinse_hold", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_target_temperature", "30.0", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_spin_speed", "1200", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_remaining_time", "8", step)
|
||||
# RINSE HOLD -> elapsed time from API (normal case)
|
||||
check_sensor_state(hass, "sensor.washing_machine_elapsed_time", "109", step)
|
||||
|
||||
# Simulate program ended
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 7
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = "Finished"
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 267
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Anti-crease"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 0
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.washing_machine", "program_ended", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program", "minimum_iron", step)
|
||||
check_sensor_state(
|
||||
hass, "sensor.washing_machine_program_phase", "anti_crease", step
|
||||
)
|
||||
check_sensor_state(hass, "sensor.washing_machine_target_temperature", "30.0", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_spin_speed", "1200", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_remaining_time", "0", step)
|
||||
# PROGRAM_ENDED -> elapsed time kept from last program (some devices immediately go to 0)
|
||||
check_sensor_state(hass, "sensor.washing_machine_elapsed_time", "109", step)
|
||||
|
||||
# Simulate when door is opened after program ended
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 3
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = (
|
||||
"Programme selected"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 256
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = ""
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0]["value_raw"] = 4000
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0][
|
||||
"value_localized"
|
||||
] = 40.0
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 1
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 59
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 0
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.washing_machine", "programmed", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_program", "minimum_iron", step)
|
||||
check_sensor_state(
|
||||
hass, "sensor.washing_machine_program_phase", "not_running", step
|
||||
)
|
||||
check_sensor_state(hass, "sensor.washing_machine_target_temperature", "40.0", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_spin_speed", "1200", step)
|
||||
check_sensor_state(hass, "sensor.washing_machine_remaining_time", "119", step)
|
||||
# PROGRAMMED -> elapsed time from API (normal case)
|
||||
check_sensor_state(hass, "sensor.washing_machine_elapsed_time", "0", step)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_device_file", ["laundry.json"])
|
||||
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
|
||||
async def test_laundry_dry_scenario(
|
||||
hass: HomeAssistant,
|
||||
mock_miele_client: MagicMock,
|
||||
setup_platform: None,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_fixture: MieleDevices,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Parametrized test for verifying time sensors for tumble dryer devices when API reports time value from last cycle, when device is off."""
|
||||
|
||||
step = 0
|
||||
|
||||
# Initial state when the washing machine is off
|
||||
check_sensor_state(hass, "sensor.tumble_dryer", "off", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program", "no_program", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program_phase", "not_running", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_drying_step", "unknown", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_remaining_time", "0", step)
|
||||
# OFF -> elapsed forced to unknown (some devices continue reporting last value of last cycle)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_elapsed_time", "unknown", step)
|
||||
|
||||
# Simulate program started
|
||||
device_fixture["DummyDryer"]["state"]["status"]["value_raw"] = 5
|
||||
device_fixture["DummyDryer"]["state"]["status"]["value_localized"] = "In use"
|
||||
device_fixture["DummyDryer"]["state"]["ProgramID"]["value_raw"] = 3
|
||||
device_fixture["DummyDryer"]["state"]["ProgramID"]["value_localized"] = (
|
||||
"Minimum iron"
|
||||
)
|
||||
device_fixture["DummyDryer"]["state"]["programPhase"]["value_raw"] = 514
|
||||
device_fixture["DummyDryer"]["state"]["programPhase"]["value_localized"] = "Drying"
|
||||
device_fixture["DummyDryer"]["state"]["remainingTime"][0] = 0
|
||||
device_fixture["DummyDryer"]["state"]["remainingTime"][1] = 49
|
||||
device_fixture["DummyDryer"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyDryer"]["state"]["elapsedTime"][1] = 20
|
||||
device_fixture["DummyDryer"]["state"]["dryingStep"]["value_raw"] = 2
|
||||
device_fixture["DummyDryer"]["state"]["dryingStep"]["value_localized"] = "Normal"
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.tumble_dryer", "in_use", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program", "minimum_iron", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program_phase", "drying", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_drying_step", "normal", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_remaining_time", "49", step)
|
||||
# IN_USE -> elapsed time from API (normal case)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_elapsed_time", "20", step)
|
||||
|
||||
# Simulate program end
|
||||
device_fixture["DummyDryer"]["state"]["status"]["value_raw"] = 7
|
||||
device_fixture["DummyDryer"]["state"]["status"]["value_localized"] = "Finished"
|
||||
device_fixture["DummyDryer"]["state"]["programPhase"]["value_raw"] = 522
|
||||
device_fixture["DummyDryer"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Finished"
|
||||
)
|
||||
device_fixture["DummyDryer"]["state"]["remainingTime"][0] = 0
|
||||
device_fixture["DummyDryer"]["state"]["remainingTime"][1] = 0
|
||||
device_fixture["DummyDryer"]["state"]["elapsedTime"][0] = 1
|
||||
device_fixture["DummyDryer"]["state"]["elapsedTime"][1] = 18
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
step += 1
|
||||
|
||||
check_sensor_state(hass, "sensor.tumble_dryer", "program_ended", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program", "minimum_iron", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_program_phase", "finished", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_drying_step", "normal", step)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_remaining_time", "0", step)
|
||||
# PROGRAM_ENDED -> elapsed time kept from last program (some devices immediately go to 0)
|
||||
check_sensor_state(hass, "sensor.tumble_dryer_elapsed_time", "20", step)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_device_file", ["laundry.json"])
|
||||
@pytest.mark.parametrize("platforms", [(SENSOR_DOMAIN,)])
|
||||
async def test_elapsed_time_sensor_restored(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_miele_client: MagicMock,
|
||||
setup_platform: None,
|
||||
device_fixture: MieleDevices,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test that elapsed time returns the restored value when program ended."""
|
||||
|
||||
entity_id = "sensor.washing_machine_elapsed_time"
|
||||
|
||||
# Simulate program started
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 5
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = "In use"
|
||||
device_fixture["DummyWasher"]["state"]["ProgramID"]["value_raw"] = 3
|
||||
device_fixture["DummyWasher"]["state"]["ProgramID"]["value_localized"] = (
|
||||
"Minimum iron"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 260
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Main wash"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 1
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 45
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0]["value_raw"] = 3000
|
||||
device_fixture["DummyWasher"]["state"]["targetTemperature"][0][
|
||||
"value_localized"
|
||||
] = 30.0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 12
|
||||
device_fixture["DummyWasher"]["state"]["spinningSpeed"]["value_raw"] = 1200
|
||||
device_fixture["DummyWasher"]["state"]["spinningSpeed"]["value_localized"] = "1200"
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity_id).state == "12"
|
||||
|
||||
# Simulate program ended
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_raw"] = 7
|
||||
device_fixture["DummyWasher"]["state"]["status"]["value_localized"] = "Finished"
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_raw"] = 267
|
||||
device_fixture["DummyWasher"]["state"]["programPhase"]["value_localized"] = (
|
||||
"Anti-crease"
|
||||
)
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["remainingTime"][1] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][0] = 0
|
||||
device_fixture["DummyWasher"]["state"]["elapsedTime"][1] = 0
|
||||
|
||||
freezer.tick(timedelta(seconds=130))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# unload config entry and reload to make sure that the state is restored
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get(entity_id).state == "unavailable"
|
||||
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# check that elapsed time is the one restored and not the value reported by API (0)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "12"
|
||||
|
Reference in New Issue
Block a user