mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Add zwave_js thermostat fan mode and fan state support (#46793)
* add thermostat fan mode and fan state support * return when fan mode is not supported * use get just in case * validate state key is in states so we dont have to use get * pylint
This commit is contained in:
parent
32bec5ea63
commit
4d23ffacd1
@ -1,5 +1,5 @@
|
||||
"""Representation of Z-Wave thermostats."""
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
from typing import Any, Callable, Dict, List, Optional, cast
|
||||
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.const import (
|
||||
@ -33,6 +33,7 @@ from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_NONE,
|
||||
SUPPORT_FAN_MODE,
|
||||
SUPPORT_PRESET_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE,
|
||||
SUPPORT_TARGET_TEMPERATURE_RANGE,
|
||||
@ -81,6 +82,8 @@ HVAC_CURRENT_MAP: Dict[int, str] = {
|
||||
ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: CURRENT_HVAC_HEAT,
|
||||
}
|
||||
|
||||
ATTR_FAN_STATE = "fan_state"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable
|
||||
@ -148,6 +151,16 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
add_to_watched_value_ids=True,
|
||||
check_all_endpoints=True,
|
||||
)
|
||||
self._fan_mode = self.get_zwave_value(
|
||||
THERMOSTAT_MODE_PROPERTY,
|
||||
CommandClass.THERMOSTAT_FAN_MODE,
|
||||
add_to_watched_value_ids=True,
|
||||
)
|
||||
self._fan_state = self.get_zwave_value(
|
||||
THERMOSTAT_OPERATING_STATE_PROPERTY,
|
||||
CommandClass.THERMOSTAT_FAN_STATE,
|
||||
add_to_watched_value_ids=True,
|
||||
)
|
||||
self._set_modes_and_presets()
|
||||
|
||||
def _setpoint_value(self, setpoint_type: ThermostatSetpointType) -> ZwaveValue:
|
||||
@ -275,6 +288,40 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
"""Return a list of available preset modes."""
|
||||
return list(self._hvac_presets)
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> Optional[str]:
|
||||
"""Return the fan setting."""
|
||||
if (
|
||||
self._fan_mode
|
||||
and self._fan_mode.value is not None
|
||||
and str(self._fan_mode.value) in self._fan_mode.metadata.states
|
||||
):
|
||||
return cast(str, self._fan_mode.metadata.states[str(self._fan_mode.value)])
|
||||
return None
|
||||
|
||||
@property
|
||||
def fan_modes(self) -> Optional[List[str]]:
|
||||
"""Return the list of available fan modes."""
|
||||
if self._fan_mode and self._fan_mode.metadata.states:
|
||||
return list(self._fan_mode.metadata.states.values())
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self) -> Optional[Dict[str, str]]:
|
||||
"""Return the optional state attributes."""
|
||||
if (
|
||||
self._fan_state
|
||||
and self._fan_state.value is not None
|
||||
and str(self._fan_state.value) in self._fan_state.metadata.states
|
||||
):
|
||||
return {
|
||||
ATTR_FAN_STATE: self._fan_state.metadata.states[
|
||||
str(self._fan_state.value)
|
||||
]
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
@ -283,8 +330,28 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
|
||||
support |= SUPPORT_TARGET_TEMPERATURE
|
||||
if len(self._current_mode_setpoint_enums) > 1:
|
||||
support |= SUPPORT_TARGET_TEMPERATURE_RANGE
|
||||
if self._fan_mode:
|
||||
support |= SUPPORT_FAN_MODE
|
||||
return support
|
||||
|
||||
async def async_set_fan_mode(self, fan_mode: str) -> None:
|
||||
"""Set new target fan mode."""
|
||||
if not self._fan_mode:
|
||||
return
|
||||
|
||||
try:
|
||||
new_state = int(
|
||||
next(
|
||||
state
|
||||
for state, label in self._fan_mode.metadata.states.items()
|
||||
if label == fan_mode
|
||||
)
|
||||
)
|
||||
except StopIteration:
|
||||
raise ValueError(f"Received an invalid fan mode: {fan_mode}") from None
|
||||
|
||||
await self.info.node.async_set_value(self._fan_mode, new_state)
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
assert self.hass
|
||||
|
@ -5,6 +5,7 @@ from zwave_js_server.event import Event
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_CURRENT_HUMIDITY,
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HVAC_ACTION,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_HVAC_MODES,
|
||||
@ -19,10 +20,12 @@ from homeassistant.components.climate.const import (
|
||||
HVAC_MODE_HEAT_COOL,
|
||||
HVAC_MODE_OFF,
|
||||
PRESET_NONE,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
)
|
||||
from homeassistant.components.zwave_js.climate import ATTR_FAN_STATE
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
|
||||
|
||||
CLIMATE_RADIO_THERMOSTAT_ENTITY = "climate.z_wave_thermostat"
|
||||
@ -50,6 +53,8 @@ async def test_thermostat_v2(
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 22.2
|
||||
assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_IDLE
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_NONE
|
||||
assert state.attributes[ATTR_FAN_MODE] == "Auto low"
|
||||
assert state.attributes[ATTR_FAN_STATE] == "Idle / off"
|
||||
|
||||
# Test setting preset mode
|
||||
await hass.services.async_call(
|
||||
@ -329,6 +334,57 @@ async def test_thermostat_v2(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test setting fan mode
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||
ATTR_FAN_MODE: "Low",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
args = client.async_send_command.call_args[0][0]
|
||||
assert args["command"] == "node.set_value"
|
||||
assert args["nodeId"] == 13
|
||||
assert args["valueId"] == {
|
||||
"endpoint": 1,
|
||||
"commandClass": 68,
|
||||
"commandClassName": "Thermostat Fan Mode",
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"ccVersion": 0,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": True,
|
||||
"writeable": True,
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"states": {"0": "Auto low", "1": "Low"},
|
||||
"label": "Thermostat fan mode",
|
||||
},
|
||||
"value": 0,
|
||||
}
|
||||
assert args["value"] == 1
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test setting invalid fan mode
|
||||
with pytest.raises(ValueError):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
{
|
||||
ATTR_ENTITY_ID: CLIMATE_RADIO_THERMOSTAT_ENTITY,
|
||||
ATTR_FAN_MODE: "fake value",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_thermostat_different_endpoints(
|
||||
hass, client, climate_radio_thermostat_ct100_plus_different_endpoints, integration
|
||||
|
@ -594,6 +594,52 @@
|
||||
"propertyName": "manufacturerData",
|
||||
"metadata": { "type": "any", "readable": true, "writeable": true }
|
||||
},
|
||||
{
|
||||
"endpoint": 1,
|
||||
"commandClass": 68,
|
||||
"commandClassName": "Thermostat Fan Mode",
|
||||
"property": "mode",
|
||||
"propertyName": "mode",
|
||||
"ccVersion": 0,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"states": { "0": "Auto low", "1": "Low" },
|
||||
"label": "Thermostat fan mode"
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"endpoint": 1,
|
||||
"commandClass": 69,
|
||||
"commandClassName": "Thermostat Fan State",
|
||||
"property": "state",
|
||||
"propertyName": "state",
|
||||
"ccVersion": 0,
|
||||
"metadata": {
|
||||
"type": "number",
|
||||
"readable": true,
|
||||
"writeable": true,
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"states": {
|
||||
"0": "Idle / off",
|
||||
"1": "Running / running low",
|
||||
"2": "Running high",
|
||||
"3": "Running medium",
|
||||
"4": "Circulation mode",
|
||||
"5": "Humidity circulation mode",
|
||||
"6": "Right - left circulation mode",
|
||||
"7": "Up - down circulation mode",
|
||||
"8": "Quiet circulation mode"
|
||||
},
|
||||
"label": "Thermostat fan state"
|
||||
},
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"commandClassName": "Thermostat Operating State",
|
||||
"commandClass": 66,
|
||||
|
Loading…
x
Reference in New Issue
Block a user