mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Fix evohome HVAC modes for VisionPro Wifi systems (#129161)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
db4278fb9d
commit
a36b350954
@ -66,8 +66,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PRESET_RESET = "Reset" # reset all child zones to EVO_FOLLOW
|
||||
PRESET_CUSTOM = "Custom"
|
||||
|
||||
HA_HVAC_TO_TCS = {HVACMode.OFF: EVO_HEATOFF, HVACMode.HEAT: EVO_AUTO}
|
||||
|
||||
TCS_PRESET_TO_HA = {
|
||||
EVO_AWAY: PRESET_AWAY,
|
||||
EVO_CUSTOM: PRESET_CUSTOM,
|
||||
@ -150,14 +148,10 @@ async def async_setup_platform(
|
||||
class EvoClimateEntity(EvoDevice, ClimateEntity):
|
||||
"""Base for any evohome-compatible climate entity (controller, zone)."""
|
||||
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
"""Return a list of available hvac operation modes."""
|
||||
return list(HA_HVAC_TO_TCS)
|
||||
|
||||
|
||||
class EvoZone(EvoChild, EvoClimateEntity):
|
||||
"""Base for any evohome-compatible heating zone."""
|
||||
@ -365,9 +359,9 @@ class EvoController(EvoClimateEntity):
|
||||
self._attr_unique_id = evo_device.systemId
|
||||
self._attr_name = evo_device.location.name
|
||||
|
||||
modes = [m[SZ_SYSTEM_MODE] for m in evo_broker.tcs.allowedSystemModes]
|
||||
self._evo_modes = [m[SZ_SYSTEM_MODE] for m in evo_device.allowedSystemModes]
|
||||
self._attr_preset_modes = [
|
||||
TCS_PRESET_TO_HA[m] for m in modes if m in list(TCS_PRESET_TO_HA)
|
||||
TCS_PRESET_TO_HA[m] for m in self._evo_modes if m in list(TCS_PRESET_TO_HA)
|
||||
]
|
||||
if self._attr_preset_modes:
|
||||
self._attr_supported_features = ClimateEntityFeature.PRESET_MODE
|
||||
@ -401,14 +395,14 @@ class EvoController(EvoClimateEntity):
|
||||
"""Set a Controller to any of its native EVO_* operating modes."""
|
||||
until = dt_util.as_utc(until) if until else None
|
||||
await self._evo_broker.call_client_api(
|
||||
self._evo_tcs.set_mode(mode, until=until) # type: ignore[arg-type]
|
||||
self._evo_device.set_mode(mode, until=until) # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
@property
|
||||
def hvac_mode(self) -> HVACMode:
|
||||
"""Return the current operating mode of a Controller."""
|
||||
tcs_mode = self._evo_tcs.system_mode
|
||||
return HVACMode.OFF if tcs_mode == EVO_HEATOFF else HVACMode.HEAT
|
||||
evo_mode = self._evo_device.system_mode
|
||||
return HVACMode.OFF if evo_mode in (EVO_HEATOFF, "Off") else HVACMode.HEAT
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
@ -418,7 +412,7 @@ class EvoController(EvoClimateEntity):
|
||||
"""
|
||||
temps = [
|
||||
z.temperature
|
||||
for z in self._evo_tcs.zones.values()
|
||||
for z in self._evo_device.zones.values()
|
||||
if z.temperature is not None
|
||||
]
|
||||
return round(sum(temps) / len(temps), 1) if temps else None
|
||||
@ -426,9 +420,9 @@ class EvoController(EvoClimateEntity):
|
||||
@property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return the current preset mode, e.g., home, away, temp."""
|
||||
if not self._evo_tcs.system_mode:
|
||||
if not self._evo_device.system_mode:
|
||||
return None
|
||||
return TCS_PRESET_TO_HA.get(self._evo_tcs.system_mode)
|
||||
return TCS_PRESET_TO_HA.get(self._evo_device.system_mode)
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Raise exception as Controllers don't have a target temperature."""
|
||||
@ -436,9 +430,13 @@ class EvoController(EvoClimateEntity):
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set an operating mode for a Controller."""
|
||||
if not (tcs_mode := HA_HVAC_TO_TCS.get(hvac_mode)):
|
||||
if hvac_mode == HVACMode.HEAT:
|
||||
evo_mode = EVO_AUTO if EVO_AUTO in self._evo_modes else "Heat"
|
||||
elif hvac_mode == HVACMode.OFF:
|
||||
evo_mode = EVO_HEATOFF if EVO_HEATOFF in self._evo_modes else "Off"
|
||||
else:
|
||||
raise HomeAssistantError(f"Invalid hvac_mode: {hvac_mode}")
|
||||
await self._set_tcs_mode(tcs_mode)
|
||||
await self._set_tcs_mode(evo_mode)
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set the preset mode; if None, then revert to 'Auto' mode."""
|
||||
@ -451,6 +449,6 @@ class EvoController(EvoClimateEntity):
|
||||
attrs = self._device_state_attrs
|
||||
for attr in STATE_ATTRS_TCS:
|
||||
if attr == SZ_ACTIVE_FAULTS:
|
||||
attrs["activeSystemFaults"] = getattr(self._evo_tcs, attr)
|
||||
attrs["activeSystemFaults"] = getattr(self._evo_device, attr)
|
||||
else:
|
||||
attrs[attr] = getattr(self._evo_tcs, attr)
|
||||
attrs[attr] = getattr(self._evo_device, attr)
|
||||
|
@ -42,7 +42,6 @@ class EvoDevice(Entity):
|
||||
"""Initialize an evohome-compatible entity (TCS, DHW, zone)."""
|
||||
self._evo_device = evo_device
|
||||
self._evo_broker = evo_broker
|
||||
self._evo_tcs = evo_broker.tcs
|
||||
|
||||
self._device_state_attrs: dict[str, Any] = {}
|
||||
|
||||
@ -101,6 +100,8 @@ class EvoChild(EvoDevice):
|
||||
"""Initialize an evohome-compatible child entity (DHW, zone)."""
|
||||
super().__init__(evo_broker, evo_device)
|
||||
|
||||
self._evo_tcs = evo_device.tcs
|
||||
|
||||
self._schedule: dict[str, Any] = {}
|
||||
self._setpoints: dict[str, Any] = {}
|
||||
|
||||
|
@ -11,6 +11,7 @@ from unittest.mock import MagicMock, patch
|
||||
from aiohttp import ClientSession
|
||||
from evohomeasync2 import EvohomeClient
|
||||
from evohomeasync2.broker import Broker
|
||||
from evohomeasync2.controlsystem import ControlSystem
|
||||
from evohomeasync2.zone import Zone
|
||||
import pytest
|
||||
|
||||
@ -177,13 +178,28 @@ async def evohome(
|
||||
yield mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def ctl_id(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, str],
|
||||
install: MagicMock,
|
||||
) -> AsyncGenerator[str]:
|
||||
"""Return the entity_id of the evohome integration's controller."""
|
||||
|
||||
async for mock_client in setup_evohome(hass, config, install=install):
|
||||
evo: EvohomeClient = mock_client.return_value
|
||||
ctl: ControlSystem = evo._get_single_tcs()
|
||||
|
||||
yield f"{Platform.CLIMATE}.{slugify(ctl.location.name)}"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def zone_id(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, str],
|
||||
install: MagicMock,
|
||||
) -> AsyncGenerator[str]:
|
||||
"""Return the entity_id of the evohome integration' first Climate zone."""
|
||||
"""Return the entity_id of the evohome integration's first zone."""
|
||||
|
||||
async for mock_client in setup_evohome(hass, config, install=install):
|
||||
evo: EvohomeClient = mock_client.return_value
|
||||
|
@ -1,4 +1,124 @@
|
||||
# serializer version: 1
|
||||
# name: test_ctl_set_hvac_mode[default]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[h032585]
|
||||
list([
|
||||
tuple(
|
||||
'Off',
|
||||
),
|
||||
tuple(
|
||||
'Heat',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[h099625]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[minimal]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[sys_004]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[default]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[h032585]
|
||||
list([
|
||||
tuple(
|
||||
'Off',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[h099625]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[minimal]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[sys_004]
|
||||
list([
|
||||
tuple(
|
||||
'HeatingOff',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[default]
|
||||
list([
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[h032585]
|
||||
list([
|
||||
tuple(
|
||||
'Heat',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[h099625]
|
||||
list([
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[minimal]
|
||||
list([
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[sys_004]
|
||||
list([
|
||||
tuple(
|
||||
'Auto',
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_setup_platform[botched][climate.bathroom_dn-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
|
@ -27,6 +27,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .conftest import setup_evohome
|
||||
from .const import TEST_INSTALLS
|
||||
@ -53,13 +54,142 @@ async def test_setup_platform(
|
||||
assert x == snapshot(name=f"{x.entity_id}-state")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS)
|
||||
async def test_ctl_set_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
ctl_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_SET_HVAC_MODE of an evohome controller."""
|
||||
|
||||
results = []
|
||||
|
||||
# SERVICE_SET_HVAC_MODE: HVACMode.OFF
|
||||
with patch("evohomeasync2.controlsystem.ControlSystem.set_mode") as mock_fcn:
|
||||
await hass.services.async_call(
|
||||
Platform.CLIMATE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: ctl_id,
|
||||
ATTR_HVAC_MODE: HVACMode.OFF,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_fcn.await_count == 1
|
||||
assert mock_fcn.await_args.args != () # 'HeatingOff' or 'Off'
|
||||
assert mock_fcn.await_args.kwargs == {"until": None}
|
||||
|
||||
results.append(mock_fcn.await_args.args)
|
||||
|
||||
# SERVICE_SET_HVAC_MODE: HVACMode.HEAT
|
||||
with patch("evohomeasync2.controlsystem.ControlSystem.set_mode") as mock_fcn:
|
||||
await hass.services.async_call(
|
||||
Platform.CLIMATE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: ctl_id,
|
||||
ATTR_HVAC_MODE: HVACMode.HEAT,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_fcn.await_count == 1
|
||||
assert mock_fcn.await_args.args != () # 'Auto' or 'Heat'
|
||||
assert mock_fcn.await_args.kwargs == {"until": None}
|
||||
|
||||
results.append(mock_fcn.await_args.args)
|
||||
|
||||
assert results == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS)
|
||||
async def test_ctl_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
ctl_id: str,
|
||||
) -> None:
|
||||
"""Test SERVICE_SET_TEMPERATURE of an evohome controller."""
|
||||
|
||||
# Entity climate.xxx does not support this service
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
Platform.CLIMATE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{
|
||||
ATTR_ENTITY_ID: ctl_id,
|
||||
ATTR_TEMPERATURE: 19.1,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS)
|
||||
async def test_ctl_turn_off(
|
||||
hass: HomeAssistant,
|
||||
ctl_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_TURN_OFF of an evohome controller."""
|
||||
|
||||
results = []
|
||||
|
||||
# SERVICE_TURN_OFF
|
||||
with patch("evohomeasync2.controlsystem.ControlSystem.set_mode") as mock_fcn:
|
||||
await hass.services.async_call(
|
||||
Platform.CLIMATE,
|
||||
SERVICE_TURN_OFF,
|
||||
{
|
||||
ATTR_ENTITY_ID: ctl_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_fcn.await_count == 1
|
||||
assert mock_fcn.await_args.args != () # 'HeatingOff' or 'Off'
|
||||
assert mock_fcn.await_args.kwargs == {"until": None}
|
||||
|
||||
results.append(mock_fcn.await_args.args)
|
||||
|
||||
assert results == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS)
|
||||
async def test_ctl_turn_on(
|
||||
hass: HomeAssistant,
|
||||
ctl_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_TURN_ON of an evohome controller."""
|
||||
|
||||
results = []
|
||||
|
||||
# SERVICE_TURN_ON
|
||||
with patch("evohomeasync2.controlsystem.ControlSystem.set_mode") as mock_fcn:
|
||||
await hass.services.async_call(
|
||||
Platform.CLIMATE,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: ctl_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_fcn.await_count == 1
|
||||
assert mock_fcn.await_args.args != () # 'Auto' or 'Heat'
|
||||
assert mock_fcn.await_args.kwargs == {"until": None}
|
||||
|
||||
results.append(mock_fcn.await_args.args)
|
||||
|
||||
assert results == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS)
|
||||
async def test_zone_set_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
zone_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_SET_HVAC_MODE of an evohome zone Climate entity."""
|
||||
"""Test SERVICE_SET_HVAC_MODE of an evohome heating zone."""
|
||||
|
||||
results = []
|
||||
|
||||
@ -107,7 +237,7 @@ async def test_zone_set_preset_mode(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_SET_PRESET_MODE of an evohome zone Climate entity."""
|
||||
"""Test SERVICE_SET_PRESET_MODE of an evohome heating zone."""
|
||||
|
||||
freezer.move_to("2024-07-10T12:00:00Z")
|
||||
results = []
|
||||
@ -175,7 +305,7 @@ async def test_zone_set_temperature(
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_SET_TEMPERATURE of an evohome zone Climate entity."""
|
||||
"""Test SERVICE_SET_TEMPERATURE of an evohome heating zone."""
|
||||
|
||||
freezer.move_to("2024-07-10T12:00:00Z")
|
||||
results = []
|
||||
@ -207,7 +337,7 @@ async def test_zone_turn_off(
|
||||
zone_id: str,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test SERVICE_TURN_OFF of a evohome zone Climate entity."""
|
||||
"""Test SERVICE_TURN_OFF of an evohome heating zone."""
|
||||
|
||||
results = []
|
||||
|
||||
@ -236,7 +366,7 @@ async def test_zone_turn_on(
|
||||
hass: HomeAssistant,
|
||||
zone_id: str,
|
||||
) -> None:
|
||||
"""Test SERVICE_TURN_ON of a evohome zone Climate entity."""
|
||||
"""Test SERVICE_TURN_ON of an evohome heating zone."""
|
||||
|
||||
# SERVICE_TURN_ON
|
||||
with patch("evohomeasync2.zone.Zone.reset_mode") as mock_fcn:
|
||||
|
Loading…
x
Reference in New Issue
Block a user