Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for Venstar WiFi Thermostats."""
2 
3 from __future__ import annotations
4 
5 import voluptuous as vol
6 
8  ATTR_HVAC_MODE,
9  ATTR_TARGET_TEMP_HIGH,
10  ATTR_TARGET_TEMP_LOW,
11  FAN_AUTO,
12  FAN_ON,
13  PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA,
14  PRESET_AWAY,
15  PRESET_NONE,
16  ClimateEntity,
17  ClimateEntityFeature,
18  HVACAction,
19  HVACMode,
20 )
21 from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
22 from homeassistant.const import (
23  ATTR_TEMPERATURE,
24  CONF_HOST,
25  CONF_PASSWORD,
26  CONF_PIN,
27  CONF_SSL,
28  CONF_TIMEOUT,
29  CONF_USERNAME,
30  PRECISION_HALVES,
31  STATE_ON,
32  UnitOfTemperature,
33 )
34 from homeassistant.core import HomeAssistant
36 from homeassistant.helpers.entity_platform import AddEntitiesCallback
37 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
38 
39 from .const import (
40  _LOGGER,
41  ATTR_FAN_STATE,
42  ATTR_HVAC_STATE,
43  CONF_HUMIDIFIER,
44  DEFAULT_SSL,
45  DOMAIN,
46  HOLD_MODE_TEMPERATURE,
47 )
48 from .coordinator import VenstarDataUpdateCoordinator
49 from .entity import VenstarEntity
50 
51 PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend(
52  {
53  vol.Required(CONF_HOST): cv.string,
54  vol.Optional(CONF_PASSWORD): cv.string,
55  vol.Optional(CONF_HUMIDIFIER, default=True): cv.boolean,
56  vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
57  vol.Optional(CONF_TIMEOUT, default=5): vol.All(
58  vol.Coerce(int), vol.Range(min=1)
59  ),
60  vol.Optional(CONF_USERNAME): cv.string,
61  vol.Optional(CONF_PIN): cv.string,
62  }
63 )
64 
65 
67  hass: HomeAssistant,
68  config_entry: ConfigEntry,
69  async_add_entities: AddEntitiesCallback,
70 ) -> None:
71  """Set up the Venstar thermostat."""
72  venstar_data_coordinator = hass.data[DOMAIN][config_entry.entry_id]
74  [
76  venstar_data_coordinator,
77  config_entry,
78  )
79  ],
80  )
81 
82 
84  hass: HomeAssistant,
85  config: ConfigType,
86  add_entities: AddEntitiesCallback,
87  discovery_info: DiscoveryInfoType | None = None,
88 ) -> None:
89  """Set up the Venstar thermostat platform.
90 
91  Venstar uses config flow for configuration now. If an entry exists in
92  configuration.yaml, the import flow will attempt to import it and create
93  a config entry.
94  """
95  _LOGGER.warning(
96  "Loading venstar via platform config is deprecated; The configuration"
97  " has been migrated to a config entry and can be safely removed"
98  )
99  # No config entry exists and configuration.yaml config exists, trigger the import flow.
100  if not hass.config_entries.async_entries(DOMAIN):
101  await hass.config_entries.flow.async_init(
102  DOMAIN, context={"source": SOURCE_IMPORT}, data=config
103  )
104 
105 
107  """Representation of a Venstar thermostat."""
108 
109  _attr_fan_modes = [FAN_ON, FAN_AUTO]
110  _attr_hvac_modes = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF, HVACMode.AUTO]
111  _attr_precision = PRECISION_HALVES
112  _attr_name = None
113  _enable_turn_on_off_backwards_compatibility = False
114 
115  def __init__(
116  self,
117  venstar_data_coordinator: VenstarDataUpdateCoordinator,
118  config: ConfigEntry,
119  ) -> None:
120  """Initialize the thermostat."""
121  super().__init__(venstar_data_coordinator, config)
122  self._mode_map_mode_map = {
123  HVACMode.HEAT: self._client_client.MODE_HEAT,
124  HVACMode.COOL: self._client_client.MODE_COOL,
125  HVACMode.AUTO: self._client_client.MODE_AUTO,
126  }
127  self._attr_unique_id_attr_unique_id = config.entry_id
128 
129  @property
130  def supported_features(self) -> ClimateEntityFeature:
131  """Return the list of supported features."""
132  features = (
133  ClimateEntityFeature.TARGET_TEMPERATURE
134  | ClimateEntityFeature.FAN_MODE
135  | ClimateEntityFeature.PRESET_MODE
136  | ClimateEntityFeature.TURN_OFF
137  | ClimateEntityFeature.TURN_ON
138  )
139 
140  if self._client_client.mode == self._client_client.MODE_AUTO:
141  features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
142 
143  if self._client_client.hum_setpoint is not None:
144  features |= ClimateEntityFeature.TARGET_HUMIDITY
145 
146  return features
147 
148  @property
149  def temperature_unit(self) -> str:
150  """Return the unit of measurement, as defined by the API."""
151  if self._client_client.tempunits == self._client_client.TEMPUNITS_F:
152  return UnitOfTemperature.FAHRENHEIT
153  return UnitOfTemperature.CELSIUS
154 
155  @property
157  """Return the current temperature."""
158  return self._client_client.get_indoor_temp()
159 
160  @property
161  def current_humidity(self):
162  """Return the current humidity."""
163  return self._client_client.get_indoor_humidity()
164 
165  @property
166  def hvac_mode(self) -> HVACMode:
167  """Return current operation mode ie. heat, cool, auto."""
168  if self._client_client.mode == self._client_client.MODE_HEAT:
169  return HVACMode.HEAT
170  if self._client_client.mode == self._client_client.MODE_COOL:
171  return HVACMode.COOL
172  if self._client_client.mode == self._client_client.MODE_AUTO:
173  return HVACMode.AUTO
174  return HVACMode.OFF
175 
176  @property
177  def hvac_action(self) -> HVACAction:
178  """Return current operation mode ie. heat, cool, auto."""
179  if self._client_client.state == self._client_client.STATE_IDLE:
180  return HVACAction.IDLE
181  if self._client_client.state == self._client_client.STATE_HEATING:
182  return HVACAction.HEATING
183  if self._client_client.state == self._client_client.STATE_COOLING:
184  return HVACAction.COOLING
185  return HVACAction.OFF
186 
187  @property
188  def fan_mode(self):
189  """Return the current fan mode."""
190  if self._client_client.fan == self._client_client.FAN_ON:
191  return FAN_ON
192  return FAN_AUTO
193 
194  @property
196  """Return the optional state attributes."""
197  return {
198  ATTR_FAN_STATE: self._client_client.fanstate,
199  ATTR_HVAC_STATE: self._client_client.state,
200  }
201 
202  @property
204  """Return the target temperature we try to reach."""
205  if self._client_client.mode == self._client_client.MODE_HEAT:
206  return self._client_client.heattemp
207  if self._client_client.mode == self._client_client.MODE_COOL:
208  return self._client_client.cooltemp
209  return None
210 
211  @property
213  """Return the lower bound temp if auto mode is on."""
214  if self._client_client.mode == self._client_client.MODE_AUTO:
215  return self._client_client.heattemp
216  return None
217 
218  @property
220  """Return the upper bound temp if auto mode is on."""
221  if self._client_client.mode == self._client_client.MODE_AUTO:
222  return self._client_client.cooltemp
223  return None
224 
225  @property
226  def target_humidity(self):
227  """Return the humidity we try to reach."""
228  return self._client_client.hum_setpoint
229 
230  @property
231  def min_humidity(self):
232  """Return the minimum humidity. Hardcoded to 0 in API."""
233  return 0
234 
235  @property
236  def max_humidity(self):
237  """Return the maximum humidity. Hardcoded to 60 in API."""
238  return 60
239 
240  @property
241  def preset_mode(self):
242  """Return current preset."""
243  if self._client_client.away:
244  return PRESET_AWAY
245  if self._client_client.schedule == 0:
246  return HOLD_MODE_TEMPERATURE
247  return PRESET_NONE
248 
249  @property
250  def preset_modes(self):
251  """Return valid preset modes."""
252  return [PRESET_NONE, PRESET_AWAY, HOLD_MODE_TEMPERATURE]
253 
254  def _set_operation_mode(self, operation_mode: HVACMode):
255  """Change the operation mode (internal)."""
256  if operation_mode == HVACMode.HEAT:
257  success = self._client_client.set_mode(self._client_client.MODE_HEAT)
258  elif operation_mode == HVACMode.COOL:
259  success = self._client_client.set_mode(self._client_client.MODE_COOL)
260  elif operation_mode == HVACMode.AUTO:
261  success = self._client_client.set_mode(self._client_client.MODE_AUTO)
262  else:
263  success = self._client_client.set_mode(self._client_client.MODE_OFF)
264 
265  if not success:
266  _LOGGER.error("Failed to change the operation mode")
267  return success
268 
269  def set_temperature(self, **kwargs):
270  """Set a new target temperature."""
271  set_temp = True
272  operation_mode = kwargs.get(ATTR_HVAC_MODE)
273  temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
274  temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
275  temperature = kwargs.get(ATTR_TEMPERATURE)
276 
277  if operation_mode and self._mode_map_mode_map.get(operation_mode) != self._client_client.mode:
278  set_temp = self._set_operation_mode_set_operation_mode(operation_mode)
279 
280  if set_temp:
281  if (
282  self._mode_map_mode_map.get(operation_mode, self._client_client.mode)
283  == self._client_client.MODE_HEAT
284  ):
285  success = self._client_client.set_setpoints(temperature, self._client_client.cooltemp)
286  elif (
287  self._mode_map_mode_map.get(operation_mode, self._client_client.mode)
288  == self._client_client.MODE_COOL
289  ):
290  success = self._client_client.set_setpoints(self._client_client.heattemp, temperature)
291  elif (
292  self._mode_map_mode_map.get(operation_mode, self._client_client.mode)
293  == self._client_client.MODE_AUTO
294  ):
295  success = self._client_client.set_setpoints(temp_low, temp_high)
296  else:
297  success = False
298  _LOGGER.error(
299  (
300  "The thermostat is currently not in a mode "
301  "that supports target temperature: %s"
302  ),
303  operation_mode,
304  )
305 
306  if not success:
307  _LOGGER.error("Failed to change the temperature")
308  self.schedule_update_ha_stateschedule_update_ha_state()
309 
310  def set_fan_mode(self, fan_mode: str) -> None:
311  """Set new target fan mode."""
312  if fan_mode == STATE_ON:
313  success = self._client_client.set_fan(self._client_client.FAN_ON)
314  else:
315  success = self._client_client.set_fan(self._client_client.FAN_AUTO)
316 
317  if not success:
318  _LOGGER.error("Failed to change the fan mode")
319  self.schedule_update_ha_stateschedule_update_ha_state()
320 
321  def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
322  """Set new target operation mode."""
323  self._set_operation_mode_set_operation_mode(hvac_mode)
324  self.schedule_update_ha_stateschedule_update_ha_state()
325 
326  def set_humidity(self, humidity: int) -> None:
327  """Set new target humidity."""
328  success = self._client_client.set_hum_setpoint(humidity)
329 
330  if not success:
331  _LOGGER.error("Failed to change the target humidity level")
332  self.schedule_update_ha_stateschedule_update_ha_state()
333 
334  def set_preset_mode(self, preset_mode: str) -> None:
335  """Set the hold mode."""
336  if preset_mode == PRESET_AWAY:
337  success = self._client_client.set_away(self._client_client.AWAY_AWAY)
338  elif preset_mode == HOLD_MODE_TEMPERATURE:
339  success = self._client_client.set_away(self._client_client.AWAY_HOME)
340  success = success and self._client_client.set_schedule(0)
341  elif preset_mode == PRESET_NONE:
342  success = self._client_client.set_away(self._client_client.AWAY_HOME)
343  success = success and self._client_client.set_schedule(1)
344  else:
345  _LOGGER.error("Unknown hold mode: %s", preset_mode)
346  success = False
347 
348  if not success:
349  _LOGGER.error("Failed to change the schedule/hold state")
350  self.schedule_update_ha_stateschedule_update_ha_state()
def _set_operation_mode(self, HVACMode operation_mode)
Definition: climate.py:254
None __init__(self, VenstarDataUpdateCoordinator venstar_data_coordinator, ConfigEntry config)
Definition: climate.py:119
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: climate.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:70