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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ZHA MOES thermostat valve presets #48178

Merged
merged 1 commit into from
Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
90 changes: 90 additions & 0 deletions homeassistant/components/zha/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
PRESET_NONE,
SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE,
Expand All @@ -49,6 +52,8 @@
CHANNEL_THERMOSTAT,
DATA_ZHA,
DATA_ZHA_DISPATCHERS,
PRESET_COMPLEX,
PRESET_SCHEDULE,
SIGNAL_ADD_ENTITIES,
SIGNAL_ATTR_UPDATED,
)
Expand Down Expand Up @@ -595,3 +600,88 @@ def _rm_rs_action(self) -> str | None:
)
class CentralitePearl(ZenWithinThermostat):
"""Centralite Pearl Thermostat implementation."""


@STRICT_MATCH(
channel_names=CHANNEL_THERMOSTAT,
manufacturers={
"_TZE200_ckud7u2l",
"_TZE200_ywdxldoj",
"_TYST11_ckud7u2l",
"_TYST11_ywdxldoj",
},
)
class MoesThermostat(Thermostat):
"""Moes Thermostat implementation."""

def __init__(self, unique_id, zha_device, channels, **kwargs):
"""Initialize ZHA Thermostat instance."""
super().__init__(unique_id, zha_device, channels, **kwargs)
self._presets = [
PRESET_NONE,
PRESET_AWAY,
PRESET_SCHEDULE,
PRESET_COMFORT,
PRESET_ECO,
PRESET_BOOST,
PRESET_COMPLEX,
]
self._supported_flags |= SUPPORT_PRESET_MODE

@property
def hvac_modes(self) -> tuple[str, ...]:
"""Return only the heat mode, because the device can't be turned off."""
return (HVAC_MODE_HEAT,)

async def async_attribute_updated(self, record):
"""Handle attribute update from device."""
if record.attr_name == "operation_preset":
if record.value == 0:
self._preset = PRESET_AWAY
if record.value == 1:
self._preset = PRESET_SCHEDULE
if record.value == 2:
self._preset = PRESET_NONE
if record.value == 3:
self._preset = PRESET_COMFORT
if record.value == 4:
self._preset = PRESET_ECO
if record.value == 5:
self._preset = PRESET_BOOST
if record.value == 6:
self._preset = PRESET_COMPLEX
await super().async_attribute_updated(record)

async def async_preset_handler(self, preset: str, enable: bool = False) -> bool:
"""Set the preset mode."""
mfg_code = self._zha_device.manufacturer_code
if not enable:
return await self._thrm.write_attributes(
{"operation_preset": 2}, manufacturer=mfg_code
)
if preset == PRESET_AWAY:
return await self._thrm.write_attributes(
{"operation_preset": 0}, manufacturer=mfg_code
)
if preset == PRESET_SCHEDULE:
return await self._thrm.write_attributes(
{"operation_preset": 1}, manufacturer=mfg_code
)
if preset == PRESET_COMFORT:
return await self._thrm.write_attributes(
{"operation_preset": 3}, manufacturer=mfg_code
)
if preset == PRESET_ECO:
return await self._thrm.write_attributes(
{"operation_preset": 4}, manufacturer=mfg_code
)
if preset == PRESET_BOOST:
return await self._thrm.write_attributes(
{"operation_preset": 5}, manufacturer=mfg_code
)
if preset == PRESET_COMPLEX:
return await self._thrm.write_attributes(
{"operation_preset": 6}, manufacturer=mfg_code
)

return False
3 changes: 3 additions & 0 deletions homeassistant/components/zha/core/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@
POWER_MAINS_POWERED = "Mains"
POWER_BATTERY_OR_UNKNOWN = "Battery or Unknown"

PRESET_SCHEDULE = "schedule"
PRESET_COMPLEX = "complex"


class RadioType(enum.Enum):
# pylint: disable=invalid-name
Expand Down
183 changes: 183 additions & 0 deletions tests/components/zha/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
PRESET_NONE,
SERVICE_SET_FAN_MODE,
SERVICE_SET_HVAC_MODE,
Expand All @@ -44,6 +47,7 @@
HVAC_MODE_2_SYSTEM,
SEQ_OF_OPERATION,
)
from homeassistant.components.zha.core.const import PRESET_COMPLEX, PRESET_SCHEDULE
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_UNKNOWN

from .common import async_enable_traffic, find_entity_id, send_attributes_report
Expand Down Expand Up @@ -103,8 +107,23 @@
"out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id],
}
}

CLIMATE_MOES = {
1: {
"device_type": zigpy.profiles.zha.DeviceType.THERMOSTAT,
"in_clusters": [
zigpy.zcl.clusters.general.Basic.cluster_id,
zigpy.zcl.clusters.general.Identify.cluster_id,
zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
61148,
],
"out_clusters": [zigpy.zcl.clusters.general.Ota.cluster_id],
}
}
MANUF_SINOPE = "Sinope Technologies"
MANUF_ZEN = "Zen Within"
MANUF_MOES = "_TZE200_ckud7u2l"

ZCL_ATTR_PLUG = {
"abs_min_heat_setpoint_limit": 800,
Expand Down Expand Up @@ -183,6 +202,13 @@ async def device_climate_zen(device_climate_mock):
return await device_climate_mock(CLIMATE_ZEN, manuf=MANUF_ZEN)


@pytest.fixture
async def device_climate_moes(device_climate_mock):
"""MOES thermostat."""

return await device_climate_mock(CLIMATE_MOES, manuf=MANUF_MOES)


def test_sequence_mappings():
"""Test correct mapping between control sequence -> HVAC Mode -> Sysmode."""

Expand Down Expand Up @@ -1106,3 +1132,160 @@ async def test_set_fan_mode(hass, device_climate_fan):
)
assert fan_cluster.write_attributes.await_count == 1
assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5}


async def test_set_moes_preset(hass, device_climate_moes):
"""Test setting preset for moes trv."""

entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass)
thrm_cluster = device_climate_moes.device.endpoints[1].thermostat

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE

await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 0
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_SCHEDULE},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"operation_preset": 1
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMFORT},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"operation_preset": 3
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_ECO},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"operation_preset": 4
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_BOOST},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"operation_preset": 5
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_COMPLEX},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 2
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}
assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
"operation_preset": 6
}

thrm_cluster.write_attributes.reset_mock()
await hass.services.async_call(
DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_NONE},
blocking=True,
)

assert thrm_cluster.write_attributes.await_count == 1
assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
"operation_preset": 2
}


async def test_set_moes_operation_mode(hass, device_climate_moes):
"""Test setting preset for moes trv."""

entity_id = await find_entity_id(DOMAIN, device_climate_moes, hass)
thrm_cluster = device_climate_moes.device.endpoints[1].thermostat

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 0})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 1})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_SCHEDULE

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 2})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 3})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMFORT

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 4})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_ECO

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 5})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_BOOST

await send_attributes_report(hass, thrm_cluster, {"operation_preset": 6})

state = hass.states.get(entity_id)
assert state.attributes[ATTR_PRESET_MODE] == PRESET_COMPLEX