1 """Representation of Z-Wave thermostats."""
3 from __future__
import annotations
5 from typing
import Any, cast
7 from zwave_js_server.client
import Client
as ZwaveClient
8 from zwave_js_server.const
import CommandClass
9 from zwave_js_server.const.command_class.thermostat
import (
10 THERMOSTAT_CURRENT_TEMP_PROPERTY,
11 THERMOSTAT_HUMIDITY_PROPERTY,
12 THERMOSTAT_MODE_PROPERTY,
13 THERMOSTAT_MODE_SETPOINT_MAP,
14 THERMOSTAT_OPERATING_STATE_PROPERTY,
15 THERMOSTAT_SETPOINT_PROPERTY,
17 ThermostatOperatingState,
18 ThermostatSetpointType,
20 from zwave_js_server.model.driver
import Driver
21 from zwave_js_server.model.value
import Value
as ZwaveValue
25 ATTR_TARGET_TEMP_HIGH,
27 DOMAIN
as CLIMATE_DOMAIN,
41 from .const
import DATA_CLIENT, DOMAIN
42 from .discovery
import ZwaveDiscoveryInfo
43 from .discovery_data_template
import DynamicCurrentTempClimateDataTemplate
44 from .entity
import ZWaveBaseEntity
45 from .helpers
import get_value_of_zwave_value
54 ThermostatMode.AUTO_CHANGE_OVER,
62 ZW_HVAC_MODE_MAP: dict[int, HVACMode] = {
63 ThermostatMode.OFF: HVACMode.OFF,
64 ThermostatMode.HEAT: HVACMode.HEAT,
65 ThermostatMode.COOL: HVACMode.COOL,
67 ThermostatMode.AUTO: HVACMode.HEAT_COOL,
68 ThermostatMode.AUXILIARY: HVACMode.HEAT,
69 ThermostatMode.FAN: HVACMode.FAN_ONLY,
70 ThermostatMode.FURNACE: HVACMode.HEAT,
71 ThermostatMode.DRY: HVACMode.DRY,
72 ThermostatMode.AUTO_CHANGE_OVER: HVACMode.HEAT_COOL,
73 ThermostatMode.HEATING_ECON: HVACMode.HEAT,
74 ThermostatMode.COOLING_ECON: HVACMode.COOL,
75 ThermostatMode.AWAY: HVACMode.HEAT_COOL,
76 ThermostatMode.FULL_POWER: HVACMode.HEAT,
79 HVAC_CURRENT_MAP: dict[int, HVACAction] = {
80 ThermostatOperatingState.IDLE: HVACAction.IDLE,
81 ThermostatOperatingState.PENDING_HEAT: HVACAction.IDLE,
82 ThermostatOperatingState.HEATING: HVACAction.HEATING,
83 ThermostatOperatingState.PENDING_COOL: HVACAction.IDLE,
84 ThermostatOperatingState.COOLING: HVACAction.COOLING,
85 ThermostatOperatingState.FAN_ONLY: HVACAction.FAN,
86 ThermostatOperatingState.VENT_ECONOMIZER: HVACAction.FAN,
87 ThermostatOperatingState.AUX_HEATING: HVACAction.HEATING,
88 ThermostatOperatingState.SECOND_STAGE_HEATING: HVACAction.HEATING,
89 ThermostatOperatingState.SECOND_STAGE_COOLING: HVACAction.COOLING,
90 ThermostatOperatingState.SECOND_STAGE_AUX_HEAT: HVACAction.HEATING,
91 ThermostatOperatingState.THIRD_STAGE_AUX_HEAT: HVACAction.HEATING,
94 ATTR_FAN_STATE =
"fan_state"
99 config_entry: ConfigEntry,
100 async_add_entities: AddEntitiesCallback,
102 """Set up Z-Wave climate from config entry."""
103 client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
106 def async_add_climate(info: ZwaveDiscoveryInfo) ->
None:
107 """Add Z-Wave Climate."""
108 driver = client.driver
109 assert driver
is not None
110 entities: list[ZWaveBaseEntity] = []
111 if info.platform_hint ==
"dynamic_current_temp":
114 entities.append(
ZWaveClimate(config_entry, driver, info))
118 config_entry.async_on_unload(
121 f
"{DOMAIN}_{config_entry.entry_id}_add_{CLIMATE_DOMAIN}",
128 """Representation of a Z-Wave climate."""
130 _attr_precision = PRECISION_TENTHS
131 _enable_turn_on_off_backwards_compatibility =
False
134 self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
136 """Initialize thermostat."""
137 super().
__init__(config_entry, driver, info)
138 self.
_hvac_modes_hvac_modes: dict[HVACMode, int |
None] = {}
140 self.
_unit_value_unit_value: ZwaveValue |
None =
None
144 THERMOSTAT_MODE_PROPERTY, command_class=CommandClass.THERMOSTAT_MODE
146 self._supports_resume: bool =
bool(
149 str(ThermostatMode.RESUME_ON.value)
154 self._setpoint_values: dict[ThermostatSetpointType, ZwaveValue |
None] = {}
155 for enum
in ThermostatSetpointType:
157 THERMOSTAT_SETPOINT_PROPERTY,
158 command_class=CommandClass.THERMOSTAT_SETPOINT,
159 value_property_key=enum.value,
160 add_to_watched_value_ids=
True,
166 and enum != ThermostatSetpointType.NA
167 and self._setpoint_values[enum]
171 THERMOSTAT_OPERATING_STATE_PROPERTY,
172 command_class=CommandClass.THERMOSTAT_OPERATING_STATE,
173 add_to_watched_value_ids=
True,
174 check_all_endpoints=
True,
177 THERMOSTAT_CURRENT_TEMP_PROPERTY,
178 command_class=CommandClass.SENSOR_MULTILEVEL,
179 add_to_watched_value_ids=
True,
180 check_all_endpoints=
True,
185 THERMOSTAT_HUMIDITY_PROPERTY,
186 command_class=CommandClass.SENSOR_MULTILEVEL,
187 add_to_watched_value_ids=
True,
188 check_all_endpoints=
True,
191 THERMOSTAT_MODE_PROPERTY,
192 CommandClass.THERMOSTAT_FAN_MODE,
193 add_to_watched_value_ids=
True,
194 check_all_endpoints=
True,
197 THERMOSTAT_OPERATING_STATE_PROPERTY,
198 CommandClass.THERMOSTAT_FAN_STATE,
199 add_to_watched_value_ids=
True,
200 check_all_endpoints=
True,
204 self._attr_supported_features |= ClimateEntityFeature.PRESET_MODE
206 self._attr_supported_features |= ClimateEntityFeature.TURN_OFF
210 self._attr_supported_features |= ClimateEntityFeature.TURN_ON
213 if any(self._setpoint_values.values()):
214 self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE
216 self._attr_supported_features |= (
217 ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
220 self._attr_supported_features |= ClimateEntityFeature.FAN_MODE
223 self, setpoint_type: ThermostatSetpointType
225 """Return a ZwaveValue for a setpoint or raise if not available."""
226 if (val := self._setpoint_values[setpoint_type])
is None:
227 raise ValueError(
"Value requested is not available")
232 self, setpoint_type: ThermostatSetpointType
234 """Optionally return the temperature value of a setpoint."""
237 except (IndexError, ValueError):
242 """Convert Z-Wave Thermostat modes into Home Assistant modes and presets."""
243 all_modes: dict[HVACMode, int |
None] = {}
244 all_presets: dict[str, int |
None] = {PRESET_NONE:
None}
251 ZW_HVAC_MODE_MAP[ThermostatMode.HEAT]: ThermostatMode.HEAT
254 for mode_id, mode_name
in self.
_current_mode_current_mode.metadata.states.items():
255 mode_id =
int(mode_id)
256 if mode_id
in THERMOSTAT_MODES:
258 if hass_mode := ZW_HVAC_MODE_MAP.get(mode_id):
259 all_modes[hass_mode] = mode_id
262 all_presets[mode_name] = mode_id
269 """Return the list of enums that are relevant to the current thermostat mode."""
273 return [ThermostatSetpointType.HEATING]
274 return THERMOSTAT_MODE_SETPOINT_MAP.get(
int(self.
_current_mode_current_mode.value), [])
278 """Return the unit of measurement used by the platform."""
282 and "f" in self.
_unit_value_unit_value.metadata.unit.lower()
284 return UnitOfTemperature.FAHRENHEIT
285 return UnitOfTemperature.CELSIUS
289 """Return hvac operation ie. heat, cool mode."""
297 return ZW_HVAC_MODE_MAP.get(
int(self.
_current_mode_current_mode.value), HVACMode.HEAT_COOL)
301 """Return the list of available hvac operation modes."""
306 """Return the current running hvac operation if supported."""
316 """Return the current humidity level."""
321 """Return the current temperature."""
326 """Return the temperature we try to reach."""
340 """Return the highbound target temperature we try to reach."""
354 """Return the lowbound target temperature we try to reach."""
368 """Return the current preset mode, e.g., home, away, temp."""
373 return_val: str = cast(
382 """Return a list of available preset modes."""
387 """Return the fan setting."""
390 and self.
_fan_mode_fan_mode.value
is not None
398 """Return the list of available fan modes."""
400 return list(self.
_fan_mode_fan_mode.metadata.states.values())
405 """Return the optional state attributes."""
408 and self.
_fan_state_fan_state.value
is not None
412 ATTR_FAN_STATE: self.
_fan_state_fan_state.metadata.states[
421 """Return the minimum temperature."""
423 base_unit: str = UnitOfTemperature.CELSIUS
426 if temp.metadata.min:
427 min_temp = temp.metadata.min
430 except (IndexError, ValueError, TypeError):
437 """Return the maximum temperature."""
439 base_unit: str = UnitOfTemperature.CELSIUS
442 if temp.metadata.max:
443 max_temp = temp.metadata.max
446 except (IndexError, ValueError, TypeError):
452 """Set new target fan mode."""
453 assert self.
_fan_mode_fan_mode
is not None
458 for state, label
in self.
_fan_mode_fan_mode.metadata.states.items()
462 except StopIteration:
463 raise ValueError(f
"Received an invalid fan mode: {fan_mode}")
from None
468 """Set new target temperature."""
469 hvac_mode: HVACMode |
None = kwargs.get(ATTR_HVAC_MODE)
471 if hvac_mode
is not None:
477 target_temp: float |
None = kwargs.get(ATTR_TEMPERATURE)
478 if target_temp
is not None:
487 target_temp_low: float |
None = kwargs.get(ATTR_TARGET_TEMP_LOW)
488 target_temp_high: float |
None = kwargs.get(ATTR_TARGET_TEMP_HIGH)
489 if target_temp_low
is not None:
491 if target_temp_high
is not None:
492 await self.
_async_set_value_async_set_value(setpoint_high, target_temp_high)
495 """Set new target hvac mode."""
496 if (hvac_mode_id := self.
_hvac_modes_hvac_modes.
get(hvac_mode))
is None:
497 raise ValueError(f
"Received an invalid hvac mode: {hvac_mode}")
505 if hvac_mode == HVACMode.OFF
and self.
_current_mode_current_mode.value != ThermostatMode.OFF:
510 """Turn the entity off."""
514 """Turn the entity on."""
525 if self._supports_resume:
544 for mode
in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL)
547 except StopIteration:
548 hvac_mode = next(mode
for mode
in self.
_hvac_modes_hvac_modes
if mode != HVACMode.OFF)
552 """Set new target preset mode."""
554 if preset_mode == PRESET_NONE:
559 if preset_mode_value
is None:
560 raise ValueError(f
"Received an invalid preset mode: {preset_mode}")
566 """Representation of a thermostat that can dynamically use a different Zwave Value for current temp."""
569 self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
571 """Initialize thermostat."""
572 super().
__init__(config_entry, driver, info)
574 DynamicCurrentTempClimateDataTemplate, self.
infoinfo.platform_data_template
579 """Return the current temperature."""
580 assert self.
infoinfo.platform_data
582 self.
data_templatedata_template.current_temperature_value(self.
infoinfo.platform_data)
584 return val
if val
is not None else super().current_temperature
str temperature_unit(self)
None async_set_hvac_mode(self, HVACMode hvac_mode)
HVACMode|None hvac_mode(self)
list[HVACMode] hvac_modes(self)
float|None current_temperature(self)
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
ZwaveValue _setpoint_value_or_raise(self, ThermostatSetpointType setpoint_type)
list[HVACMode] hvac_modes(self)
None async_set_temperature(self, **Any kwargs)
float|None target_temperature_high(self)
list[str]|None preset_modes(self)
None async_set_preset_mode(self, str preset_mode)
float|None target_temperature(self)
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
dict[str, str]|None extra_state_attributes(self)
list[ThermostatSetpointType] _current_mode_setpoint_enums(self)
float|None target_temperature_low(self)
str temperature_unit(self)
_last_hvac_mode_id_before_off
HVACAction|None hvac_action(self)
None async_turn_off(self)
int|None current_humidity(self)
None async_set_fan_mode(self, str fan_mode)
None async_set_hvac_mode(self, HVACMode hvac_mode)
float|None current_temperature(self)
list[str]|None fan_modes(self)
None _set_modes_and_presets(self)
float|None _setpoint_temperature(self, ThermostatSetpointType setpoint_type)
str|None preset_mode(self)
SetValueResult|None _async_set_value(self, ZwaveValue value, Any new_value, dict|None options=None, bool|None wait_for_result=None)
ZwaveValue|None get_zwave_value(self, str|int value_property, int|None command_class=None, int|None endpoint=None, int|str|None value_property_key=None, bool add_to_watched_value_ids=True, bool check_all_endpoints=False)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Any|None get_value_of_zwave_value(ZwaveValue|None value)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)