Add time remaining sensors to Midea (#182435)

This commit is contained in:
Simone Chemelli
2026-09-17 22:08:57 +02:00
committed by GitHub
parent c6c9edf624
commit 6c2d4140cc
5 changed files with 219 additions and 2 deletions
@@ -73,6 +73,9 @@
},
"target_indoor_fan_speed": {
"default": "mdi:fan-clock"
},
"time_remaining": {
"default": "mdi:progress-clock"
}
},
"switch": {
+21 -1
View File
@@ -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([
+39 -1
View File
@@ -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"),
[