Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust system mode behavior for Acova (Zehnder) heaters #256

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions tests/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
HVAC_MODE_2_SYSTEM,
SEQ_OF_OPERATION,
Thermostat as ThermostatEntity,
ZehnderThermostat,
)
from zha.application.platforms.climate.const import FanState
from zha.application.platforms.sensor import (
Expand Down Expand Up @@ -120,6 +121,19 @@
}
}

CLIMATE_ZEHNDER = {
1: {
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
SIG_EP_INPUT: [
zigpy.zcl.clusters.general.Basic.cluster_id,
zigpy.zcl.clusters.general.Identify.cluster_id,
zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
],
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Identify.cluster_id],
}
}

CLIMATE_MOES = {
1: {
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
Expand Down Expand Up @@ -168,6 +182,7 @@

MANUF_SINOPE = "Sinope Technologies"
MANUF_ZEN = "Zen Within"
MANUF_ZEHNDER = "ZEHNDER GROUP VAUX ANDIGNY "
MANUF_MOES = "_TZE200_ckud7u2l"
MANUF_BECA = "_TZE200_b6wax7g0"
MANUF_ZONNSMART = "_TZE200_hue3yfsn"
Expand Down Expand Up @@ -479,6 +494,118 @@ async def test_climate_hvac_action_running_state_zen(
assert sensor_entity.state["state"] == "idle"


async def test_climate_hvac_action_running_state_zehnder(
zha_gateway: Gateway,
):
"""Test Zehnder hvac action via running state."""
device_climate_zehnder = await device_climate_mock(
zha_gateway, CLIMATE_ZEHNDER, manuf=MANUF_ZEHNDER
)

thrm_cluster = device_climate_zehnder.device.endpoints[1].thermostat

entity: ThermostatEntity = get_entity(
device_climate_zehnder, platform=Platform.CLIMATE, entity_type=ThermostatEntity
)

assert entity.state["hvac_action"] is None

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Cool_2nd_Stage_On}
)
assert entity.state["hvac_action"] == "cooling"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_State_On}
)
assert entity.state["hvac_action"] == "fan"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Heat_2nd_Stage_On}
)
assert entity.state["hvac_action"] == "heating"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_2nd_Stage_On}
)
assert entity.state["hvac_action"] == "fan"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Cool_State_On}
)
assert entity.state["hvac_action"] == "cooling"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_3rd_Stage_On}
)
assert entity.state["hvac_action"] == "fan"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Heat_State_On}
)
assert entity.state["hvac_action"] == "heating"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x0029: Thermostat.RunningState.Idle}
)
assert entity.state["hvac_action"] == "off"

await send_attributes_report(
zha_gateway, thrm_cluster, {0x001C: Thermostat.SystemMode.Heat}
)
assert entity.state["hvac_action"] == "idle"


@pytest.mark.parametrize(
"hvac_mode, sys_mode",
(
("heat", Thermostat.SystemMode.Auto),
("off", Thermostat.SystemMode.Off),
("heat_cool", None),
),
)
async def test_set_hvac_mode_zehnder(
zha_gateway: Gateway,
hvac_mode,
sys_mode,
):
"""Test setting hvac mode."""
device_climate_zehnder = await device_climate_mock(
zha_gateway, CLIMATE_ZEHNDER, manuf=MANUF_ZEHNDER
)

thrm_cluster = device_climate_zehnder.device.endpoints[1].thermostat
entity: ThermostatEntity = get_entity(
device_climate_zehnder, platform=Platform.CLIMATE, entity_type=ZehnderThermostat
)

assert entity.state["hvac_mode"] == "off"

await entity.async_set_hvac_mode(hvac_mode)
await zha_gateway.async_block_till_done()

if sys_mode is not None:
assert entity.state["hvac_mode"] == hvac_mode
assert thrm_cluster.write_attributes.call_count == 1
assert thrm_cluster.write_attributes.call_args[0][0] == {
"system_mode": sys_mode
}
else:
assert thrm_cluster.write_attributes.call_count == 0
assert entity.state["hvac_mode"] == "off"

# turn off
thrm_cluster.write_attributes.reset_mock()
await entity.async_set_hvac_mode("off")
await zha_gateway.async_block_till_done()

assert entity.state["hvac_mode"] == "off"
assert thrm_cluster.write_attributes.call_count == 1
assert thrm_cluster.write_attributes.call_args[0][0] == {
"system_mode": Thermostat.SystemMode.Off
}


async def test_climate_hvac_action_pi_demand(
zha_gateway: Gateway,
):
Expand Down
66 changes: 66 additions & 0 deletions zha/application/platforms/climate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,72 @@ class ZenWithinThermostat(Thermostat):
"""Zen Within Thermostat implementation."""


@MULTI_MATCH(
cluster_handler_names=CLUSTER_HANDLER_THERMOSTAT,
manufacturers={"ZEHNDER GROUP VAUX ANDIGNY ", "ZEHNDER GROUP VAUX ANDIGNY"},
stop_on_match_group=CLUSTER_HANDLER_THERMOSTAT,
)
class ZehnderThermostat(Thermostat):
"""Zehnder thermostat to adapt AUTO mode behavior."""

ZEHNDER_HVAC_MODE_2_SYSTEM = {
HVACMode.OFF: SystemMode.Off,
HVACMode.HEAT: SystemMode.Auto,
}

ZEHNDER_SYSTEM_MODE_2_HVAC = {
SystemMode.Off: HVACMode.OFF,
SystemMode.Auto: HVACMode.HEAT,
}

hvac_modes = [HVACMode.OFF, HVACMode.HEAT]

async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target operation mode."""
if hvac_mode not in self.hvac_modes:
self.warning(
"can't set '%s' mode. Supported modes are: %s",
hvac_mode,
self.hvac_modes,
)
return

if await self._thermostat_cluster_handler.async_set_operation_mode(
ZehnderThermostat.ZEHNDER_HVAC_MODE_2_SYSTEM[hvac_mode]
):
self.maybe_emit_state_changed_event()

@property
def current_temperature(self):
"""Force no current temperature."""
return None

@property
def state(self) -> dict[str, Any]:
"""Get the state of the lock."""
thermostat = self._thermostat_cluster_handler
system_mode = ZehnderThermostat.ZEHNDER_SYSTEM_MODE_2_HVAC.get(
thermostat.system_mode, "unknown"
)

response = super().state

response[ATTR_SYS_MODE] = (
f"[{thermostat.system_mode}]/{system_mode}"
if self.hvac_mode is not None
else None
)

return response

@property
def hvac_mode(self) -> HVACMode | None:
"""Return HVAC operation mode."""
return ZehnderThermostat.ZEHNDER_SYSTEM_MODE_2_HVAC.get(
self._thermostat_cluster_handler.system_mode
)


@MULTI_MATCH(
cluster_handler_names=CLUSTER_HANDLER_THERMOSTAT,
aux_cluster_handlers=CLUSTER_HANDLER_FAN,
Expand Down
Loading