mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 17:31:15 -04:00
Add time remaining sensors to Midea (#182435)
This commit is contained in:
@@ -73,6 +73,9 @@
|
||||
},
|
||||
"target_indoor_fan_speed": {
|
||||
"default": "mdi:fan-clock"
|
||||
},
|
||||
"time_remaining": {
|
||||
"default": "mdi:progress-clock"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Midea Sensor entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from typing import cast, override
|
||||
|
||||
from midealocal.const import DeviceType
|
||||
@@ -31,6 +32,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .entity import MideaConfigEntry, MideaEntity
|
||||
|
||||
@@ -469,6 +471,17 @@ SENSOR_ENTITIES: list[MideaSensorEntityDescription] = [
|
||||
"freeze_warm",
|
||||
],
|
||||
),
|
||||
MideaSensorEntityDescription(
|
||||
key="time_remaining",
|
||||
translation_key="time_remaining",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
),
|
||||
MideaSensorEntityDescription(
|
||||
key="heating_time_remaining",
|
||||
translation_key="time_remaining",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
models=[DeviceType.E2],
|
||||
),
|
||||
MideaSensorEntityDescription(
|
||||
key="wash_time",
|
||||
translation_key="wash_time",
|
||||
@@ -710,9 +723,16 @@ class MideaSensor(MideaEntity, SensorEntity):
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> StateType:
|
||||
def native_value(self) -> StateType | datetime:
|
||||
"""Native value of the sensor."""
|
||||
value = self._device.get_attribute(self.entity_description.key)
|
||||
if value == "unknown":
|
||||
return None
|
||||
if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP:
|
||||
if not isinstance(value, (int, float)) or value <= 0:
|
||||
return None
|
||||
# round to the closest minute
|
||||
return (dt_util.utcnow() + timedelta(seconds=30)).replace(
|
||||
second=0, microsecond=0
|
||||
) + timedelta(minutes=value)
|
||||
return cast("StateType", value)
|
||||
|
||||
@@ -629,6 +629,9 @@
|
||||
"temperature_raw": {
|
||||
"name": "Raw temperature"
|
||||
},
|
||||
"time_remaining": {
|
||||
"name": "Time remaining"
|
||||
},
|
||||
"top_compartment_temperature": {
|
||||
"name": "Top compartment temperature"
|
||||
},
|
||||
|
||||
@@ -762,6 +762,57 @@
|
||||
'state': '21.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[e8][sensor.electric_slow_cooker_time_remaining-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'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.electric_slow_cooker_time_remaining',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Time remaining',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Time remaining',
|
||||
'platform': 'midea',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'time_remaining',
|
||||
'unique_id': '12345678_time_remaining',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[e8][sensor.electric_slow_cooker_time_remaining-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Electric Slow Cooker Time remaining',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.electric_slow_cooker_time_remaining',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2026-01-09T13:00:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ea][sensor.electric_rice_cooker_bottom_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1126,6 +1177,57 @@
|
||||
'state': 'heat_rice',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ea][sensor.electric_rice_cooker_time_remaining-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'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.electric_rice_cooker_time_remaining',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Time remaining',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Time remaining',
|
||||
'platform': 'midea',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'time_remaining',
|
||||
'unique_id': '12345678_time_remaining',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ea][sensor.electric_rice_cooker_time_remaining-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Electric Rice Cooker Time remaining',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.electric_rice_cooker_time_remaining',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2026-01-09T12:05:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ea][sensor.electric_rice_cooker_top_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1492,6 +1594,57 @@
|
||||
'state': 'diy',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ec][sensor.electric_pressure_cooker_time_remaining-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'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.electric_pressure_cooker_time_remaining',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Time remaining',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Time remaining',
|
||||
'platform': 'midea',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'time_remaining',
|
||||
'unique_id': '12345678_time_remaining',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ec][sensor.electric_pressure_cooker_time_remaining-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'timestamp',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Electric Pressure Cooker Time remaining',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.electric_pressure_cooker_time_remaining',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2026-01-09T12:05:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ec][sensor.electric_pressure_cooker_top_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from midealocal.const import DeviceType
|
||||
from midealocal.devices.ac import DeviceAttributes as ACAttributes
|
||||
from midealocal.devices.c3 import DeviceAttributes as C3Attributes
|
||||
@@ -72,7 +73,7 @@ from tests.common import MockConfigEntry, snapshot_platform
|
||||
DeviceType.E8,
|
||||
attributes={
|
||||
E8Attributes.status: 1,
|
||||
E8Attributes.time_remaining: 3600,
|
||||
E8Attributes.time_remaining: 60,
|
||||
E8Attributes.keep_warm_remaining: 1800,
|
||||
E8Attributes.working_time: 7200,
|
||||
E8Attributes.target_temperature: 22.0,
|
||||
@@ -179,12 +180,14 @@ from tests.common import MockConfigEntry, snapshot_platform
|
||||
)
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
device: DummyDevice,
|
||||
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test sensor entities are created."""
|
||||
freezer.move_to("2026-01-09 12:00:00+00:00")
|
||||
config_entry = mock_config_entry(device)
|
||||
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, config_entry, device)
|
||||
@@ -249,6 +252,41 @@ async def test_sensor_state_update(
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
async def test_time_remaining_sensor_as_timestamp(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
set_device_attribute: SetDeviceAttribute,
|
||||
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
|
||||
) -> None:
|
||||
"""Test time_remaining is reported as an absolute timestamp in minutes."""
|
||||
freezer.move_to("2026-01-09 12:00:00+00:00")
|
||||
device = DummyDevice(
|
||||
DeviceType.E8,
|
||||
attributes={E8Attributes.time_remaining: 90},
|
||||
)
|
||||
config_entry = mock_config_entry(device)
|
||||
with patch("homeassistant.components.midea._PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, config_entry, device)
|
||||
|
||||
entity_entry = entity_entries(hass, config_entry)[
|
||||
f"{TEST_DEVICE_ID}_time_remaining"
|
||||
]
|
||||
|
||||
state = hass.states.get(entity_entry.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "2026-01-09T13:30:00+00:00"
|
||||
|
||||
await set_device_attribute(device, E8Attributes.time_remaining, 0)
|
||||
state = hass.states.get(entity_entry.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "unknown"
|
||||
|
||||
await set_device_attribute(device, E8Attributes.time_remaining, None)
|
||||
state = hass.states.get(entity_entry.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("model", "expected_options"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user