Add Midea Water Heater platform (#180693)

This commit is contained in:
Lucas Mindêllo de Andrade
2026-09-10 20:19:49 +02:00
committed by GitHub
parent 3ea011d36b
commit ed25ae3642
5 changed files with 1328 additions and 0 deletions
@@ -40,6 +40,7 @@ _PLATFORMS: list[Platform] = [
Platform.SENSOR,
Platform.SWITCH,
Platform.TIME,
Platform.WATER_HEATER,
]
@@ -721,6 +721,14 @@
"timing_regeneration": {
"name": "Regeneration time"
}
},
"water_heater": {
"water_heater_bathing": {
"name": "Bathing water heater"
},
"water_heater_heating": {
"name": "Heating water heater"
}
}
},
"exceptions": {
@@ -0,0 +1,430 @@
"""Water Heater platform for Midea."""
from dataclasses import dataclass
from typing import Any, ClassVar, cast, override
from midealocal.device import DeviceType
from midealocal.devices.c3 import DeviceAttributes as C3Attributes, MideaC3Device
from midealocal.devices.cd import DeviceAttributes as CDAttributes, MideaCDDevice
from midealocal.devices.e2 import DeviceAttributes as E2Attributes, MideaE2Device
from midealocal.devices.e3 import DeviceAttributes as E3Attributes, MideaE3Device
from midealocal.devices.e6 import DeviceAttributes as E6Attributes, MideaE6Device
from homeassistant.components.water_heater import (
WaterHeaterEntity,
WaterHeaterEntityDescription,
WaterHeaterEntityFeature,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
PRECISION_HALVES,
PRECISION_WHOLE,
STATE_OFF,
STATE_ON,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .entity import MideaConfigEntry, MideaEntity, midea_api_call
PARALLEL_UPDATES = 0
@dataclass(kw_only=True, frozen=True)
class MideaWaterHeaterEntityDescription(WaterHeaterEntityDescription):
"""Description for a Midea water heater entity."""
models: list[DeviceType]
zone: int = 0
WATER_HEATERS: list[MideaWaterHeaterEntityDescription] = [
MideaWaterHeaterEntityDescription(
key="water_heater",
translation_key="water_heater",
models=[
DeviceType.C3,
DeviceType.CD,
DeviceType.E2,
DeviceType.E3,
],
),
MideaWaterHeaterEntityDescription(
key="water_heater_heating",
translation_key="water_heater_heating",
models=[
DeviceType.E6,
],
),
MideaWaterHeaterEntityDescription(
key="water_heater_bathing",
translation_key="water_heater_bathing",
models=[
DeviceType.E6,
],
zone=1,
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MideaConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up water heater entries."""
device = config_entry.runtime_data
entities: list[MideaWaterHeater] = []
for description in WATER_HEATERS:
if device.device_type not in description.models:
continue
if device.device_type == DeviceType.E2:
entities.append(
MideaE2WaterHeater(cast("MideaE2Device", device), description)
)
elif device.device_type == DeviceType.E3:
entities.append(
MideaE3WaterHeater(cast("MideaE3Device", device), description)
)
elif device.device_type == DeviceType.E6:
entities.append(
MideaE6WaterHeater(cast("MideaE6Device", device), description)
)
elif device.device_type == DeviceType.C3:
entities.append(
MideaC3WaterHeater(cast("MideaC3Device", device), description)
)
elif device.device_type == DeviceType.CD:
entities.append(
MideaCDWaterHeater(cast("MideaCDDevice", device), description)
)
async_add_entities(entities)
type MideaWaterHeaterDevice = (
MideaE2Device | MideaE3Device | MideaC3Device | MideaE6Device | MideaCDDevice
)
class MideaWaterHeater(MideaEntity, WaterHeaterEntity):
"""Midea Water Heater Entries Base Class."""
_device: MideaWaterHeaterDevice
def __init__(
self,
device: MideaWaterHeaterDevice,
description: MideaWaterHeaterEntityDescription,
) -> None:
"""Midea Water Heater entity init."""
super().__init__(device, description)
self._operations: list[str] = []
self._attr_supported_features = (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
)
self._attr_precision = float(PRECISION_WHOLE)
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
@property
@override
def target_temperature_step(self) -> float | None:
"""Return the supported target temperature step."""
return cast("float | None", getattr(self._device, "temperature_step", None))
@property
@override
def current_operation(self) -> str | None:
"""Midea Water Heater current operation."""
return STATE_ON if self._device.get_attribute("power") else STATE_OFF
@property
@override
def current_temperature(self) -> float:
"""Midea Water Heater current temperature."""
return cast("float", self._device.get_attribute("current_temperature"))
@property
@override
def target_temperature(self) -> float:
"""Midea Water Heater target temperature."""
return cast("float", self._device.get_attribute("target_temperature"))
@override
def set_temperature(self, **kwargs: Any) -> None:
"""Midea Water Heater set temperature."""
if ATTR_TEMPERATURE not in kwargs:
return # pragma: no cover
with midea_api_call():
temperature = float(kwargs[ATTR_TEMPERATURE])
self._device.set_attribute("target_temperature", temperature)
@override
def turn_on(self, **kwargs: Any) -> None:
"""Midea Water Heater turn on."""
with midea_api_call():
self._device.set_attribute(attr="power", value=True)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Midea Water Heater turn off."""
with midea_api_call():
self._device.set_attribute(attr="power", value=False)
class MideaE2WaterHeater(MideaWaterHeater):
"""Midea E2 Water Heater Entries."""
_device: MideaE2Device
@property
@override
def min_temp(self) -> float:
"""Midea E2 Water Heater min temperature."""
return cast("float", self._device.get_attribute(E2Attributes.temperature_min))
@property
@override
def max_temp(self) -> float:
"""Midea E2 Water Heater max temperature."""
return cast("float", self._device.get_attribute(E2Attributes.temperature_max))
class MideaE3WaterHeater(MideaWaterHeater):
"""Midea E3 Water Heater Entries."""
_device: MideaE3Device
@property
@override
def min_temp(self) -> float:
"""Midea E3 Water Heater min temperature."""
return cast("float", self._device.get_attribute(E3Attributes.temperature_min))
@property
@override
def max_temp(self) -> float:
"""Midea E3 Water Heater max temperature."""
return cast("float", self._device.get_attribute(E3Attributes.temperature_max))
@property
@override
def precision(self) -> float:
"""Midea E3 Water Heater precision."""
return float(
PRECISION_HALVES if self._device.precision_halves else PRECISION_WHOLE,
)
class MideaC3WaterHeater(MideaWaterHeater):
"""Midea C3 Water Heater Entries."""
_device: MideaC3Device
@property
@override
def current_operation(self) -> str:
"""Midea C3 Water Heater current operation."""
return (
STATE_ON
if self._device.get_attribute(C3Attributes.dhw_power)
else STATE_OFF
)
@property
@override
def current_temperature(self) -> float:
"""Midea C3 Water Heater current temperature."""
return cast(
"float",
self._device.get_attribute(C3Attributes.tank_actual_temperature),
)
@property
@override
def target_temperature(self) -> float:
"""Midea C3 Water Heater target temperature."""
return cast("float", self._device.get_attribute(C3Attributes.dhw_target_temp))
@override
def set_temperature(self, **kwargs: Any) -> None:
"""Midea C3 Water Heater set temperature."""
if ATTR_TEMPERATURE not in kwargs:
return # pragma: no cover
with midea_api_call():
temperature = float(kwargs[ATTR_TEMPERATURE])
self._device.set_attribute(C3Attributes.dhw_target_temp, temperature)
@property
@override
def min_temp(self) -> float:
"""Midea C3 Water Heater min temperature."""
return cast("float", self._device.get_attribute(C3Attributes.dhw_temp_min))
@property
@override
def max_temp(self) -> float:
"""Midea C3 Water Heater max temperature."""
return cast("float", self._device.get_attribute(C3Attributes.dhw_temp_max))
@override
def turn_on(self, **kwargs: Any) -> None:
"""Midea C3 Water Heater turn on."""
with midea_api_call():
self._device.set_attribute(attr=C3Attributes.dhw_power, value=True)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Midea C3 Water Heater turn off."""
with midea_api_call():
self._device.set_attribute(attr=C3Attributes.dhw_power, value=False)
class MideaE6WaterHeater(MideaWaterHeater):
"""Midea E6 Water Heater Entries."""
_device: MideaE6Device
entity_description: MideaWaterHeaterEntityDescription
_powers: ClassVar[list[E6Attributes]] = [
E6Attributes.heating_power,
E6Attributes.main_power,
]
_current_temperatures: ClassVar[list[E6Attributes]] = [
E6Attributes.heating_leaving_temperature,
E6Attributes.bathing_leaving_temperature,
]
_target_temperatures: ClassVar[list[E6Attributes]] = [
E6Attributes.heating_temperature,
E6Attributes.bathing_temperature,
]
def __init__(
self, device: MideaE6Device, description: MideaWaterHeaterEntityDescription
) -> None:
"""Midea E6 Water Heater entity init."""
super().__init__(device, description)
self._power_attr = MideaE6WaterHeater._powers[description.zone]
self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[
description.zone
]
self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[
description.zone
]
self._attr_supported_features = (
WaterHeaterEntityFeature.TARGET_TEMPERATURE
| WaterHeaterEntityFeature.ON_OFF
)
if description.zone == 0:
self._attr_supported_features |= WaterHeaterEntityFeature.AWAY_MODE
@property
@override
def current_operation(self) -> str:
"""Midea E6 Water Heater current operation."""
if self.entity_description.zone == 0:
return (
STATE_ON
if self._device.get_attribute(E6Attributes.main_power)
and self._device.get_attribute(E6Attributes.heating_power)
else STATE_OFF
)
return (
STATE_ON
if self._device.get_attribute(E6Attributes.main_power)
else STATE_OFF
)
@property
@override
def current_temperature(self) -> float:
"""Midea E6 Water Heater current temperature."""
return cast("float", self._device.get_attribute(self._current_temperature_attr))
@property
@override
def target_temperature(self) -> float:
"""Midea E6 Water Heater target temperature."""
return cast("float", self._device.get_attribute(self._target_temperature_attr))
@override
def set_temperature(self, **kwargs: Any) -> None:
"""Midea E6 Water Heater set temperature."""
if ATTR_TEMPERATURE not in kwargs:
return # pragma: no cover
with midea_api_call():
temperature = float(kwargs[ATTR_TEMPERATURE])
self._device.set_attribute(self._target_temperature_attr, temperature)
@property
@override
def min_temp(self) -> float:
"""Midea E6 Water Heater min temperature."""
min_temperature = cast(
"list[float]",
self._device.get_attribute(E6Attributes.temperature_min),
)
return min_temperature[self.entity_description.zone]
@property
@override
def max_temp(self) -> float:
"""Midea E6 Water Heater max temperature."""
max_temperature = cast(
"list[float]",
self._device.get_attribute(E6Attributes.temperature_max),
)
return max_temperature[self.entity_description.zone]
@override
def turn_on(self, **kwargs: Any) -> None:
"""Midea E6 Water Heater turn on."""
with midea_api_call():
self._device.set_attribute(attr=self._power_attr, value=True)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Midea E6 Water Heater turn off."""
with midea_api_call():
self._device.set_attribute(attr=self._power_attr, value=False)
@property
@override
def is_away_mode_on(self) -> bool:
"""Return whether E6 away mode is active."""
return self._device.get_attribute(E6Attributes.heating_modes) == "out"
@override
def turn_away_mode_on(self) -> None:
"""Midea Water Heater turn away mode on."""
with midea_api_call():
self._device.set_attribute(attr=E6Attributes.heating_modes, value="out")
@override
def turn_away_mode_off(self) -> None:
"""Midea Water Heater turn away mode off."""
with midea_api_call():
self._device.set_attribute(
attr=E6Attributes.heating_modes, value=self._device.preset_modes[0]
)
class MideaCDWaterHeater(MideaWaterHeater):
"""Midea CD Water Heater Entries."""
_device: MideaCDDevice
@property
@override
def min_temp(self) -> float:
"""Midea CD Water Heater min temperature."""
return cast("float", self._device.get_attribute(CDAttributes.min_temperature))
@property
@override
def max_temp(self) -> float:
"""Midea CD Water Heater max temperature."""
return cast("float", self._device.get_attribute(CDAttributes.max_temperature))
@@ -0,0 +1,374 @@
# serializer version: 1
# name: test_midea_water_heater_default_state[c3][water_heater.heat_pump_wi_fi_controller-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 60,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 20,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.heat_pump_wi_fi_controller',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 9>,
'translation_key': 'water_heater',
'unique_id': '12345678_water_heater',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[c3][water_heater.heat_pump_wi_fi_controller-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: None,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Heat Pump Wi-Fi Controller',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 60,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 20,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 9>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 25,
}),
'context': <ANY>,
'entity_id': 'water_heater.heat_pump_wi_fi_controller',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_midea_water_heater_default_state[cd][water_heater.heat_pump_water_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 65,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.heat_pump_water_heater',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 9>,
'translation_key': 'water_heater',
'unique_id': '12345678_water_heater',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[cd][water_heater.heat_pump_water_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 41,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Heat Pump Water Heater',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 65,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 9>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 40,
}),
'context': <ANY>,
'entity_id': 'water_heater.heat_pump_water_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_midea_water_heater_default_state[e2][water_heater.electric_water_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 75,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 30,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.electric_water_heater',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 9>,
'translation_key': 'water_heater',
'unique_id': '12345678_water_heater',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[e2][water_heater.electric_water_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 21,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Electric Water Heater',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 75,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 30,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 9>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 40,
}),
'context': <ANY>,
'entity_id': 'water_heater.electric_water_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_midea_water_heater_default_state[e3][water_heater.gas_water_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 65.0,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35.0,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.gas_water_heater',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 9>,
'translation_key': 'water_heater',
'unique_id': '12345678_water_heater',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[e3][water_heater.gas_water_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 38.0,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Gas Water Heater',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 65.0,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35.0,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 9>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 40.0,
}),
'context': <ANY>,
'entity_id': 'water_heater.gas_water_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_midea_water_heater_default_state[e6][water_heater.gas_boiler_bathing_water_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 60,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.gas_boiler_bathing_water_heater',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Bathing water heater',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bathing water heater',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 9>,
'translation_key': 'water_heater_bathing',
'unique_id': '12345678_water_heater_bathing',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[e6][water_heater.gas_boiler_bathing_water_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: None,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Gas Boiler Bathing water heater',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 60,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 35,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 9>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 40,
}),
'context': <ANY>,
'entity_id': 'water_heater.gas_boiler_bathing_water_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_midea_water_heater_default_state[e6][water_heater.gas_boiler_heating_water_heater-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 80,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 30,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'water_heater',
'entity_category': None,
'entity_id': 'water_heater.gas_boiler_heating_water_heater',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Heating water heater',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Heating water heater',
'platform': 'midea',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <WaterHeaterEntityFeature: 13>,
'translation_key': 'water_heater_heating',
'unique_id': '12345678_water_heater_heating',
'unit_of_measurement': None,
})
# ---
# name: test_midea_water_heater_default_state[e6][water_heater.gas_boiler_heating_water_heater-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<WaterHeaterStateAttribute.AWAY_MODE: 'away_mode'>: 'off',
<WaterHeaterStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: None,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Gas Boiler Heating water heater',
<WaterHeaterCapabilityAttribute.MAX_TEMP: 'max_temp'>: 80,
<WaterHeaterCapabilityAttribute.MIN_TEMP: 'min_temp'>: 30,
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <WaterHeaterEntityFeature: 13>,
<WaterHeaterStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
<WaterHeaterStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
<WaterHeaterCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
<WaterHeaterStateAttribute.TARGET_TEMPERATURE: 'temperature'>: 50,
}),
'context': <ANY>,
'entity_id': 'water_heater.gas_boiler_heating_water_heater',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
+515
View File
@@ -0,0 +1,515 @@
"""Tests for the Midea water heater platform."""
from collections.abc import Callable
from unittest.mock import patch
from midealocal.const import DeviceType
from midealocal.devices.c3 import C3SilentLevel, DeviceAttributes as C3Attributes
from midealocal.devices.cd import DeviceAttributes as CDAttributes
from midealocal.devices.e2 import DeviceAttributes as E2Attributes
from midealocal.devices.e3 import DeviceAttributes as E3Attributes
from midealocal.devices.e6 import DeviceAttributes as E6Attributes
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.water_heater import (
ATTR_AWAY_MODE,
ATTR_TEMPERATURE,
DOMAIN as WATER_HEATER_DOMAIN,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_TEMPERATURE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .conftest import DummyDevice, entity_entries
from .const import TEST_DEVICE_ID
from tests.common import MockConfigEntry, snapshot_platform
def _c3_device() -> DummyDevice:
return DummyDevice(
DeviceType.C3,
attributes={
C3Attributes.zone1_power: False,
C3Attributes.zone2_power: False,
C3Attributes.dhw_power: False,
C3Attributes.zone1_curve: False,
C3Attributes.zone2_curve: False,
C3Attributes.disinfect: False,
C3Attributes.fast_dhw: False,
C3Attributes.zone_temp_type: [False, False],
C3Attributes.zone1_room_temp_mode: False,
C3Attributes.zone2_room_temp_mode: False,
C3Attributes.zone1_water_temp_mode: False,
C3Attributes.zone2_water_temp_mode: False,
C3Attributes.silent_mode: False,
C3Attributes.silent_level: C3SilentLevel.OFF.name.lower(),
C3Attributes.eco_mode: False,
C3Attributes.tbh: False,
C3Attributes.mode: 1,
C3Attributes.mode_auto: 1,
C3Attributes.zone_target_temp: [25.0, 25.0],
C3Attributes.dhw_target_temp: 25.0,
C3Attributes.room_target_temp: 30.0,
C3Attributes.zone_heating_temp_max: [55.0, 55.0],
C3Attributes.zone_heating_temp_min: [25.0, 25.0],
C3Attributes.zone_cooling_temp_max: [25.0, 25.0],
C3Attributes.zone_cooling_temp_min: [5.0, 5.0],
C3Attributes.room_temp_max: 60.0,
C3Attributes.room_temp_min: 34.0,
C3Attributes.dhw_temp_max: 60.0,
C3Attributes.dhw_temp_min: 20.0,
C3Attributes.tank_actual_temperature: None,
C3Attributes.target_temperature: [25.0, 25.0],
C3Attributes.temperature_max: [0.0, 0.0],
C3Attributes.temperature_min: [0.0, 0.0],
C3Attributes.total_energy_consumption: None,
C3Attributes.status_heating: None,
C3Attributes.status_dhw: None,
C3Attributes.status_tbh: None,
C3Attributes.status_ibh: None,
C3Attributes.total_produced_energy: None,
C3Attributes.outdoor_temperature: None,
C3Attributes.temp_tw_in: None,
C3Attributes.temp_tw_out: None,
C3Attributes.instant_power0: None,
C3Attributes.error_code: 0,
},
)
def _cd_device() -> DummyDevice:
device = DummyDevice(
DeviceType.CD,
attributes={
CDAttributes.power: True,
CDAttributes.mode: "standard",
CDAttributes.max_temperature: 65.0,
CDAttributes.min_temperature: 35.0,
CDAttributes.target_temperature: 40.0,
CDAttributes.current_temperature: 41.0,
CDAttributes.outdoor_temperature: 25.0,
CDAttributes.condenser_temperature: 23.0,
CDAttributes.compressor_temperature: 30.0,
CDAttributes.compressor_status: None,
CDAttributes.water_level: None,
CDAttributes.fahrenheit: False,
CDAttributes.heat: None,
CDAttributes.dual_heat: None,
CDAttributes.elec_heat: None,
CDAttributes.top_elec_heat: None,
CDAttributes.bottom_elec_heat: None,
CDAttributes.water_pump: None,
CDAttributes.four_way: None,
CDAttributes.back_water: None,
CDAttributes.sterilize: None,
CDAttributes.disinfect: None,
CDAttributes.disinfection_temperature: None,
CDAttributes.top_temperature: None,
CDAttributes.bottom_temperature: None,
CDAttributes.wind: None,
CDAttributes.eco: None,
CDAttributes.smart_grid: None,
CDAttributes.multi_terminal: None,
CDAttributes.mute_effect: None,
CDAttributes.mute_status: None,
CDAttributes.maintenance_reminder: None,
CDAttributes.maintain_warn_tag: None,
CDAttributes.maintain_warn: None,
CDAttributes.error_code: None,
CDAttributes.typeinfo: None,
CDAttributes.vacation_mode: False,
CDAttributes.vacation_days: 0,
CDAttributes.vacation_temperature: None,
CDAttributes.vacation_start_year: None,
CDAttributes.vacation_start_month: None,
CDAttributes.vacation_start_day: None,
CDAttributes.order1_effect: None,
CDAttributes.order2_effect: None,
CDAttributes.auto_sterilize_week: None,
CDAttributes.auto_sterilize_hour: None,
CDAttributes.auto_sterilize_minute: None,
CDAttributes.weekly_effects: None,
CDAttributes.weekly_schedule: None,
CDAttributes.daily_timer_schedule: None,
},
)
device.preset_modes = [
"none",
"energy_save",
"standard",
"dual",
"smart",
]
return device
def _e2_device() -> DummyDevice:
return DummyDevice(
DeviceType.E2,
attributes={
E2Attributes.power: True,
E2Attributes.heating: True,
E2Attributes.keep_warm: True,
E2Attributes.protection: False,
E2Attributes.temperature_max: 75.0,
E2Attributes.temperature_min: 30.0,
E2Attributes.current_temperature: 21.0,
E2Attributes.target_temperature: 40.0,
E2Attributes.whole_tank_heating: True,
E2Attributes.variable_heating: True,
E2Attributes.heating_time_remaining: 50,
E2Attributes.water_consumption: None,
E2Attributes.heating_power: None,
E2Attributes.fast_hot_power: None,
E2Attributes.water_flow: None,
E2Attributes.sterilization: None,
E2Attributes.heat_water_level: None,
E2Attributes.eplus: None,
E2Attributes.memory: None,
E2Attributes.fast_wash: None,
E2Attributes.half_heat: None,
E2Attributes.summer: None,
E2Attributes.winter: None,
E2Attributes.efficient: None,
E2Attributes.night: None,
E2Attributes.screen_off: None,
E2Attributes.sleep: None,
E2Attributes.cloud: None,
E2Attributes.appoint_wash: None,
E2Attributes.now_wash: None,
E2Attributes.smart_sterilize: None,
E2Attributes.sterilize_high_temp: None,
E2Attributes.uv_sterilize: None,
E2Attributes.discharge_status: None,
E2Attributes.top_temp: None,
E2Attributes.bottom_heat: None,
E2Attributes.top_heat: None,
E2Attributes.water_cyclic: None,
E2Attributes.water_system: None,
E2Attributes.in_temperature: None,
E2Attributes.day_water_consumption: None,
E2Attributes.volume: None,
E2Attributes.rate: None,
},
)
def _e3_device() -> DummyDevice:
device = DummyDevice(
DeviceType.E3,
attributes={
E3Attributes.power: False,
E3Attributes.burning_state: False,
E3Attributes.zero_cold_water: False,
E3Attributes.protection: False,
E3Attributes.zero_cold_pulse: False,
E3Attributes.smart_volume: False,
E3Attributes.current_temperature: 38.0,
E3Attributes.target_temperature: 40.0,
E3Attributes.temperature_min: 35.0,
E3Attributes.temperature_max: 65.0,
},
)
device.precision_halves = True
return device
def _e6_device() -> DummyDevice:
device = DummyDevice(
DeviceType.E6,
attributes={
E6Attributes.main_power: False,
E6Attributes.heating_power: True,
E6Attributes.heating_working: None,
E6Attributes.bathing_working: None,
E6Attributes.temperature_min: [30.0, 35.0],
E6Attributes.temperature_max: [80.0, 60.0],
E6Attributes.heating_temperature: 50.0,
E6Attributes.bathing_temperature: 40.0,
E6Attributes.heating_leaving_temperature: None,
E6Attributes.bathing_leaving_temperature: None,
E6Attributes.cold_water_single: None,
E6Attributes.cold_water_dot: None,
E6Attributes.heating_modes: None,
},
)
device.preset_modes = [
"normal",
"out",
"home",
"sleep",
]
return device
async def _assert_service_calls(
hass: HomeAssistant,
entity_id: str,
service: str,
service_data: dict,
expected_calls: list[tuple],
device: DummyDevice,
) -> None:
"""Call a water heater service and assert the fake device recorded the right call."""
device.calls.clear()
await hass.services.async_call(
WATER_HEATER_DOMAIN,
service,
{ATTR_ENTITY_ID: entity_id, **service_data},
blocking=True,
)
assert device.calls == expected_calls
@pytest.mark.parametrize(
"device",
[
pytest.param(
_c3_device(),
id="c3",
),
pytest.param(
_cd_device(),
id="cd",
),
pytest.param(
_e2_device(),
id="e2",
),
pytest.param(
_e3_device(),
id="e3",
),
pytest.param(
_e6_device(),
id="e6",
),
],
)
async def test_midea_water_heater_default_state(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
device: DummyDevice,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
) -> None:
"""Test water heater entities are created and service calls reach the device."""
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.WATER_HEATER]):
await setup_integration(hass, config_entry, device)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
("device", "services", "services_data", "expected_calls", "entity_suffix"),
[
pytest.param(
_c3_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
],
[
[("set_attribute", C3Attributes.dhw_power, False)],
[("set_attribute", C3Attributes.dhw_power, True)],
[("set_attribute", C3Attributes.dhw_target_temp, 50.0)],
],
"water_heater",
id="c3",
),
pytest.param(
_cd_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
],
[
[("set_attribute", CDAttributes.power, False)],
[("set_attribute", CDAttributes.power, True)],
[("set_attribute", CDAttributes.target_temperature, 50.0)],
],
"water_heater",
id="cd",
),
pytest.param(
_e2_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
],
[
[("set_attribute", E2Attributes.power, False)],
[("set_attribute", E2Attributes.power, True)],
[("set_attribute", E2Attributes.target_temperature, 50.0)],
],
"water_heater",
id="e2",
),
pytest.param(
_e3_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
],
[
[("set_attribute", E3Attributes.power, False)],
[("set_attribute", E3Attributes.power, True)],
[("set_attribute", E3Attributes.target_temperature, 50.0)],
],
"water_heater",
id="e3",
),
pytest.param(
_e6_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
SERVICE_SET_AWAY_MODE,
SERVICE_SET_AWAY_MODE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
{ATTR_AWAY_MODE: True},
{ATTR_AWAY_MODE: False},
],
[
[
("set_attribute", E6Attributes.heating_power, False),
],
[
("set_attribute", E6Attributes.heating_power, True),
],
[
("set_attribute", E6Attributes.heating_temperature, 50.0),
],
[
("set_attribute", E6Attributes.heating_modes, "out"),
],
[
("set_attribute", E6Attributes.heating_modes, "normal"),
],
],
"water_heater_heating",
id="e6_heating",
),
pytest.param(
_e6_device(),
[
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_SET_TEMPERATURE,
],
[
{},
{},
{ATTR_TEMPERATURE: 50.0},
],
[
[
("set_attribute", E6Attributes.main_power, False),
],
[
("set_attribute", E6Attributes.main_power, True),
],
[
("set_attribute", E6Attributes.bathing_temperature, 50.0),
],
],
"water_heater_bathing",
id="e6_bathing",
),
],
)
async def test_midea_water_heater_services(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
device: DummyDevice,
services: list[str],
services_data: list[dict],
expected_calls: list[list[tuple]],
entity_suffix: str,
) -> None:
"""Test water heater service calls reach the device."""
config_entry = mock_config_entry(device)
with patch("homeassistant.components.midea._PLATFORMS", [Platform.WATER_HEATER]):
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[
f"{TEST_DEVICE_ID}_{entity_suffix}"
]
for i, service in enumerate(services):
await _assert_service_calls(
hass,
entity_entry.entity_id,
service,
services_data[i],
expected_calls[i],
device,
)
async def test_e2_min_max_temperature_from_device(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test E2 min/max temperature are read from the device attributes."""
device = _e2_device()
config_entry = mock_config_entry(device)
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_water_heater"]
entity = hass.data[WATER_HEATER_DOMAIN].get_entity(entity_entry.entity_id)
assert entity is not None
assert entity.min_temp == 30.0
assert entity.max_temp == 75.0
async def test_e3_min_max_temperature_from_device(
hass: HomeAssistant,
mock_config_entry: Callable[[DummyDevice], MockConfigEntry],
) -> None:
"""Test E3 min/max temperature are read from the device attributes."""
device = _e3_device()
config_entry = mock_config_entry(device)
await setup_integration(hass, config_entry, device)
entity_entry = entity_entries(hass, config_entry)[f"{TEST_DEVICE_ID}_water_heater"]
entity = hass.data[WATER_HEATER_DOMAIN].get_entity(entity_entry.entity_id)
assert entity is not None
assert entity.min_temp == 35.0
assert entity.max_temp == 65.0