Fix support for legacy Z-Wave thermostats (#29955)

This brings back support for Z-Wave thermostats
of SETPOINT_THERMOSTAT specific device class.
Such devices don't have COMMAND_CLASS_THERMOSTAT_MODE
and are now handled separately.
This commit is contained in:
Andrew Onyshchuk 2019-12-15 19:02:18 -06:00 committed by Charles Garwood
parent 95a6a7502a
commit bfafa77016
4 changed files with 372 additions and 29 deletions

View File

@ -1,7 +1,7 @@
"""Support for Z-Wave climate devices.""" """Support for Z-Wave climate devices."""
# Because we do not compile openzwave on CI # Because we do not compile openzwave on CI
import logging import logging
from typing import Optional from typing import Optional, Tuple
from homeassistant.components.climate import ClimateDevice from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
@ -34,7 +34,7 @@ from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from . import ZWaveDeviceEntity from . import ZWaveDeviceEntity, const
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -147,10 +147,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
def get_device(hass, values, **kwargs): def get_device(hass, values, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
temp_unit = hass.config.units.temperature_unit temp_unit = hass.config.units.temperature_unit
return ZWaveClimate(values, temp_unit) if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_SETPOINT:
return ZWaveClimateSingleSetpoint(values, temp_unit)
if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_MODE:
return ZWaveClimateMultipleSetpoint(values, temp_unit)
return None
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): class ZWaveClimateBase(ZWaveDeviceEntity, ClimateDevice):
"""Representation of a Z-Wave Climate device.""" """Representation of a Z-Wave Climate device."""
def __init__(self, values, temp_unit): def __init__(self, values, temp_unit):
@ -188,18 +192,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._zxt_120 = 1 self._zxt_120 = 1
self.update_properties() self.update_properties()
def _current_mode_setpoints(self): def _mode(self) -> None:
current_mode = str(self.values.primary.data).lower() """Return thermostat mode Z-Wave value."""
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ()) raise NotImplementedError()
return tuple(getattr(self.values, name, None) for name in setpoints_names)
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
raise NotImplementedError()
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE support = SUPPORT_TARGET_TEMPERATURE
if HVAC_MODE_HEAT_COOL in self._hvac_list: if self._hvac_list and HVAC_MODE_HEAT_COOL in self._hvac_list:
support |= SUPPORT_TARGET_TEMPERATURE_RANGE support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if PRESET_AWAY in self._preset_list: if self._preset_list and PRESET_AWAY in self._preset_list:
support |= SUPPORT_TARGET_TEMPERATURE_RANGE support |= SUPPORT_TARGET_TEMPERATURE_RANGE
if self.values.fan_mode: if self.values.fan_mode:
@ -237,13 +244,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def _update_operation_mode(self): def _update_operation_mode(self):
"""Update hvac and preset modes.""" """Update hvac and preset modes."""
if self.values.primary: if self._mode():
self._hvac_list = [] self._hvac_list = []
self._hvac_mapping = {} self._hvac_mapping = {}
self._preset_list = [] self._preset_list = []
self._preset_mapping = {} self._preset_mapping = {}
mode_list = self.values.primary.data_items mode_list = self._mode().data_items
if mode_list: if mode_list:
for mode in mode_list: for mode in mode_list:
ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower()) ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower())
@ -271,7 +278,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
# Presets are supported # Presets are supported
self._preset_list.append(PRESET_NONE) self._preset_list.append(PRESET_NONE)
current_mode = self.values.primary.data current_mode = self._mode().data
_LOGGER.debug("current_mode=%s", current_mode) _LOGGER.debug("current_mode=%s", current_mode)
_hvac_temp = next( _hvac_temp = next(
( (
@ -424,7 +431,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be one of HVAC_MODE_*. Need to be one of HVAC_MODE_*.
""" """
if self.values.primary: if self._mode():
return self._hvac_mode return self._hvac_mode
return self._default_hvac_mode return self._default_hvac_mode
@ -434,7 +441,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be a subset of HVAC_MODES. Need to be a subset of HVAC_MODES.
""" """
if self.values.primary: if self._mode():
return self._hvac_list return self._hvac_list
return [] return []
@ -451,7 +458,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return true if aux heater.""" """Return true if aux heater."""
if not self._aux_heat: if not self._aux_heat:
return None return None
if self.values.primary.data == AUX_HEAT_ZWAVE_MODE: if self._mode().data == AUX_HEAT_ZWAVE_MODE:
return True return True
return False return False
@ -461,7 +468,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be one of PRESET_*. Need to be one of PRESET_*.
""" """
if self.values.primary: if self._mode():
return self._preset_mode return self._preset_mode
return PRESET_NONE return PRESET_NONE
@ -471,7 +478,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
Need to be a subset of PRESET_MODES. Need to be a subset of PRESET_MODES.
""" """
if self.values.primary: if self._mode():
return self._preset_list return self._preset_list
return [] return []
@ -520,11 +527,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_hvac_mode(self, hvac_mode): def set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode.""" """Set new target hvac mode."""
_LOGGER.debug("Set hvac_mode to %s", hvac_mode) _LOGGER.debug("Set hvac_mode to %s", hvac_mode)
if not self.values.primary: if not self._mode():
return return
operation_mode = self._hvac_mapping.get(hvac_mode) operation_mode = self._hvac_mapping.get(hvac_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode self._mode().data = operation_mode
def turn_aux_heat_on(self): def turn_aux_heat_on(self):
"""Turn auxillary heater on.""" """Turn auxillary heater on."""
@ -532,7 +539,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return return
operation_mode = AUX_HEAT_ZWAVE_MODE operation_mode = AUX_HEAT_ZWAVE_MODE
_LOGGER.debug("Aux heat on. Set operation mode to %s", operation_mode) _LOGGER.debug("Aux heat on. Set operation mode to %s", operation_mode)
self.values.primary.data = operation_mode self._mode().data = operation_mode
def turn_aux_heat_off(self): def turn_aux_heat_off(self):
"""Turn auxillary heater off.""" """Turn auxillary heater off."""
@ -543,23 +550,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
else: else:
operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF) operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF)
_LOGGER.debug("Aux heat off. Set operation mode to %s", operation_mode) _LOGGER.debug("Aux heat off. Set operation mode to %s", operation_mode)
self.values.primary.data = operation_mode self._mode().data = operation_mode
def set_preset_mode(self, preset_mode): def set_preset_mode(self, preset_mode):
"""Set new target preset mode.""" """Set new target preset mode."""
_LOGGER.debug("Set preset_mode to %s", preset_mode) _LOGGER.debug("Set preset_mode to %s", preset_mode)
if not self.values.primary: if not self._mode():
return return
if preset_mode == PRESET_NONE: if preset_mode == PRESET_NONE:
# Activate the current hvac mode # Activate the current hvac mode
self._update_operation_mode() self._update_operation_mode()
operation_mode = self._hvac_mapping.get(self.hvac_mode) operation_mode = self._hvac_mapping.get(self.hvac_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode self._mode().data = operation_mode
else: else:
operation_mode = self._preset_mapping.get(preset_mode, preset_mode) operation_mode = self._preset_mapping.get(preset_mode, preset_mode)
_LOGGER.debug("Set operation_mode to %s", operation_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode)
self.values.primary.data = operation_mode self._mode().data = operation_mode
def set_swing_mode(self, swing_mode): def set_swing_mode(self, swing_mode):
"""Set new target swing mode.""" """Set new target swing mode."""
@ -575,3 +582,37 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
if self._fan_action: if self._fan_action:
data[ATTR_FAN_ACTION] = self._fan_action data[ATTR_FAN_ACTION] = self._fan_action
return data return data
class ZWaveClimateSingleSetpoint(ZWaveClimateBase):
"""Representation of a single setpoint Z-Wave thermostat device."""
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveClimateBase.__init__(self, values, temp_unit)
def _mode(self) -> None:
"""Return thermostat mode Z-Wave value."""
return self.values.mode
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
return (self.values.primary,)
class ZWaveClimateMultipleSetpoint(ZWaveClimateBase):
"""Representation of a multiple setpoint Z-Wave thermostat device."""
def __init__(self, values, temp_unit):
"""Initialize the Z-Wave climate device."""
ZWaveClimateBase.__init__(self, values, temp_unit)
def _mode(self) -> None:
"""Return thermostat mode Z-Wave value."""
return self.values.primary
def _current_mode_setpoints(self) -> Tuple:
"""Return a tuple of current setpoint Z-Wave value(s)."""
current_mode = str(self.values.primary.data).lower()
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ())
return tuple(getattr(self.values, name, None) for name in setpoints_names)

View File

@ -48,11 +48,60 @@ DISCOVERY_SCHEMAS = [
), ),
}, },
{ {
const.DISC_COMPONENT: "climate", const.DISC_COMPONENT: "climate", # thermostat without COMMAND_CLASS_THERMOSTAT_MODE
const.DISC_GENERIC_DEVICE_CLASS: [ const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_THERMOSTAT, const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_SENSOR_MULTILEVEL, const.GENERIC_TYPE_SENSOR_MULTILEVEL,
], ],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_THERMOSTAT_HEATING,
const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT,
],
const.DISC_VALUES: dict(
DEFAULT_VALUES_SCHEMA,
**{
const.DISC_PRIMARY: {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_SETPOINT]
},
"temperature": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL],
const.DISC_INDEX: [const.INDEX_SENSOR_MULTILEVEL_TEMPERATURE],
const.DISC_OPTIONAL: True,
},
"fan_mode": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_FAN_MODE],
const.DISC_OPTIONAL: True,
},
"operating_state": {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE
],
const.DISC_OPTIONAL: True,
},
"fan_action": {
const.DISC_COMMAND_CLASS: [
const.COMMAND_CLASS_THERMOSTAT_FAN_ACTION
],
const.DISC_OPTIONAL: True,
},
"mode": {
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_MODE],
const.DISC_OPTIONAL: True,
},
},
),
},
{
const.DISC_COMPONENT: "climate", # thermostat with COMMAND_CLASS_THERMOSTAT_MODE
const.DISC_GENERIC_DEVICE_CLASS: [
const.GENERIC_TYPE_THERMOSTAT,
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
],
const.DISC_SPECIFIC_DEVICE_CLASS: [
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL,
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2,
const.SPECIFIC_TYPE_SETBACK_THERMOSTAT,
],
const.DISC_VALUES: dict( const.DISC_VALUES: dict(
DEFAULT_VALUES_SCHEMA, DEFAULT_VALUES_SCHEMA,
**{ **{

View File

@ -15,14 +15,18 @@ from homeassistant.components.climate.const import (
PRESET_BOOST, PRESET_BOOST,
PRESET_ECO, PRESET_ECO,
PRESET_NONE, PRESET_NONE,
SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE, SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE, SUPPORT_PRESET_MODE,
SUPPORT_SWING_MODE, SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE, SUPPORT_TARGET_TEMPERATURE_RANGE,
) )
from homeassistant.components.zwave import climate from homeassistant.components.zwave import climate, const
from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES from homeassistant.components.zwave.climate import (
AUX_HEAT_ZWAVE_MODE,
DEFAULT_HVAC_MODES,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed
@ -34,6 +38,7 @@ def device(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -62,6 +67,7 @@ def device_zxt_120(hass, mock_openzwave):
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -90,6 +96,7 @@ def device_mapping(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat", data="Heat",
data_items=["Off", "Cool", "Heat", "Full Power", "Auto"], data_items=["Off", "Cool", "Heat", "Full Power", "Auto"],
node=node, node=node,
@ -112,6 +119,7 @@ def device_unknown(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data="Heat", data="Heat",
data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"], data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"],
node=node, node=node,
@ -134,6 +142,7 @@ def device_heat_cool(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -162,6 +171,7 @@ def device_heat_cool_range(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL, data=HVAC_MODE_HEAT_COOL,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -189,6 +199,7 @@ def device_heat_cool_away(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT_COOL, data=HVAC_MODE_HEAT_COOL,
data_items=[ data_items=[
HVAC_MODE_OFF, HVAC_MODE_OFF,
@ -219,6 +230,7 @@ def device_heat_eco(hass, mock_openzwave):
node = MockNode() node = MockNode()
values = MockEntityValues( values = MockEntityValues(
primary=MockValue( primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT, data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"], data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "heat econ"],
node=node, node=node,
@ -235,6 +247,100 @@ def device_heat_eco(hass, mock_openzwave):
yield device yield device
@pytest.fixture
def device_aux_heat(hass, mock_openzwave):
"""Fixture to provide a precreated climate device. aux heat."""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT, "Aux Heat"],
node=node,
),
setpoint_heating=MockValue(data=2, node=node),
setpoint_eco_heating=MockValue(data=1, node=node),
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data="test4", node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
@pytest.fixture
def device_single_setpoint(hass, mock_openzwave):
"""Fixture to provide a precreated climate device.
SETPOINT_THERMOSTAT device class.
"""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node
),
mode=None,
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
@pytest.fixture
def device_single_setpoint_with_mode(hass, mock_openzwave):
"""Fixture to provide a precreated climate device.
SETPOINT_THERMOSTAT device class with COMMAND_CLASS_THERMOSTAT_MODE command class
"""
node = MockNode()
values = MockEntityValues(
primary=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT, data=1, node=node
),
mode=MockValue(
command_class=const.COMMAND_CLASS_THERMOSTAT_MODE,
data=HVAC_MODE_HEAT,
data_items=[HVAC_MODE_OFF, HVAC_MODE_HEAT],
node=node,
),
temperature=MockValue(data=5, node=node, units=None),
fan_mode=MockValue(data="test2", data_items=[3, 4, 5], node=node),
operating_state=MockValue(data=CURRENT_HVAC_HEAT, node=node),
fan_action=MockValue(data=7, node=node),
)
device = climate.get_device(hass, node=node, values=values, node_config={})
yield device
def test_get_device_detects_none(hass, mock_openzwave):
"""Test get_device returns None."""
node = MockNode()
value = MockValue(data=0, node=node)
values = MockEntityValues(primary=value)
device = climate.get_device(hass, node=node, values=values, node_config={})
assert device is None
def test_get_device_detects_multiple_setpoint_device(device):
"""Test get_device returns a Z-Wave multiple setpoint device."""
assert isinstance(device, climate.ZWaveClimateMultipleSetpoint)
def test_get_device_detects_single_setpoint_device(device_single_setpoint):
"""Test get_device returns a Z-Wave single setpoint device."""
assert isinstance(device_single_setpoint, climate.ZWaveClimateSingleSetpoint)
def test_default_hvac_modes(): def test_default_hvac_modes():
"""Test wether all hvac modes are included in default_hvac_modes.""" """Test wether all hvac modes are included in default_hvac_modes."""
for hvac_mode in HVAC_MODES: for hvac_mode in HVAC_MODES:
@ -274,6 +380,18 @@ def test_supported_features_preset_mode(device_mapping):
) )
def test_supported_features_preset_mode_away(device_heat_cool_away):
"""Test supported features flags with swing mode."""
device = device_heat_cool_away
assert (
device.supported_features
== SUPPORT_FAN_MODE
+ SUPPORT_TARGET_TEMPERATURE
+ SUPPORT_TARGET_TEMPERATURE_RANGE
+ SUPPORT_PRESET_MODE
)
def test_supported_features_swing_mode(device_zxt_120): def test_supported_features_swing_mode(device_zxt_120):
"""Test supported features flags with swing mode.""" """Test supported features flags with swing mode."""
device = device_zxt_120 device = device_zxt_120
@ -286,6 +404,27 @@ def test_supported_features_swing_mode(device_zxt_120):
) )
def test_supported_features_aux_heat(device_aux_heat):
"""Test supported features flags with aux heat."""
device = device_aux_heat
assert (
device.supported_features
== SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE + SUPPORT_AUX_HEAT
)
def test_supported_features_single_setpoint(device_single_setpoint):
"""Test supported features flags for SETPOINT_THERMOSTAT."""
device = device_single_setpoint
assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE
def test_supported_features_single_setpoint_with_mode(device_single_setpoint_with_mode):
"""Test supported features flags for SETPOINT_THERMOSTAT."""
device = device_single_setpoint_with_mode
assert device.supported_features == SUPPORT_FAN_MODE + SUPPORT_TARGET_TEMPERATURE
def test_zxt_120_swing_mode(device_zxt_120): def test_zxt_120_swing_mode(device_zxt_120):
"""Test operation of the zxt 120 swing mode.""" """Test operation of the zxt 120 swing mode."""
device = device_zxt_120 device = device_zxt_120
@ -331,6 +470,22 @@ def test_data_lists(device):
assert device.preset_modes == [] assert device.preset_modes == []
def test_data_lists_single_setpoint(device_single_setpoint):
"""Test data lists from zwave value items."""
device = device_single_setpoint
assert device.fan_modes == [3, 4, 5]
assert device.hvac_modes == []
assert device.preset_modes == []
def test_data_lists_single_setpoint_with_mode(device_single_setpoint_with_mode):
"""Test data lists from zwave value items."""
device = device_single_setpoint_with_mode
assert device.fan_modes == [3, 4, 5]
assert device.hvac_modes == [HVAC_MODE_OFF, HVAC_MODE_HEAT]
assert device.preset_modes == []
def test_data_lists_mapping(device_mapping): def test_data_lists_mapping(device_mapping):
"""Test data lists from zwave value items.""" """Test data lists from zwave value items."""
device = device_mapping device = device_mapping
@ -404,6 +559,14 @@ def test_target_value_set_eco(device_heat_eco):
assert device.values.setpoint_eco_heating.data == 0 assert device.values.setpoint_eco_heating.data == 0
def test_target_value_set_single_setpoint(device_single_setpoint):
"""Test values changed for climate device."""
device = device_single_setpoint
assert device.values.primary.data == 1
device.set_temperature(**{ATTR_TEMPERATURE: 2})
assert device.values.primary.data == 2
def test_operation_value_set(device): def test_operation_value_set(device):
"""Test values changed for climate device.""" """Test values changed for climate device."""
assert device.values.primary.data == HVAC_MODE_HEAT assert device.values.primary.data == HVAC_MODE_HEAT
@ -546,6 +709,15 @@ def test_target_changed_with_mode(device):
assert device.target_temperature_high == 10 assert device.target_temperature_high == 10
def test_target_value_changed_single_setpoint(device_single_setpoint):
"""Test values changed for climate device."""
device = device_single_setpoint
assert device.target_temperature == 1
device.values.primary.data = 2
value_changed(device.values.primary)
assert device.target_temperature == 2
def test_temperature_value_changed(device): def test_temperature_value_changed(device):
"""Test values changed for climate device.""" """Test values changed for climate device."""
assert device.current_temperature == 5 assert device.current_temperature == 5
@ -677,3 +849,44 @@ def test_fan_action_value_changed(device):
device.values.fan_action.data = 9 device.values.fan_action.data = 9
value_changed(device.values.fan_action) value_changed(device.values.fan_action)
assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9 assert device.device_state_attributes[climate.ATTR_FAN_ACTION] == 9
def test_aux_heat_unsupported_set(device):
"""Test aux heat for climate device."""
device = device
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_on()
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_off()
assert device.values.primary.data == HVAC_MODE_HEAT
def test_aux_heat_unsupported_value_changed(device):
"""Test aux heat for climate device."""
device = device
assert device.is_aux_heat is None
device.values.primary.data = HVAC_MODE_HEAT
value_changed(device.values.primary)
assert device.is_aux_heat is None
def test_aux_heat_set(device_aux_heat):
"""Test aux heat for climate device."""
device = device_aux_heat
assert device.values.primary.data == HVAC_MODE_HEAT
device.turn_aux_heat_on()
assert device.values.primary.data == AUX_HEAT_ZWAVE_MODE
device.turn_aux_heat_off()
assert device.values.primary.data == HVAC_MODE_HEAT
def test_aux_heat_value_changed(device_aux_heat):
"""Test aux heat for climate device."""
device = device_aux_heat
assert device.is_aux_heat is False
device.values.primary.data = AUX_HEAT_ZWAVE_MODE
value_changed(device.values.primary)
assert device.is_aux_heat is True
device.values.primary.data = HVAC_MODE_HEAT
value_changed(device.values.primary)
assert device.is_aux_heat is False

View File

@ -573,7 +573,11 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave):
assert len(mock_receivers) == 1 assert len(mock_receivers) == 1
node = MockNode(node_id=11, generic=const.GENERIC_TYPE_THERMOSTAT) node = MockNode(
node_id=11,
generic=const.GENERIC_TYPE_THERMOSTAT,
specific=const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2,
)
thermostat_mode = MockValue( thermostat_mode = MockValue(
data="Heat", data="Heat",
data_items=["Off", "Heat"], data_items=["Off", "Heat"],
@ -638,6 +642,42 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave):
) )
async def test_value_discovery_legacy_thermostat(hass, mock_openzwave):
"""Test discovery of a node. Special case for legacy thermostats."""
mock_receivers = []
def mock_connect(receiver, signal, *args, **kwargs):
if signal == MockNetwork.SIGNAL_VALUE_ADDED:
mock_receivers.append(receiver)
with patch("pydispatch.dispatcher.connect", new=mock_connect):
await async_setup_component(hass, "zwave", {"zwave": {}})
await hass.async_block_till_done()
assert len(mock_receivers) == 1
node = MockNode(
node_id=11,
generic=const.GENERIC_TYPE_THERMOSTAT,
specific=const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT,
)
setpoint_heating = MockValue(
data=22.0,
node=node,
command_class=const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=1,
genre=const.GENRE_USER,
)
hass.async_add_job(mock_receivers[0], node, setpoint_heating)
await hass.async_block_till_done()
assert (
hass.states.get("climate.mock_node_mock_value").attributes["temperature"]
== 22.0
)
async def test_power_schemes(hass, mock_openzwave): async def test_power_schemes(hass, mock_openzwave):
"""Test power attribute.""" """Test power attribute."""
mock_receivers = [] mock_receivers = []