Skip to content

Commit bfafa77

Browse files
oandrewcgarwood
authored andcommitted
Fix support for legacy Z-Wave thermostats (home-assistant#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.
1 parent 95a6a75 commit bfafa77

File tree

4 files changed

+372
-29
lines changed

4 files changed

+372
-29
lines changed

homeassistant/components/zwave/climate.py

+66-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Support for Z-Wave climate devices."""
22
# Because we do not compile openzwave on CI
33
import logging
4-
from typing import Optional
4+
from typing import Optional, Tuple
55

66
from homeassistant.components.climate import ClimateDevice
77
from homeassistant.components.climate.const import (
@@ -34,7 +34,7 @@
3434
from homeassistant.core import callback
3535
from homeassistant.helpers.dispatcher import async_dispatcher_connect
3636

37-
from . import ZWaveDeviceEntity
37+
from . import ZWaveDeviceEntity, const
3838

3939
_LOGGER = logging.getLogger(__name__)
4040

@@ -147,10 +147,14 @@ def async_add_climate(climate):
147147
def get_device(hass, values, **kwargs):
148148
"""Create Z-Wave entity device."""
149149
temp_unit = hass.config.units.temperature_unit
150-
return ZWaveClimate(values, temp_unit)
150+
if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_SETPOINT:
151+
return ZWaveClimateSingleSetpoint(values, temp_unit)
152+
if values.primary.command_class == const.COMMAND_CLASS_THERMOSTAT_MODE:
153+
return ZWaveClimateMultipleSetpoint(values, temp_unit)
154+
return None
151155

152156

153-
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
157+
class ZWaveClimateBase(ZWaveDeviceEntity, ClimateDevice):
154158
"""Representation of a Z-Wave Climate device."""
155159

156160
def __init__(self, values, temp_unit):
@@ -188,18 +192,21 @@ def __init__(self, values, temp_unit):
188192
self._zxt_120 = 1
189193
self.update_properties()
190194

191-
def _current_mode_setpoints(self):
192-
current_mode = str(self.values.primary.data).lower()
193-
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ())
194-
return tuple(getattr(self.values, name, None) for name in setpoints_names)
195+
def _mode(self) -> None:
196+
"""Return thermostat mode Z-Wave value."""
197+
raise NotImplementedError()
198+
199+
def _current_mode_setpoints(self) -> Tuple:
200+
"""Return a tuple of current setpoint Z-Wave value(s)."""
201+
raise NotImplementedError()
195202

196203
@property
197204
def supported_features(self):
198205
"""Return the list of supported features."""
199206
support = SUPPORT_TARGET_TEMPERATURE
200-
if HVAC_MODE_HEAT_COOL in self._hvac_list:
207+
if self._hvac_list and HVAC_MODE_HEAT_COOL in self._hvac_list:
201208
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
202-
if PRESET_AWAY in self._preset_list:
209+
if self._preset_list and PRESET_AWAY in self._preset_list:
203210
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
204211

205212
if self.values.fan_mode:
@@ -237,13 +244,13 @@ def update_properties(self):
237244

238245
def _update_operation_mode(self):
239246
"""Update hvac and preset modes."""
240-
if self.values.primary:
247+
if self._mode():
241248
self._hvac_list = []
242249
self._hvac_mapping = {}
243250
self._preset_list = []
244251
self._preset_mapping = {}
245252

246-
mode_list = self.values.primary.data_items
253+
mode_list = self._mode().data_items
247254
if mode_list:
248255
for mode in mode_list:
249256
ha_mode = HVAC_STATE_MAPPINGS.get(str(mode).lower())
@@ -271,7 +278,7 @@ def _update_operation_mode(self):
271278
# Presets are supported
272279
self._preset_list.append(PRESET_NONE)
273280

274-
current_mode = self.values.primary.data
281+
current_mode = self._mode().data
275282
_LOGGER.debug("current_mode=%s", current_mode)
276283
_hvac_temp = next(
277284
(
@@ -424,7 +431,7 @@ def hvac_mode(self):
424431
425432
Need to be one of HVAC_MODE_*.
426433
"""
427-
if self.values.primary:
434+
if self._mode():
428435
return self._hvac_mode
429436
return self._default_hvac_mode
430437

@@ -434,7 +441,7 @@ def hvac_modes(self):
434441
435442
Need to be a subset of HVAC_MODES.
436443
"""
437-
if self.values.primary:
444+
if self._mode():
438445
return self._hvac_list
439446
return []
440447

@@ -451,7 +458,7 @@ def is_aux_heat(self):
451458
"""Return true if aux heater."""
452459
if not self._aux_heat:
453460
return None
454-
if self.values.primary.data == AUX_HEAT_ZWAVE_MODE:
461+
if self._mode().data == AUX_HEAT_ZWAVE_MODE:
455462
return True
456463
return False
457464

@@ -461,7 +468,7 @@ def preset_mode(self):
461468
462469
Need to be one of PRESET_*.
463470
"""
464-
if self.values.primary:
471+
if self._mode():
465472
return self._preset_mode
466473
return PRESET_NONE
467474

@@ -471,7 +478,7 @@ def preset_modes(self):
471478
472479
Need to be a subset of PRESET_MODES.
473480
"""
474-
if self.values.primary:
481+
if self._mode():
475482
return self._preset_list
476483
return []
477484

@@ -520,19 +527,19 @@ def set_fan_mode(self, fan_mode):
520527
def set_hvac_mode(self, hvac_mode):
521528
"""Set new target hvac mode."""
522529
_LOGGER.debug("Set hvac_mode to %s", hvac_mode)
523-
if not self.values.primary:
530+
if not self._mode():
524531
return
525532
operation_mode = self._hvac_mapping.get(hvac_mode)
526533
_LOGGER.debug("Set operation_mode to %s", operation_mode)
527-
self.values.primary.data = operation_mode
534+
self._mode().data = operation_mode
528535

529536
def turn_aux_heat_on(self):
530537
"""Turn auxillary heater on."""
531538
if not self._aux_heat:
532539
return
533540
operation_mode = AUX_HEAT_ZWAVE_MODE
534541
_LOGGER.debug("Aux heat on. Set operation mode to %s", operation_mode)
535-
self.values.primary.data = operation_mode
542+
self._mode().data = operation_mode
536543

537544
def turn_aux_heat_off(self):
538545
"""Turn auxillary heater off."""
@@ -543,23 +550,23 @@ def turn_aux_heat_off(self):
543550
else:
544551
operation_mode = self._hvac_mapping.get(HVAC_MODE_OFF)
545552
_LOGGER.debug("Aux heat off. Set operation mode to %s", operation_mode)
546-
self.values.primary.data = operation_mode
553+
self._mode().data = operation_mode
547554

548555
def set_preset_mode(self, preset_mode):
549556
"""Set new target preset mode."""
550557
_LOGGER.debug("Set preset_mode to %s", preset_mode)
551-
if not self.values.primary:
558+
if not self._mode():
552559
return
553560
if preset_mode == PRESET_NONE:
554561
# Activate the current hvac mode
555562
self._update_operation_mode()
556563
operation_mode = self._hvac_mapping.get(self.hvac_mode)
557564
_LOGGER.debug("Set operation_mode to %s", operation_mode)
558-
self.values.primary.data = operation_mode
565+
self._mode().data = operation_mode
559566
else:
560567
operation_mode = self._preset_mapping.get(preset_mode, preset_mode)
561568
_LOGGER.debug("Set operation_mode to %s", operation_mode)
562-
self.values.primary.data = operation_mode
569+
self._mode().data = operation_mode
563570

564571
def set_swing_mode(self, swing_mode):
565572
"""Set new target swing mode."""
@@ -575,3 +582,37 @@ def device_state_attributes(self):
575582
if self._fan_action:
576583
data[ATTR_FAN_ACTION] = self._fan_action
577584
return data
585+
586+
587+
class ZWaveClimateSingleSetpoint(ZWaveClimateBase):
588+
"""Representation of a single setpoint Z-Wave thermostat device."""
589+
590+
def __init__(self, values, temp_unit):
591+
"""Initialize the Z-Wave climate device."""
592+
ZWaveClimateBase.__init__(self, values, temp_unit)
593+
594+
def _mode(self) -> None:
595+
"""Return thermostat mode Z-Wave value."""
596+
return self.values.mode
597+
598+
def _current_mode_setpoints(self) -> Tuple:
599+
"""Return a tuple of current setpoint Z-Wave value(s)."""
600+
return (self.values.primary,)
601+
602+
603+
class ZWaveClimateMultipleSetpoint(ZWaveClimateBase):
604+
"""Representation of a multiple setpoint Z-Wave thermostat device."""
605+
606+
def __init__(self, values, temp_unit):
607+
"""Initialize the Z-Wave climate device."""
608+
ZWaveClimateBase.__init__(self, values, temp_unit)
609+
610+
def _mode(self) -> None:
611+
"""Return thermostat mode Z-Wave value."""
612+
return self.values.primary
613+
614+
def _current_mode_setpoints(self) -> Tuple:
615+
"""Return a tuple of current setpoint Z-Wave value(s)."""
616+
current_mode = str(self.values.primary.data).lower()
617+
setpoints_names = MODE_SETPOINT_MAPPINGS.get(current_mode, ())
618+
return tuple(getattr(self.values, name, None) for name in setpoints_names)

homeassistant/components/zwave/discovery_schemas.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,60 @@
4848
),
4949
},
5050
{
51-
const.DISC_COMPONENT: "climate",
51+
const.DISC_COMPONENT: "climate", # thermostat without COMMAND_CLASS_THERMOSTAT_MODE
5252
const.DISC_GENERIC_DEVICE_CLASS: [
5353
const.GENERIC_TYPE_THERMOSTAT,
5454
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
5555
],
56+
const.DISC_SPECIFIC_DEVICE_CLASS: [
57+
const.SPECIFIC_TYPE_THERMOSTAT_HEATING,
58+
const.SPECIFIC_TYPE_SETPOINT_THERMOSTAT,
59+
],
60+
const.DISC_VALUES: dict(
61+
DEFAULT_VALUES_SCHEMA,
62+
**{
63+
const.DISC_PRIMARY: {
64+
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_SETPOINT]
65+
},
66+
"temperature": {
67+
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SENSOR_MULTILEVEL],
68+
const.DISC_INDEX: [const.INDEX_SENSOR_MULTILEVEL_TEMPERATURE],
69+
const.DISC_OPTIONAL: True,
70+
},
71+
"fan_mode": {
72+
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_FAN_MODE],
73+
const.DISC_OPTIONAL: True,
74+
},
75+
"operating_state": {
76+
const.DISC_COMMAND_CLASS: [
77+
const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE
78+
],
79+
const.DISC_OPTIONAL: True,
80+
},
81+
"fan_action": {
82+
const.DISC_COMMAND_CLASS: [
83+
const.COMMAND_CLASS_THERMOSTAT_FAN_ACTION
84+
],
85+
const.DISC_OPTIONAL: True,
86+
},
87+
"mode": {
88+
const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_THERMOSTAT_MODE],
89+
const.DISC_OPTIONAL: True,
90+
},
91+
},
92+
),
93+
},
94+
{
95+
const.DISC_COMPONENT: "climate", # thermostat with COMMAND_CLASS_THERMOSTAT_MODE
96+
const.DISC_GENERIC_DEVICE_CLASS: [
97+
const.GENERIC_TYPE_THERMOSTAT,
98+
const.GENERIC_TYPE_SENSOR_MULTILEVEL,
99+
],
100+
const.DISC_SPECIFIC_DEVICE_CLASS: [
101+
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL,
102+
const.SPECIFIC_TYPE_THERMOSTAT_GENERAL_V2,
103+
const.SPECIFIC_TYPE_SETBACK_THERMOSTAT,
104+
],
56105
const.DISC_VALUES: dict(
57106
DEFAULT_VALUES_SCHEMA,
58107
**{

0 commit comments

Comments
 (0)