mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
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:
parent
95a6a7502a
commit
bfafa77016
@ -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)
|
||||||
|
@ -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,
|
||||||
**{
|
**{
|
||||||
|
@ -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
|
||||||
|
@ -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 = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user