Add HVAC action to MELCloud Home ATW zones (#183771)

This commit is contained in:
Erwin Douna
2026-09-30 17:05:01 +02:00
committed by GitHub
parent fabc92e5cf
commit 1cdf874d07
3 changed files with 66 additions and 0 deletions
@@ -7,6 +7,7 @@ from aiomelcloudhome import (
ATAOperationMode,
ATAVaneHorizontal,
ATAVaneVertical,
ATWOperationMode,
ATWZoneMode,
)
@@ -14,6 +15,7 @@ from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityDescription,
ClimateEntityFeature,
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
@@ -92,6 +94,15 @@ HVAC_MODE_TO_ATW_ZONE_MODE: dict[HVACMode, ATWZoneMode] = {
HVACMode.COOL: ATWZoneMode.COOL_ROOM_TEMPERATURE,
}
# The unit heats either the tank or the zones, so heating the tank idles the zones
ATW_OPERATION_TO_HVAC_ACTION: dict[ATWOperationMode, HVACAction] = {
ATWOperationMode.STOP: HVACAction.IDLE,
ATWOperationMode.HOT_WATER: HVACAction.IDLE,
ATWOperationMode.HEAT: HVACAction.HEATING,
ATWOperationMode.HEAT_ZONES: HVACAction.HEATING,
ATWOperationMode.COOL: HVACAction.COOLING,
}
async def async_setup_entry(
hass: HomeAssistant,
@@ -441,6 +452,16 @@ class ATWZoneClimateEntity(MelCloudHomeATWZoneEntity, ClimateEntity):
else HVACMode.OFF
)
@property
@override
def hvac_action(self) -> HVACAction | None:
"""Return what the unit is doing for this zone."""
if self.hvac_mode == HVACMode.OFF:
return HVACAction.OFF
if self.unit.operation_mode is None:
return None
return ATW_OPERATION_TO_HVAC_ACTION[self.unit.operation_mode]
@override
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the HVAC mode."""
@@ -49,6 +49,7 @@
'attributes': ReadOnlyDict({
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 20.0,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Heat Pump Zone 1',
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
@@ -117,6 +118,7 @@
'attributes': ReadOnlyDict({
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 21.0,
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Heat Pump Zone 2',
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
<HVACMode.OFF: 'off'>,
<HVACMode.HEAT: 'heat'>,
@@ -19,6 +19,7 @@ import pytest
from homeassistant.components.climate import (
ATTR_FAN_MODE,
ATTR_HVAC_ACTION,
ATTR_HVAC_MODE,
ATTR_SWING_HORIZONTAL_MODE,
ATTR_SWING_MODE,
@@ -28,6 +29,7 @@ from homeassistant.components.climate import (
SERVICE_SET_SWING_HORIZONTAL_MODE,
SERVICE_SET_SWING_MODE,
SERVICE_SET_TEMPERATURE,
HVACAction,
HVACMode,
)
from homeassistant.components.melcloud_home.const import DOMAIN
@@ -527,6 +529,47 @@ async def test_atw_zone_temperature_range(
assert state2.attributes["max_temp"] == 28.0
@pytest.mark.parametrize(
("overrides", "expected_action"),
[
pytest.param({"OperationMode": "Stop"}, HVACAction.IDLE, id="idle"),
pytest.param({"OperationMode": "HotWater"}, HVACAction.IDLE, id="hot_water"),
pytest.param({"OperationMode": "Heat"}, HVACAction.HEATING, id="heat"),
pytest.param(
{"OperationMode": "HeatZones"}, HVACAction.HEATING, id="heat_zones"
),
pytest.param({"OperationMode": "Cool"}, HVACAction.COOLING, id="cool"),
pytest.param({"OperationMode": "Unknown"}, None, id="unknown"),
pytest.param(
{"OperationMode": "Heat", "Power": "False"}, HVACAction.OFF, id="off"
),
],
)
async def test_atw_zone_hvac_action(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_melcloud_client: AsyncMock,
overrides: dict[str, str],
expected_action: HVACAction | None,
) -> None:
"""Test the ATW zone HVAC action follows the unit's operation mode."""
context: dict[str, Any] = await async_load_json_object_fixture(
hass, "context.json", DOMAIN
)
settings = {
setting["name"]: setting
for setting in context["buildings"][0]["airToWaterUnits"][0]["settings"]
}
for name, value in overrides.items():
settings[name]["value"] = value
mock_melcloud_client.get_context.return_value = UserContext.model_validate(context)
await setup_integration(hass, mock_config_entry)
assert (state := hass.states.get(ATW_ZONE1_ENTITY_ID))
assert state.attributes.get(ATTR_HVAC_ACTION) == expected_action
@pytest.mark.parametrize(
("entity_id", "method", "service", "service_data"),
[