Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for NuHeat thermostats."""
2 
3 import logging
4 from typing import Any
5 
6 from nuheat.config import SCHEDULE_HOLD, SCHEDULE_RUN, SCHEDULE_TEMPORARY_HOLD
7 from nuheat.util import (
8  celsius_to_nuheat,
9  fahrenheit_to_nuheat,
10  nuheat_to_celsius,
11  nuheat_to_fahrenheit,
12 )
13 
15  ATTR_HVAC_MODE,
16  ClimateEntity,
17  ClimateEntityFeature,
18  HVACAction,
19  HVACMode,
20 )
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.helpers import event as event_helper
25 from homeassistant.helpers.device_registry import DeviceInfo
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.update_coordinator import CoordinatorEntity
28 
29 from .const import DOMAIN, MANUFACTURER, NUHEAT_API_STATE_SHIFT_DELAY
30 
31 _LOGGER = logging.getLogger(__name__)
32 
33 
34 # The device does not have an off function.
35 # To turn it off set to min_temp and PRESET_PERMANENT_HOLD
36 OPERATION_LIST = [HVACMode.AUTO, HVACMode.HEAT]
37 
38 PRESET_RUN = "Run Schedule"
39 PRESET_TEMPORARY_HOLD = "Temporary Hold"
40 PRESET_PERMANENT_HOLD = "Permanent Hold"
41 
42 PRESET_MODES = [PRESET_RUN, PRESET_TEMPORARY_HOLD, PRESET_PERMANENT_HOLD]
43 
44 PRESET_MODE_TO_SCHEDULE_MODE_MAP = {
45  PRESET_RUN: SCHEDULE_RUN,
46  PRESET_TEMPORARY_HOLD: SCHEDULE_TEMPORARY_HOLD,
47  PRESET_PERMANENT_HOLD: SCHEDULE_HOLD,
48 }
49 
50 SCHEDULE_MODE_TO_PRESET_MODE_MAP = {
51  value: key for key, value in PRESET_MODE_TO_SCHEDULE_MODE_MAP.items()
52 }
53 
54 
56  hass: HomeAssistant,
57  config_entry: ConfigEntry,
58  async_add_entities: AddEntitiesCallback,
59 ) -> None:
60  """Set up the NuHeat thermostat(s)."""
61  thermostat, coordinator = hass.data[DOMAIN][config_entry.entry_id]
62 
63  temperature_unit = hass.config.units.temperature_unit
64  entity = NuHeatThermostat(coordinator, thermostat, temperature_unit)
65 
66  # No longer need a service as set_hvac_mode to auto does this
67  # since climate 1.0 has been implemented
68 
69  async_add_entities([entity], True)
70 
71 
73  """Representation of a NuHeat Thermostat."""
74 
75  _attr_hvac_modes = OPERATION_LIST
76  _attr_supported_features = (
77  ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
78  )
79  _attr_has_entity_name = True
80  _attr_name = None
81  _attr_preset_modes = PRESET_MODES
82  _enable_turn_on_off_backwards_compatibility = False
83 
84  def __init__(self, coordinator, thermostat, temperature_unit):
85  """Initialize the thermostat."""
86  super().__init__(coordinator)
87  self._thermostat_thermostat = thermostat
88  self._temperature_unit_temperature_unit = temperature_unit
89  self._schedule_mode_schedule_mode = None
90  self._target_temperature_target_temperature = None
91  self._attr_unique_id_attr_unique_id = thermostat.serial_number
92 
93  @property
94  def temperature_unit(self) -> str:
95  """Return the unit of measurement."""
96  if self._temperature_unit_temperature_unit == "C":
97  return UnitOfTemperature.CELSIUS
98 
99  return UnitOfTemperature.FAHRENHEIT
100 
101  @property
103  """Return the current temperature."""
104  if self._temperature_unit_temperature_unit == "C":
105  return self._thermostat_thermostat.celsius
106 
107  return self._thermostat_thermostat.fahrenheit
108 
109  @property
110  def available(self) -> bool:
111  """Return the unique id."""
112  return self.coordinator.last_update_success and self._thermostat_thermostat.online
113 
114  def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
115  """Set the system mode."""
116  if hvac_mode == HVACMode.AUTO:
117  self._set_schedule_mode_set_schedule_mode(SCHEDULE_RUN)
118  elif hvac_mode == HVACMode.HEAT:
119  self._set_schedule_mode_set_schedule_mode(SCHEDULE_HOLD)
120 
121  @property
122  def hvac_mode(self) -> HVACMode:
123  """Return current setting heat or auto."""
124  if self._schedule_mode_schedule_mode in (SCHEDULE_TEMPORARY_HOLD, SCHEDULE_HOLD):
125  return HVACMode.HEAT
126  return HVACMode.AUTO
127 
128  @property
129  def hvac_action(self) -> HVACAction:
130  """Return current operation heat or idle."""
131  return HVACAction.HEATING if self._thermostat_thermostat.heating else HVACAction.IDLE
132 
133  @property
134  def min_temp(self):
135  """Return the minimum supported temperature for the thermostat."""
136  if self._temperature_unit_temperature_unit == "C":
137  return self._thermostat_thermostat.min_celsius
138 
139  return self._thermostat_thermostat.min_fahrenheit
140 
141  @property
142  def max_temp(self):
143  """Return the maximum supported temperature for the thermostat."""
144  if self._temperature_unit_temperature_unit == "C":
145  return self._thermostat_thermostat.max_celsius
146 
147  return self._thermostat_thermostat.max_fahrenheit
148 
149  @property
151  """Return the currently programmed temperature."""
152  if self._temperature_unit_temperature_unit == "C":
153  return nuheat_to_celsius(self._target_temperature_target_temperature)
154 
155  return nuheat_to_fahrenheit(self._target_temperature_target_temperature)
156 
157  @property
158  def preset_mode(self):
159  """Return current preset mode."""
160  return SCHEDULE_MODE_TO_PRESET_MODE_MAP.get(self._schedule_mode_schedule_mode, PRESET_RUN)
161 
162  def set_preset_mode(self, preset_mode: str) -> None:
163  """Update the hold mode of the thermostat."""
164  self._set_schedule_mode_set_schedule_mode(
165  PRESET_MODE_TO_SCHEDULE_MODE_MAP.get(preset_mode, SCHEDULE_RUN)
166  )
167 
168  def _set_schedule_mode(self, schedule_mode):
169  """Set a schedule mode."""
170  self._schedule_mode_schedule_mode = schedule_mode
171  # Changing the property here does the actual set
172  self._thermostat_thermostat.schedule_mode = schedule_mode
173  self._schedule_update_schedule_update()
174 
175  def set_temperature(self, **kwargs: Any) -> None:
176  """Set a new target temperature."""
177  self._set_temperature_and_mode_set_temperature_and_mode(
178  kwargs.get(ATTR_TEMPERATURE), hvac_mode=kwargs.get(ATTR_HVAC_MODE)
179  )
180 
181  def _set_temperature_and_mode(self, temperature, hvac_mode=None, preset_mode=None):
182  """Set temperature and hvac mode at the same time."""
183  if self._temperature_unit_temperature_unit == "C":
184  target_temperature = celsius_to_nuheat(temperature)
185  else:
186  target_temperature = fahrenheit_to_nuheat(temperature)
187 
188  # If they set a temperature without changing the mode
189  # to heat, we behave like the device does locally
190  # and set a temp hold.
191  target_schedule_mode = SCHEDULE_TEMPORARY_HOLD
192  if preset_mode:
193  target_schedule_mode = PRESET_MODE_TO_SCHEDULE_MODE_MAP.get(
194  preset_mode, SCHEDULE_RUN
195  )
196  elif self._schedule_mode_schedule_mode == SCHEDULE_HOLD or (
197  hvac_mode and hvac_mode == HVACMode.HEAT
198  ):
199  target_schedule_mode = SCHEDULE_HOLD
200 
201  _LOGGER.debug(
202  "Setting NuHeat thermostat temperature to %s %s and schedule mode: %s",
203  temperature,
204  self.temperature_unittemperature_unittemperature_unit,
205  target_schedule_mode,
206  )
207 
208  self._thermostat_thermostat.set_target_temperature(
209  target_temperature, target_schedule_mode
210  )
211  self._schedule_mode_schedule_mode = target_schedule_mode
212  self._target_temperature_target_temperature = target_temperature
213  self._schedule_update_schedule_update()
214 
215  def _schedule_update(self):
216  if not self.hasshass:
217  return
218 
219  # Update the new state
220  self.schedule_update_ha_stateschedule_update_ha_state(False)
221 
222  # nuheat has a delay switching state
223  # so we schedule a poll of the api
224  # in the future to make sure the change actually
225  # took effect
226  event_helper.call_later(
227  self.hasshass, NUHEAT_API_STATE_SHIFT_DELAY, self._forced_refresh_forced_refresh
228  )
229 
230  async def _forced_refresh(self, *_) -> None:
231  """Force a refresh."""
232  await self.coordinator.async_refresh()
233 
234  async def async_added_to_hass(self) -> None:
235  """When entity is added to hass."""
236  await super().async_added_to_hass()
237  self._update_internal_state_update_internal_state()
238 
239  @callback
241  """Update our internal state from the last api response."""
242  self._schedule_mode_schedule_mode = self._thermostat_thermostat.schedule_mode
243  self._target_temperature_target_temperature = self._thermostat_thermostat.target_temperature
244 
245  @callback
247  """Get the latest state from the thermostat."""
248  self._update_internal_state_update_internal_state()
249  self.async_write_ha_stateasync_write_ha_state()
250 
251  @property
252  def device_info(self) -> DeviceInfo:
253  """Return the device_info of the device."""
254  return DeviceInfo(
255  identifiers={(DOMAIN, self._thermostat_thermostat.serial_number)},
256  serial_number=self._thermostat_thermostat.serial_number,
257  name=self._thermostat_thermostat.room,
258  model="nVent Signature",
259  manufacturer=MANUFACTURER,
260  suggested_area=self._thermostat_thermostat.room,
261  )
def _set_temperature_and_mode(self, temperature, hvac_mode=None, preset_mode=None)
Definition: climate.py:181
def __init__(self, coordinator, thermostat, temperature_unit)
Definition: climate.py:84
None set_hvac_mode(self, HVACMode hvac_mode)
Definition: climate.py:114
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:59