Ozw climate fixes (#37560)

* fix presets and mode conversion

* fix mapping issues in ozw climate

* build mapping table in advance

* Copying a dict to a list copies the keys by default
pull/37635/head
Marcel van der Veldt 2020-07-07 22:20:57 +02:00 committed by GitHub
parent 2b37cbe079
commit cbccf011e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 34 deletions

View File

@ -12,7 +12,6 @@ from homeassistant.components.climate.const import (
CURRENT_HVAC_HEAT,
CURRENT_HVAC_IDLE,
CURRENT_HVAC_OFF,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
@ -65,6 +64,16 @@ class ThermostatMode(IntEnum):
MANUFACTURER_SPECIFIC = 31
# In Z-Wave the modes and presets are both in ThermostatMode.
# This list contains thermostatmodes we should consider a mode only
MODES_LIST = [
ThermostatMode.OFF,
ThermostatMode.HEAT,
ThermostatMode.COOL,
ThermostatMode.AUTO,
ThermostatMode.AUTO_CHANGE_OVER,
]
MODE_SETPOINT_MAPPINGS = {
ThermostatMode.OFF: (),
ThermostatMode.HEAT: ("setpoint_heating",),
@ -99,11 +108,14 @@ HVAC_CURRENT_MAPPINGS = {
# Map Z-Wave HVAC Mode to Home Assistant value
# Note: We treat "auto" as "heat_cool" as most Z-Wave devices
# report auto_changeover as auto without schedule support.
ZW_HVAC_MODE_MAPPINGS = {
ThermostatMode.OFF: HVAC_MODE_OFF,
ThermostatMode.HEAT: HVAC_MODE_HEAT,
ThermostatMode.COOL: HVAC_MODE_COOL,
ThermostatMode.AUTO: HVAC_MODE_AUTO,
# Z-Wave auto mode is actually heat/cool in the hass world
ThermostatMode.AUTO: HVAC_MODE_HEAT_COOL,
ThermostatMode.AUXILIARY: HVAC_MODE_HEAT,
ThermostatMode.FAN: HVAC_MODE_FAN_ONLY,
ThermostatMode.FURNANCE: HVAC_MODE_HEAT,
@ -120,7 +132,6 @@ HVAC_MODE_ZW_MAPPINGS = {
HVAC_MODE_OFF: ThermostatMode.OFF,
HVAC_MODE_HEAT: ThermostatMode.HEAT,
HVAC_MODE_COOL: ThermostatMode.COOL,
HVAC_MODE_AUTO: ThermostatMode.AUTO,
HVAC_MODE_FAN_ONLY: ThermostatMode.FAN,
HVAC_MODE_DRY: ThermostatMode.DRY,
HVAC_MODE_HEAT_COOL: ThermostatMode.AUTO_CHANGE_OVER,
@ -148,12 +159,16 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
def __init__(self, values):
"""Initialize the entity."""
super().__init__(values)
self._current_mode_setpoint_values = self._get_current_mode_setpoint_values()
self._hvac_modes = {}
self._hvac_presets = {}
self.on_value_update()
@callback
def on_value_update(self):
"""Call when the underlying value(s) is added or updated."""
"""Call when the underlying values object changes."""
self._current_mode_setpoint_values = self._get_current_mode_setpoint_values()
if not self._hvac_modes:
self._set_modes_and_presets()
@property
def hvac_mode(self):
@ -161,21 +176,13 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
if not self.values.mode:
return None
return ZW_HVAC_MODE_MAPPINGS.get(
self.values.mode.value[VALUE_SELECTED_ID], HVAC_MODE_AUTO
self.values.mode.value[VALUE_SELECTED_ID], HVAC_MODE_HEAT_COOL
)
@property
def hvac_modes(self):
"""Return the list of available hvac operation modes."""
if not self.values.mode:
return []
# Z-Wave uses one list for both modes and presets. Extract the unique modes
all_modes = []
for val in self.values.mode.value[VALUE_LIST]:
hass_mode = ZW_HVAC_MODE_MAPPINGS.get(val[VALUE_ID])
if hass_mode and hass_mode not in all_modes:
all_modes.append(hass_mode)
return all_modes
return list(self._hvac_modes)
@property
def fan_mode(self):
@ -212,20 +219,15 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
@property
def preset_mode(self):
"""Return preset operation ie. eco, away."""
# Z-Wave uses mode-values > 10 for presets
if self.values.mode.value[VALUE_SELECTED_ID] > 10:
# A Zwave mode that can not be translated to a hass mode is considered a preset
if self.values.mode.value[VALUE_SELECTED_ID] not in MODES_LIST:
return self.values.mode.value[VALUE_SELECTED_LABEL]
return PRESET_NONE
@property
def preset_modes(self):
"""Return the list of available preset operation modes."""
# Z-Wave uses mode-values > 10 for presets
return [PRESET_NONE] + [
val[VALUE_LABEL]
for val in self.values.mode.value[VALUE_LIST]
if val[VALUE_ID] > 10
]
return list(self._hvac_presets)
@property
def target_temperature(self):
@ -272,12 +274,10 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode):
"""Set new target hvac mode."""
if not self.values.mode:
return
if hvac_mode not in self.hvac_modes:
hvac_mode_value = self._hvac_modes.get(hvac_mode)
if not hvac_mode_value:
_LOGGER.warning("Received an invalid hvac mode: %s", hvac_mode)
return
hvac_mode_value = HVAC_MODE_ZW_MAPPINGS.get(hvac_mode)
self.values.mode.send_value(hvac_mode_value)
async def async_set_preset_mode(self, preset_mode):
@ -286,9 +286,7 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
# try to restore to the (translated) main hvac mode
await self.async_set_hvac_mode(self.hvac_mode)
return
preset_mode_value = _get_list_id(
self.values.mode.value[VALUE_LIST], preset_mode
)
preset_mode_value = self._hvac_presets.get(preset_mode)
if preset_mode_value is None:
_LOGGER.warning("Received an invalid preset mode: %s", preset_mode)
return
@ -331,6 +329,25 @@ class ZWaveClimateEntity(ZWaveDeviceEntity, ClimateEntity):
if getattr(self.values, value_name, None)
)
def _set_modes_and_presets(self):
"""Convert Z-Wave Thermostat modes into Home Assistant modes and presets."""
if not self.values.mode:
return
all_modes = {}
all_presets = {PRESET_NONE: None}
# Z-Wave uses one list for both modes and presets.
# Iterate over all Z-Wave ThermostatModes and extract the hvac modes and presets.
for val in self.values.mode.value[VALUE_LIST]:
if val[VALUE_ID] in MODES_LIST:
# treat value as hvac mode
hass_mode = ZW_HVAC_MODE_MAPPINGS.get(val[VALUE_ID])
all_modes[hass_mode] = val[VALUE_ID]
else:
# treat value as hvac preset
all_presets[val[VALUE_LABEL]] = val[VALUE_ID]
self._hvac_modes = all_modes
self._hvac_presets = all_presets
def _get_list_id(value_lst, value_lbl):
"""Return the id for the value in the list."""

View File

@ -10,9 +10,9 @@ from homeassistant.components.climate.const import (
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_IDLE,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_HEAT_COOL,
HVAC_MODE_OFF,
)
@ -32,7 +32,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog):
HVAC_MODE_OFF,
HVAC_MODE_HEAT,
HVAC_MODE_COOL,
HVAC_MODE_AUTO,
HVAC_MODE_HEAT_COOL,
]
assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 23.1
@ -60,7 +60,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog):
await hass.services.async_call(
"climate",
"set_hvac_mode",
{"entity_id": "climate.ct32_thermostat_mode", "hvac_mode": HVAC_MODE_AUTO},
{"entity_id": "climate.ct32_thermostat_mode", "hvac_mode": HVAC_MODE_HEAT_COOL},
blocking=True,
)
assert len(sent_messages) == 2
@ -106,7 +106,7 @@ async def test_climate(hass, climate_data, sent_messages, climate_msg, caplog):
await hass.async_block_till_done()
state = hass.states.get("climate.ct32_thermostat_mode")
assert state is not None
assert state.state == HVAC_MODE_AUTO
assert state.state == HVAC_MODE_HEAT_COOL
assert state.attributes.get(ATTR_TEMPERATURE) is None
assert state.attributes[ATTR_TARGET_TEMP_LOW] == 21.1
assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.6