diff --git a/homeassistant/components/zwave/climate.py b/homeassistant/components/zwave/climate.py index 81b37aa5cb6..2b421db70b5 100644 --- a/homeassistant/components/zwave/climate.py +++ b/homeassistant/components/zwave/climate.py @@ -1,7 +1,7 @@ """Support for Z-Wave climate devices.""" # Because we do not compile openzwave on CI import logging -from typing import Optional +from typing import Optional, Tuple from homeassistant.components.climate import ClimateDevice 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.helpers.dispatcher import async_dispatcher_connect -from . import ZWaveDeviceEntity +from . import ZWaveDeviceEntity, const _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): """Create Z-Wave entity device.""" 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.""" def __init__(self, values, temp_unit): @@ -188,18 +192,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._zxt_120 = 1 self.update_properties() - def _current_mode_setpoints(self): - 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) + def _mode(self) -> None: + """Return thermostat mode Z-Wave value.""" + raise NotImplementedError() + + def _current_mode_setpoints(self) -> Tuple: + """Return a tuple of current setpoint Z-Wave value(s).""" + raise NotImplementedError() @property def supported_features(self): """Return the list of supported features.""" 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 - if PRESET_AWAY in self._preset_list: + if self._preset_list and PRESET_AWAY in self._preset_list: support |= SUPPORT_TARGET_TEMPERATURE_RANGE if self.values.fan_mode: @@ -237,13 +244,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def _update_operation_mode(self): """Update hvac and preset modes.""" - if self.values.primary: + if self._mode(): self._hvac_list = [] self._hvac_mapping = {} self._preset_list = [] self._preset_mapping = {} - mode_list = self.values.primary.data_items + mode_list = self._mode().data_items if mode_list: for mode in mode_list: ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower()) @@ -271,7 +278,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): # Presets are supported self._preset_list.append(PRESET_NONE) - current_mode = self.values.primary.data + current_mode = self._mode().data _LOGGER.debug("current_mode=%s", current_mode) _hvac_temp = next( ( @@ -424,7 +431,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be one of HVAC_MODE_*. """ - if self.values.primary: + if self._mode(): return self._hvac_mode return self._default_hvac_mode @@ -434,7 +441,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be a subset of HVAC_MODES. """ - if self.values.primary: + if self._mode(): return self._hvac_list return [] @@ -451,7 +458,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Return true if aux heater.""" if not self._aux_heat: return None - if self.values.primary.data == AUX_HEAT_ZWAVE_MODE: + if self._mode().data == AUX_HEAT_ZWAVE_MODE: return True return False @@ -461,7 +468,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be one of PRESET_*. """ - if self.values.primary: + if self._mode(): return self._preset_mode return PRESET_NONE @@ -471,7 +478,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): Need to be a subset of PRESET_MODES. """ - if self.values.primary: + if self._mode(): return self._preset_list return [] @@ -520,11 +527,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def set_hvac_mode(self, hvac_mode): """Set new target hvac mode.""" _LOGGER.debug("Set hvac_mode to %s", hvac_mode) - if not self.values.primary: + if not self._mode(): return operation_mode = self._hvac_mapping.get(hvac_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): """Turn auxillary heater on.""" @@ -532,7 +539,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): return operation_mode = AUX_HEAT_ZWAVE_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): """Turn auxillary heater off.""" @@ -543,23 +550,23 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): else: operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF) _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): """Set new target preset mode.""" _LOGGER.debug("Set preset_mode to %s", preset_mode) - if not self.values.primary: + if not self._mode(): return if preset_mode == PRESET_NONE: # Activate the current hvac mode self._update_operation_mode() operation_mode = self._hvac_mapping.get(self.hvac_mode) _LOGGER.debug("Set operation_mode to %s", operation_mode) - self.values.primary.data = operation_mode + self._mode().data = operation_mode else: operation_mode = self._preset_mapping.get(preset_mode, preset_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): """Set new target swing mode.""" @@ -575,3 +582,37 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if self._fan_action: data[ATTR_FAN_ACTION] = self._fan_action 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) diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index 2d6f08169ea..5e4b83d81e1 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -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.GENERIC_TYPE_THERMOSTAT, 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( DEFAULT_VALUES_SCHEMA, **{ diff --git a/tests/components/zwave/test_climate.py b/tests/components/zwave/test_climate.py index b820e496226..631bf0a0ce8 100644 --- a/tests/components/zwave/test_climate.py +++ b/tests/components/zwave/test_climate.py @@ -15,14 +15,18 @@ from homeassistant.components.climate.const import ( PRESET_BOOST, PRESET_ECO, PRESET_NONE, + SUPPORT_AUX_HEAT, SUPPORT_FAN_MODE, SUPPORT_PRESET_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_RANGE, ) -from homeassistant.components.zwave import climate -from homeassistant.components.zwave.climate import DEFAULT_HVAC_MODES +from homeassistant.components.zwave import climate, const +from homeassistant.components.zwave.climate import ( + AUX_HEAT_ZWAVE_MODE, + DEFAULT_HVAC_MODES, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from tests.mock.zwave import MockEntityValues, MockNode, MockValue, value_changed @@ -34,6 +38,7 @@ def device(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -62,6 +67,7 @@ def device_zxt_120(hass, mock_openzwave): values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -90,6 +96,7 @@ def device_mapping(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data="Heat", data_items=["Off", "Cool", "Heat", "Full Power", "Auto"], node=node, @@ -112,6 +119,7 @@ def device_unknown(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data="Heat", data_items=["Off", "Cool", "Heat", "heat_cool", "Abcdefg"], node=node, @@ -134,6 +142,7 @@ def device_heat_cool(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT, data_items=[ HVAC_MODE_OFF, @@ -162,6 +171,7 @@ def device_heat_cool_range(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT_COOL, data_items=[ HVAC_MODE_OFF, @@ -189,6 +199,7 @@ def device_heat_cool_away(hass, mock_openzwave): node = MockNode() values = MockEntityValues( primary=MockValue( + command_class=const.COMMAND_CLASS_THERMOSTAT_MODE, data=HVAC_MODE_HEAT_COOL, data_items=[ HVAC_MODE_OFF, @@ -219,6 +230,7 @@ def device_heat_eco(hass, mock_openzwave): 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, "heat econ"], node=node, @@ -235,6 +247,100 @@ def device_heat_eco(hass, mock_openzwave): 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(): """Test wether all hvac modes are included in default_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): """Test supported features flags with swing mode.""" 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): """Test operation of the zxt 120 swing mode.""" device = device_zxt_120 @@ -331,6 +470,22 @@ def test_data_lists(device): 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): """Test data lists from zwave value items.""" device = device_mapping @@ -404,6 +559,14 @@ def test_target_value_set_eco(device_heat_eco): 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): """Test values changed for climate device.""" 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 +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): """Test values changed for climate device.""" assert device.current_temperature == 5 @@ -677,3 +849,44 @@ def test_fan_action_value_changed(device): device.values.fan_action.data = 9 value_changed(device.values.fan_action) 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 diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 8f717b2903c..36c91823220 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -573,7 +573,11 @@ async def test_value_discovery_existing_entity(hass, mock_openzwave): 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( data="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): """Test power attribute.""" mock_receivers = []