mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Refactor ZHA HVAC thermostat channel (#56238)
* Refactor HVAC channel to use zigpy cached attributes * Allow named attributes in ZHA test attribute reports * Let attribute write to update cache * WIP Update tests * Cleanup
This commit is contained in:
parent
16832bc35b
commit
5b0e00a74b
@ -125,134 +125,118 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
"unoccupied_heating_setpoint": False,
|
"unoccupied_heating_setpoint": False,
|
||||||
"unoccupied_cooling_setpoint": False,
|
"unoccupied_cooling_setpoint": False,
|
||||||
}
|
}
|
||||||
self._abs_max_cool_setpoint_limit = 3200 # 32C
|
|
||||||
self._abs_min_cool_setpoint_limit = 1600 # 16C
|
|
||||||
self._ctrl_seqe_of_oper = 0xFF
|
|
||||||
self._abs_max_heat_setpoint_limit = 3000 # 30C
|
|
||||||
self._abs_min_heat_setpoint_limit = 700 # 7C
|
|
||||||
self._running_mode = None
|
|
||||||
self._max_cool_setpoint_limit = None
|
|
||||||
self._max_heat_setpoint_limit = None
|
|
||||||
self._min_cool_setpoint_limit = None
|
|
||||||
self._min_heat_setpoint_limit = None
|
|
||||||
self._local_temp = None
|
|
||||||
self._occupancy = None
|
|
||||||
self._occupied_cooling_setpoint = None
|
|
||||||
self._occupied_heating_setpoint = None
|
|
||||||
self._pi_cooling_demand = None
|
|
||||||
self._pi_heating_demand = None
|
|
||||||
self._running_state = None
|
|
||||||
self._system_mode = None
|
|
||||||
self._unoccupied_cooling_setpoint = None
|
|
||||||
self._unoccupied_heating_setpoint = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abs_max_cool_setpoint_limit(self) -> int:
|
def abs_max_cool_setpoint_limit(self) -> int:
|
||||||
"""Absolute maximum cooling setpoint."""
|
"""Absolute maximum cooling setpoint."""
|
||||||
return self._abs_max_cool_setpoint_limit
|
return self.cluster.get("abs_max_cool_setpoint_limit", 3200)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abs_min_cool_setpoint_limit(self) -> int:
|
def abs_min_cool_setpoint_limit(self) -> int:
|
||||||
"""Absolute minimum cooling setpoint."""
|
"""Absolute minimum cooling setpoint."""
|
||||||
return self._abs_min_cool_setpoint_limit
|
return self.cluster.get("abs_min_cool_setpoint_limit", 1600)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abs_max_heat_setpoint_limit(self) -> int:
|
def abs_max_heat_setpoint_limit(self) -> int:
|
||||||
"""Absolute maximum heating setpoint."""
|
"""Absolute maximum heating setpoint."""
|
||||||
return self._abs_max_heat_setpoint_limit
|
return self.cluster.get("abs_max_heat_setpoint_limit", 3000)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abs_min_heat_setpoint_limit(self) -> int:
|
def abs_min_heat_setpoint_limit(self) -> int:
|
||||||
"""Absolute minimum heating setpoint."""
|
"""Absolute minimum heating setpoint."""
|
||||||
return self._abs_min_heat_setpoint_limit
|
return self.cluster.get("abs_min_heat_setpoint_limit", 700)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ctrl_seqe_of_oper(self) -> int:
|
def ctrl_seqe_of_oper(self) -> int:
|
||||||
"""Control Sequence of operations attribute."""
|
"""Control Sequence of operations attribute."""
|
||||||
return self._ctrl_seqe_of_oper
|
return self.cluster.get("ctrl_seqe_of_oper", 0xFF)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_cool_setpoint_limit(self) -> int:
|
def max_cool_setpoint_limit(self) -> int:
|
||||||
"""Maximum cooling setpoint."""
|
"""Maximum cooling setpoint."""
|
||||||
if self._max_cool_setpoint_limit is None:
|
sp_limit = self.cluster.get("max_cool_setpoint_limit")
|
||||||
|
if sp_limit is None:
|
||||||
return self.abs_max_cool_setpoint_limit
|
return self.abs_max_cool_setpoint_limit
|
||||||
return self._max_cool_setpoint_limit
|
return sp_limit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_cool_setpoint_limit(self) -> int:
|
def min_cool_setpoint_limit(self) -> int:
|
||||||
"""Minimum cooling setpoint."""
|
"""Minimum cooling setpoint."""
|
||||||
if self._min_cool_setpoint_limit is None:
|
sp_limit = self.cluster.get("min_cool_setpoint_limit")
|
||||||
|
if sp_limit is None:
|
||||||
return self.abs_min_cool_setpoint_limit
|
return self.abs_min_cool_setpoint_limit
|
||||||
return self._min_cool_setpoint_limit
|
return sp_limit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_heat_setpoint_limit(self) -> int:
|
def max_heat_setpoint_limit(self) -> int:
|
||||||
"""Maximum heating setpoint."""
|
"""Maximum heating setpoint."""
|
||||||
if self._max_heat_setpoint_limit is None:
|
sp_limit = self.cluster.get("max_heat_setpoint_limit")
|
||||||
|
if sp_limit is None:
|
||||||
return self.abs_max_heat_setpoint_limit
|
return self.abs_max_heat_setpoint_limit
|
||||||
return self._max_heat_setpoint_limit
|
return sp_limit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_heat_setpoint_limit(self) -> int:
|
def min_heat_setpoint_limit(self) -> int:
|
||||||
"""Minimum heating setpoint."""
|
"""Minimum heating setpoint."""
|
||||||
if self._min_heat_setpoint_limit is None:
|
sp_limit = self.cluster.get("min_heat_setpoint_limit")
|
||||||
|
if sp_limit is None:
|
||||||
return self.abs_min_heat_setpoint_limit
|
return self.abs_min_heat_setpoint_limit
|
||||||
return self._min_heat_setpoint_limit
|
return sp_limit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def local_temp(self) -> int | None:
|
def local_temp(self) -> int | None:
|
||||||
"""Thermostat temperature."""
|
"""Thermostat temperature."""
|
||||||
return self._local_temp
|
return self.cluster.get("local_temp")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def occupancy(self) -> int | None:
|
def occupancy(self) -> int | None:
|
||||||
"""Is occupancy detected."""
|
"""Is occupancy detected."""
|
||||||
return self._occupancy
|
return self.cluster.get("occupancy")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def occupied_cooling_setpoint(self) -> int | None:
|
def occupied_cooling_setpoint(self) -> int | None:
|
||||||
"""Temperature when room is occupied."""
|
"""Temperature when room is occupied."""
|
||||||
return self._occupied_cooling_setpoint
|
return self.cluster.get("occupied_cooling_setpoint")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def occupied_heating_setpoint(self) -> int | None:
|
def occupied_heating_setpoint(self) -> int | None:
|
||||||
"""Temperature when room is occupied."""
|
"""Temperature when room is occupied."""
|
||||||
return self._occupied_heating_setpoint
|
return self.cluster.get("occupied_heating_setpoint")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pi_cooling_demand(self) -> int:
|
def pi_cooling_demand(self) -> int:
|
||||||
"""Cooling demand."""
|
"""Cooling demand."""
|
||||||
return self._pi_cooling_demand
|
return self.cluster.get("pi_cooling_demand")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pi_heating_demand(self) -> int:
|
def pi_heating_demand(self) -> int:
|
||||||
"""Heating demand."""
|
"""Heating demand."""
|
||||||
return self._pi_heating_demand
|
return self.cluster.get("pi_heating_demand")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def running_mode(self) -> int | None:
|
def running_mode(self) -> int | None:
|
||||||
"""Thermostat running mode."""
|
"""Thermostat running mode."""
|
||||||
return self._running_mode
|
return self.cluster.get("running_mode")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def running_state(self) -> int | None:
|
def running_state(self) -> int | None:
|
||||||
"""Thermostat running state, state of heat, cool, fan relays."""
|
"""Thermostat running state, state of heat, cool, fan relays."""
|
||||||
return self._running_state
|
return self.cluster.get("running_state")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def system_mode(self) -> int | None:
|
def system_mode(self) -> int | None:
|
||||||
"""System mode."""
|
"""System mode."""
|
||||||
return self._system_mode
|
return self.cluster.get("system_mode")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unoccupied_cooling_setpoint(self) -> int | None:
|
def unoccupied_cooling_setpoint(self) -> int | None:
|
||||||
"""Temperature when room is not occupied."""
|
"""Temperature when room is not occupied."""
|
||||||
return self._unoccupied_cooling_setpoint
|
return self.cluster.get("unoccupied_cooling_setpoint")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unoccupied_heating_setpoint(self) -> int | None:
|
def unoccupied_heating_setpoint(self) -> int | None:
|
||||||
"""Temperature when room is not occupied."""
|
"""Temperature when room is not occupied."""
|
||||||
return self._unoccupied_heating_setpoint
|
return self.cluster.get("unoccupied_heating_setpoint")
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def attribute_updated(self, attrid, value):
|
def attribute_updated(self, attrid, value):
|
||||||
@ -261,7 +245,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
self.debug(
|
self.debug(
|
||||||
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
|
"Attribute report '%s'[%s] = %s", self.cluster.name, attr_name, value
|
||||||
)
|
)
|
||||||
setattr(self, f"_{attr_name}", value)
|
|
||||||
self.async_send_signal(
|
self.async_send_signal(
|
||||||
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
|
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
|
||||||
AttributeUpdateRecord(attrid, attr_name, value),
|
AttributeUpdateRecord(attrid, attr_name, value),
|
||||||
@ -276,8 +259,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
self._init_attrs.pop(attr, None)
|
self._init_attrs.pop(attr, None)
|
||||||
if attr in fail:
|
if attr in fail:
|
||||||
continue
|
continue
|
||||||
if isinstance(attr, str):
|
|
||||||
setattr(self, f"_{attr}", res[attr])
|
|
||||||
self.async_send_signal(
|
self.async_send_signal(
|
||||||
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
|
f"{self.unique_id}_{SIGNAL_ATTR_UPDATED}",
|
||||||
AttributeUpdateRecord(None, attr, res[attr]),
|
AttributeUpdateRecord(None, attr, res[attr]),
|
||||||
@ -301,7 +282,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
self.debug("couldn't set '%s' operation mode", mode)
|
self.debug("couldn't set '%s' operation mode", mode)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._system_mode = mode
|
|
||||||
self.debug("set system to %s", mode)
|
self.debug("set system to %s", mode)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -317,11 +297,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
self.debug("couldn't set heating setpoint")
|
self.debug("couldn't set heating setpoint")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if is_away:
|
|
||||||
self._unoccupied_heating_setpoint = temperature
|
|
||||||
else:
|
|
||||||
self._occupied_heating_setpoint = temperature
|
|
||||||
self.debug("set heating setpoint to %s", temperature)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_set_cooling_setpoint(
|
async def async_set_cooling_setpoint(
|
||||||
@ -335,10 +310,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
if not await self.write_attributes(data):
|
if not await self.write_attributes(data):
|
||||||
self.debug("couldn't set cooling setpoint")
|
self.debug("couldn't set cooling setpoint")
|
||||||
return False
|
return False
|
||||||
if is_away:
|
|
||||||
self._unoccupied_cooling_setpoint = temperature
|
|
||||||
else:
|
|
||||||
self._occupied_cooling_setpoint = temperature
|
|
||||||
self.debug("set cooling setpoint to %s", temperature)
|
self.debug("set cooling setpoint to %s", temperature)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -349,7 +320,6 @@ class ThermostatChannel(ZigbeeChannel):
|
|||||||
self.debug("read 'occupancy' attr, success: %s, fail: %s", res, fail)
|
self.debug("read 'occupancy' attr, success: %s, fail: %s", res, fail)
|
||||||
if "occupancy" not in res:
|
if "occupancy" not in res:
|
||||||
return None
|
return None
|
||||||
self._occupancy = res["occupancy"]
|
|
||||||
return bool(self.occupancy)
|
return bool(self.occupancy)
|
||||||
except ZigbeeException as ex:
|
except ZigbeeException as ex:
|
||||||
self.debug("Couldn't read 'occupancy' attribute: %s", ex)
|
self.debug("Couldn't read 'occupancy' attribute: %s", ex)
|
||||||
|
@ -3,6 +3,7 @@ import asyncio
|
|||||||
import math
|
import math
|
||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
|
import zigpy.zcl
|
||||||
import zigpy.zcl.foundation as zcl_f
|
import zigpy.zcl.foundation as zcl_f
|
||||||
|
|
||||||
import homeassistant.components.zha.core.const as zha_const
|
import homeassistant.components.zha.core.const as zha_const
|
||||||
@ -47,7 +48,8 @@ def patch_cluster(cluster):
|
|||||||
cluster.read_attributes = AsyncMock(wraps=cluster.read_attributes)
|
cluster.read_attributes = AsyncMock(wraps=cluster.read_attributes)
|
||||||
cluster.read_attributes_raw = AsyncMock(side_effect=_read_attribute_raw)
|
cluster.read_attributes_raw = AsyncMock(side_effect=_read_attribute_raw)
|
||||||
cluster.unbind = AsyncMock(return_value=[0])
|
cluster.unbind = AsyncMock(return_value=[0])
|
||||||
cluster.write_attributes = AsyncMock(
|
cluster.write_attributes = AsyncMock(wraps=cluster.write_attributes)
|
||||||
|
cluster._write_attributes = AsyncMock(
|
||||||
return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]]
|
return_value=[zcl_f.WriteAttributesResponse.deserialize(b"\x00")[0]]
|
||||||
)
|
)
|
||||||
if cluster.cluster_id == 4:
|
if cluster.cluster_id == 4:
|
||||||
@ -76,13 +78,16 @@ def send_attribute_report(hass, cluster, attrid, value):
|
|||||||
return send_attributes_report(hass, cluster, {attrid: value})
|
return send_attributes_report(hass, cluster, {attrid: value})
|
||||||
|
|
||||||
|
|
||||||
async def send_attributes_report(hass, cluster: int, attributes: dict):
|
async def send_attributes_report(hass, cluster: zigpy.zcl.Cluster, attributes: dict):
|
||||||
"""Cause the sensor to receive an attribute report from the network.
|
"""Cause the sensor to receive an attribute report from the network.
|
||||||
|
|
||||||
This is to simulate the normal device communication that happens when a
|
This is to simulate the normal device communication that happens when a
|
||||||
device is paired to the zigbee network.
|
device is paired to the zigbee network.
|
||||||
"""
|
"""
|
||||||
attrs = [make_attribute(attrid, value) for attrid, value in attributes.items()]
|
attrs = [
|
||||||
|
make_attribute(cluster.attridx.get(attr, attr), value)
|
||||||
|
for attr, value in attributes.items()
|
||||||
|
]
|
||||||
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
hdr = make_zcl_header(zcl_f.Command.Report_Attributes)
|
||||||
hdr.frame_control.disable_default_response = True
|
hdr.frame_control.disable_default_response = True
|
||||||
cluster.handle_message(hdr, [attrs])
|
cluster.handle_message(hdr, [attrs])
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Test configuration for the ZHA component."""
|
"""Test configuration for the ZHA component."""
|
||||||
|
import itertools
|
||||||
import time
|
import time
|
||||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||||
|
|
||||||
@ -116,6 +117,7 @@ def zigpy_device_mock(zigpy_app_controller):
|
|||||||
node_descriptor=b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00",
|
node_descriptor=b"\x02@\x807\x10\x7fd\x00\x00*d\x00\x00",
|
||||||
nwk=0xB79C,
|
nwk=0xB79C,
|
||||||
patch_cluster=True,
|
patch_cluster=True,
|
||||||
|
quirk=None,
|
||||||
):
|
):
|
||||||
"""Make a fake device using the specified cluster classes."""
|
"""Make a fake device using the specified cluster classes."""
|
||||||
device = zigpy.device.Device(
|
device = zigpy.device.Device(
|
||||||
@ -133,13 +135,20 @@ def zigpy_device_mock(zigpy_app_controller):
|
|||||||
endpoint.request = AsyncMock(return_value=[0])
|
endpoint.request = AsyncMock(return_value=[0])
|
||||||
|
|
||||||
for cluster_id in ep.get(SIG_EP_INPUT, []):
|
for cluster_id in ep.get(SIG_EP_INPUT, []):
|
||||||
cluster = endpoint.add_input_cluster(cluster_id)
|
endpoint.add_input_cluster(cluster_id)
|
||||||
if patch_cluster:
|
|
||||||
common.patch_cluster(cluster)
|
|
||||||
|
|
||||||
for cluster_id in ep.get(SIG_EP_OUTPUT, []):
|
for cluster_id in ep.get(SIG_EP_OUTPUT, []):
|
||||||
cluster = endpoint.add_output_cluster(cluster_id)
|
endpoint.add_output_cluster(cluster_id)
|
||||||
if patch_cluster:
|
|
||||||
|
if quirk:
|
||||||
|
device = quirk(zigpy_app_controller, device.ieee, device.nwk, device)
|
||||||
|
|
||||||
|
if patch_cluster:
|
||||||
|
for endpoint in (ep for epid, ep in device.endpoints.items() if epid):
|
||||||
|
endpoint.request = AsyncMock(return_value=[0])
|
||||||
|
for cluster in itertools.chain(
|
||||||
|
endpoint.in_clusters.values(), endpoint.out_clusters.values()
|
||||||
|
):
|
||||||
common.patch_cluster(cluster)
|
common.patch_cluster(cluster)
|
||||||
|
|
||||||
return device
|
return device
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import zhaquirks.sinope.thermostat
|
||||||
|
import zhaquirks.tuya.valve
|
||||||
import zigpy.profiles
|
import zigpy.profiles
|
||||||
import zigpy.zcl.clusters
|
import zigpy.zcl.clusters
|
||||||
from zigpy.zcl.clusters.hvac import Thermostat
|
from zigpy.zcl.clusters.hvac import Thermostat
|
||||||
@ -96,6 +98,12 @@ CLIMATE_SINOPE = {
|
|||||||
],
|
],
|
||||||
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id, 65281],
|
SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id, 65281],
|
||||||
},
|
},
|
||||||
|
196: {
|
||||||
|
SIG_EP_PROFILE: 0xC25D,
|
||||||
|
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
|
||||||
|
SIG_EP_INPUT: [zigpy.zcl.clusters.general.PowerConfiguration.cluster_id],
|
||||||
|
SIG_EP_OUTPUT: [],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
CLIMATE_ZEN = {
|
CLIMATE_ZEN = {
|
||||||
@ -159,13 +167,13 @@ ZCL_ATTR_PLUG = {
|
|||||||
def device_climate_mock(hass, zigpy_device_mock, zha_device_joined):
|
def device_climate_mock(hass, zigpy_device_mock, zha_device_joined):
|
||||||
"""Test regular thermostat device."""
|
"""Test regular thermostat device."""
|
||||||
|
|
||||||
async def _dev(clusters, plug=None, manuf=None):
|
async def _dev(clusters, plug=None, manuf=None, quirk=None):
|
||||||
if plug is None:
|
if plug is None:
|
||||||
plugged_attrs = ZCL_ATTR_PLUG
|
plugged_attrs = ZCL_ATTR_PLUG
|
||||||
else:
|
else:
|
||||||
plugged_attrs = {**ZCL_ATTR_PLUG, **plug}
|
plugged_attrs = {**ZCL_ATTR_PLUG, **plug}
|
||||||
|
|
||||||
zigpy_device = zigpy_device_mock(clusters, manufacturer=manuf)
|
zigpy_device = zigpy_device_mock(clusters, manufacturer=manuf, quirk=quirk)
|
||||||
zigpy_device.endpoints[1].thermostat.PLUGGED_ATTR_READS = plugged_attrs
|
zigpy_device.endpoints[1].thermostat.PLUGGED_ATTR_READS = plugged_attrs
|
||||||
zha_device = await zha_device_joined(zigpy_device)
|
zha_device = await zha_device_joined(zigpy_device)
|
||||||
await async_enable_traffic(hass, [zha_device])
|
await async_enable_traffic(hass, [zha_device])
|
||||||
@ -198,7 +206,11 @@ async def device_climate_fan(device_climate_mock):
|
|||||||
async def device_climate_sinope(device_climate_mock):
|
async def device_climate_sinope(device_climate_mock):
|
||||||
"""Sinope thermostat."""
|
"""Sinope thermostat."""
|
||||||
|
|
||||||
return await device_climate_mock(CLIMATE_SINOPE, manuf=MANUF_SINOPE)
|
return await device_climate_mock(
|
||||||
|
CLIMATE_SINOPE,
|
||||||
|
manuf=MANUF_SINOPE,
|
||||||
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -212,7 +224,9 @@ async def device_climate_zen(device_climate_mock):
|
|||||||
async def device_climate_moes(device_climate_mock):
|
async def device_climate_moes(device_climate_mock):
|
||||||
"""MOES thermostat."""
|
"""MOES thermostat."""
|
||||||
|
|
||||||
return await device_climate_mock(CLIMATE_MOES, manuf=MANUF_MOES)
|
return await device_climate_mock(
|
||||||
|
CLIMATE_MOES, manuf=MANUF_MOES, quirk=zhaquirks.tuya.valve.MoesHY368_Type1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_sequence_mappings():
|
def test_sequence_mappings():
|
||||||
@ -456,22 +470,18 @@ async def test_target_temperature(
|
|||||||
):
|
):
|
||||||
"""Test target temperature property."""
|
"""Test target temperature property."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_cooling_setpoint": 2500,
|
||||||
):
|
"occupied_heating_setpoint": 2200,
|
||||||
device_climate = await device_climate_mock(
|
"system_mode": sys_mode,
|
||||||
CLIMATE_SINOPE,
|
"unoccupied_heating_setpoint": 1600,
|
||||||
{
|
"unoccupied_cooling_setpoint": 2700,
|
||||||
"occupied_cooling_setpoint": 2500,
|
},
|
||||||
"occupied_heating_setpoint": 2200,
|
manuf=MANUF_SINOPE,
|
||||||
"system_mode": sys_mode,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"unoccupied_heating_setpoint": 1600,
|
)
|
||||||
"unoccupied_cooling_setpoint": 2700,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
if preset:
|
if preset:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -498,20 +508,16 @@ async def test_target_temperature_high(
|
|||||||
):
|
):
|
||||||
"""Test target temperature high property."""
|
"""Test target temperature high property."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_cooling_setpoint": 1700,
|
||||||
):
|
"system_mode": Thermostat.SystemMode.Auto,
|
||||||
device_climate = await device_climate_mock(
|
"unoccupied_cooling_setpoint": unoccupied,
|
||||||
CLIMATE_SINOPE,
|
},
|
||||||
{
|
manuf=MANUF_SINOPE,
|
||||||
"occupied_cooling_setpoint": 1700,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"system_mode": Thermostat.SystemMode.Auto,
|
)
|
||||||
"unoccupied_cooling_setpoint": unoccupied,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
if preset:
|
if preset:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -538,20 +544,16 @@ async def test_target_temperature_low(
|
|||||||
):
|
):
|
||||||
"""Test target temperature low property."""
|
"""Test target temperature low property."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_heating_setpoint": 2100,
|
||||||
):
|
"system_mode": Thermostat.SystemMode.Auto,
|
||||||
device_climate = await device_climate_mock(
|
"unoccupied_heating_setpoint": unoccupied,
|
||||||
CLIMATE_SINOPE,
|
},
|
||||||
{
|
manuf=MANUF_SINOPE,
|
||||||
"occupied_heating_setpoint": 2100,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"system_mode": Thermostat.SystemMode.Auto,
|
)
|
||||||
"unoccupied_heating_setpoint": unoccupied,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
if preset:
|
if preset:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -748,22 +750,18 @@ async def test_set_temperature_hvac_mode(hass, device_climate):
|
|||||||
async def test_set_temperature_heat_cool(hass, device_climate_mock):
|
async def test_set_temperature_heat_cool(hass, device_climate_mock):
|
||||||
"""Test setting temperature service call in heating/cooling HVAC mode."""
|
"""Test setting temperature service call in heating/cooling HVAC mode."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_cooling_setpoint": 2500,
|
||||||
):
|
"occupied_heating_setpoint": 2000,
|
||||||
device_climate = await device_climate_mock(
|
"system_mode": Thermostat.SystemMode.Auto,
|
||||||
CLIMATE_SINOPE,
|
"unoccupied_heating_setpoint": 1600,
|
||||||
{
|
"unoccupied_cooling_setpoint": 2700,
|
||||||
"occupied_cooling_setpoint": 2500,
|
},
|
||||||
"occupied_heating_setpoint": 2000,
|
manuf=MANUF_SINOPE,
|
||||||
"system_mode": Thermostat.SystemMode.Auto,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"unoccupied_heating_setpoint": 1600,
|
)
|
||||||
"unoccupied_cooling_setpoint": 2700,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
||||||
|
|
||||||
@ -838,22 +836,18 @@ async def test_set_temperature_heat_cool(hass, device_climate_mock):
|
|||||||
async def test_set_temperature_heat(hass, device_climate_mock):
|
async def test_set_temperature_heat(hass, device_climate_mock):
|
||||||
"""Test setting temperature service call in heating HVAC mode."""
|
"""Test setting temperature service call in heating HVAC mode."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_cooling_setpoint": 2500,
|
||||||
):
|
"occupied_heating_setpoint": 2000,
|
||||||
device_climate = await device_climate_mock(
|
"system_mode": Thermostat.SystemMode.Heat,
|
||||||
CLIMATE_SINOPE,
|
"unoccupied_heating_setpoint": 1600,
|
||||||
{
|
"unoccupied_cooling_setpoint": 2700,
|
||||||
"occupied_cooling_setpoint": 2500,
|
},
|
||||||
"occupied_heating_setpoint": 2000,
|
manuf=MANUF_SINOPE,
|
||||||
"system_mode": Thermostat.SystemMode.Heat,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"unoccupied_heating_setpoint": 1600,
|
)
|
||||||
"unoccupied_cooling_setpoint": 2700,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
||||||
|
|
||||||
@ -921,22 +915,18 @@ async def test_set_temperature_heat(hass, device_climate_mock):
|
|||||||
async def test_set_temperature_cool(hass, device_climate_mock):
|
async def test_set_temperature_cool(hass, device_climate_mock):
|
||||||
"""Test setting temperature service call in cooling HVAC mode."""
|
"""Test setting temperature service call in cooling HVAC mode."""
|
||||||
|
|
||||||
with patch.object(
|
device_climate = await device_climate_mock(
|
||||||
zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
|
CLIMATE_SINOPE,
|
||||||
"ep_attribute",
|
{
|
||||||
"sinope_manufacturer_specific",
|
"occupied_cooling_setpoint": 2500,
|
||||||
):
|
"occupied_heating_setpoint": 2000,
|
||||||
device_climate = await device_climate_mock(
|
"system_mode": Thermostat.SystemMode.Cool,
|
||||||
CLIMATE_SINOPE,
|
"unoccupied_cooling_setpoint": 1600,
|
||||||
{
|
"unoccupied_heating_setpoint": 2700,
|
||||||
"occupied_cooling_setpoint": 2500,
|
},
|
||||||
"occupied_heating_setpoint": 2000,
|
manuf=MANUF_SINOPE,
|
||||||
"system_mode": Thermostat.SystemMode.Cool,
|
quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
|
||||||
"unoccupied_cooling_setpoint": 1600,
|
)
|
||||||
"unoccupied_heating_setpoint": 2700,
|
|
||||||
},
|
|
||||||
manuf=MANUF_SINOPE,
|
|
||||||
)
|
|
||||||
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
entity_id = await find_entity_id(DOMAIN, device_climate, hass)
|
||||||
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
thrm_cluster = device_climate.device.endpoints[1].thermostat
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user