Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for Bryant Evolution HVAC systems."""
2 
3 from datetime import timedelta
4 import logging
5 from typing import Any
6 
7 from evolutionhttp import BryantEvolutionLocalClient
8 
10  ClimateEntity,
11  ClimateEntityFeature,
12  HVACAction,
13  HVACMode,
14 )
15 from homeassistant.const import UnitOfTemperature
16 from homeassistant.core import HomeAssistant
17 from homeassistant.exceptions import HomeAssistantError
18 from homeassistant.helpers.device_registry import DeviceInfo
19 from homeassistant.helpers.entity import Entity
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 
22 from . import BryantEvolutionConfigEntry, names
23 from .const import CONF_SYSTEM_ZONE, DOMAIN
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 
28 SCAN_INTERVAL = timedelta(seconds=60)
29 
30 
32  hass: HomeAssistant,
33  config_entry: BryantEvolutionConfigEntry,
34  async_add_entities: AddEntitiesCallback,
35 ) -> None:
36  """Set up a config entry."""
37 
38  # Add a climate entity for each system/zone.
39  sam_uid = names.sam_device_uid(config_entry)
40  entities: list[Entity] = []
41  for sz in config_entry.data[CONF_SYSTEM_ZONE]:
42  system_id = sz[0]
43  zone_id = sz[1]
44  client = config_entry.runtime_data.get(tuple(sz))
45  climate = BryantEvolutionClimate(
46  client,
47  system_id,
48  zone_id,
49  sam_uid,
50  )
51  entities.append(climate)
52  async_add_entities(entities, update_before_add=True)
53 
54 
56  """ClimateEntity for Bryant Evolution HVAC systems.
57 
58  Design note: this class updates using polling. However, polling
59  is very slow (~1500 ms / parameter). To improve the user
60  experience on updates, we also locally update this instance and
61  call async_write_ha_state as well.
62  """
63 
64  _attr_has_entity_name = True
65  _attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
66  _attr_supported_features = (
67  ClimateEntityFeature.TARGET_TEMPERATURE
68  | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
69  | ClimateEntityFeature.FAN_MODE
70  | ClimateEntityFeature.TURN_ON
71  | ClimateEntityFeature.TURN_OFF
72  )
73  _attr_hvac_modes = [
74  HVACMode.HEAT,
75  HVACMode.COOL,
76  HVACMode.HEAT_COOL,
77  HVACMode.OFF,
78  ]
79  _attr_fan_modes = ["auto", "low", "med", "high"]
80  _enable_turn_on_off_backwards_compatibility = False
81 
82  def __init__(
83  self,
84  client: BryantEvolutionLocalClient,
85  system_id: int,
86  zone_id: int,
87  sam_uid: str,
88  ) -> None:
89  """Initialize an entity from parts."""
90  self._client_client = client
91  self._attr_name_attr_name = None
92  self._attr_unique_id_attr_unique_id = names.zone_entity_uid(sam_uid, system_id, zone_id)
93  self._attr_device_info_attr_device_info = DeviceInfo(
94  identifiers={(DOMAIN, self._attr_unique_id_attr_unique_id)},
95  manufacturer="Bryant",
96  via_device=(DOMAIN, names.system_device_uid(sam_uid, system_id)),
97  name=f"System {system_id} Zone {zone_id}",
98  )
99 
100  async def async_update(self) -> None:
101  """Update the entity state."""
102  self._attr_current_temperature_attr_current_temperature = await self._client_client.read_current_temperature()
103  if (fan_mode := await self._client_client.read_fan_mode()) is not None:
104  self._attr_fan_mode_attr_fan_mode = fan_mode.lower()
105  else:
106  self._attr_fan_mode_attr_fan_mode = None
107  self._attr_target_temperature_attr_target_temperature = None
108  self._attr_target_temperature_high_attr_target_temperature_high = None
109  self._attr_target_temperature_low_attr_target_temperature_low = None
110  self._attr_hvac_mode_attr_hvac_mode = await self._read_hvac_mode_read_hvac_mode()
111 
112  # Set target_temperature or target_temperature_{high, low} based on mode.
113  match self._attr_hvac_mode_attr_hvac_mode:
114  case HVACMode.HEAT:
115  self._attr_target_temperature_attr_target_temperature = (
116  await self._client_client.read_heating_setpoint()
117  )
118  case HVACMode.COOL:
119  self._attr_target_temperature_attr_target_temperature = (
120  await self._client_client.read_cooling_setpoint()
121  )
122  case HVACMode.HEAT_COOL:
123  self._attr_target_temperature_high_attr_target_temperature_high = (
124  await self._client_client.read_cooling_setpoint()
125  )
126  self._attr_target_temperature_low_attr_target_temperature_low = (
127  await self._client_client.read_heating_setpoint()
128  )
129  case HVACMode.OFF:
130  pass
131  case _:
132  _LOGGER.error("Unknown HVAC mode %s", self._attr_hvac_mode_attr_hvac_mode)
133 
134  # Note: depends on current temperature and target temperature low read
135  # above.
136  self._attr_hvac_action_attr_hvac_action = await self._read_hvac_action_read_hvac_action()
137 
138  async def _read_hvac_mode(self) -> HVACMode:
139  mode_and_active = await self._client_client.read_hvac_mode()
140  if not mode_and_active:
141  raise HomeAssistantError(
142  translation_domain=DOMAIN, translation_key="failed_to_read_hvac_mode"
143  )
144  mode = mode_and_active[0]
145  mode_enum = {
146  "HEAT": HVACMode.HEAT,
147  "COOL": HVACMode.COOL,
148  "AUTO": HVACMode.HEAT_COOL,
149  "OFF": HVACMode.OFF,
150  }.get(mode.upper())
151  if mode_enum is None:
152  raise HomeAssistantError(
153  translation_domain=DOMAIN,
154  translation_key="failed_to_parse_hvac_mode",
155  translation_placeholders={"mode": mode},
156  )
157  return mode_enum
158 
159  async def _read_hvac_action(self) -> HVACAction:
160  """Return the current running hvac operation."""
161  mode_and_active = await self._client_client.read_hvac_mode()
162  if not mode_and_active:
163  raise HomeAssistantError(
164  translation_domain=DOMAIN, translation_key="failed_to_read_hvac_action"
165  )
166  mode, is_active = mode_and_active
167  if not is_active:
168  return HVACAction.OFF
169  match mode.upper():
170  case "HEAT":
171  return HVACAction.HEATING
172  case "COOL":
173  return HVACAction.COOLING
174  case "OFF":
175  return HVACAction.OFF
176  case "AUTO":
177  # In AUTO, we need to figure out what the actual action is
178  # based on the setpoints.
179  if (
180  self.current_temperaturecurrent_temperature is not None
181  and self.target_temperature_lowtarget_temperature_low is not None
182  ):
183  if self.current_temperaturecurrent_temperature > self.target_temperature_lowtarget_temperature_low:
184  # If the system is on and the current temperature is
185  # higher than the point at which heating would activate,
186  # then we must be cooling.
187  return HVACAction.COOLING
188  return HVACAction.HEATING
189  raise HomeAssistantError(
190  translation_domain=DOMAIN,
191  translation_key="failed_to_parse_hvac_mode",
192  translation_placeholders={
193  "mode_and_active": mode_and_active,
194  "current_temperature": str(self.current_temperaturecurrent_temperature),
195  "target_temperature_low": str(self.target_temperature_lowtarget_temperature_low),
196  },
197  )
198 
199  async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
200  """Set new target hvac mode."""
201  if hvac_mode == HVACMode.HEAT_COOL:
202  hvac_mode = HVACMode.AUTO
203  if not await self._client_client.set_hvac_mode(hvac_mode):
204  raise HomeAssistantError(
205  translation_domain=DOMAIN, translation_key="failed_to_set_hvac_mode"
206  )
207  self._attr_hvac_mode_attr_hvac_mode = hvac_mode
208  self._async_write_ha_state_async_write_ha_state()
209 
210  async def async_set_temperature(self, **kwargs: Any) -> None:
211  """Set new target temperature."""
212  if kwargs.get("target_temp_high"):
213  temp = int(kwargs["target_temp_high"])
214  if not await self._client_client.set_cooling_setpoint(temp):
215  raise HomeAssistantError(
216  translation_domain=DOMAIN, translation_key="failed_to_set_clsp"
217  )
218  self._attr_target_temperature_high_attr_target_temperature_high = temp
219 
220  if kwargs.get("target_temp_low"):
221  temp = int(kwargs["target_temp_low"])
222  if not await self._client_client.set_heating_setpoint(temp):
223  raise HomeAssistantError(
224  translation_domain=DOMAIN, translation_key="failed_to_set_htsp"
225  )
226  self._attr_target_temperature_low_attr_target_temperature_low = temp
227 
228  if kwargs.get("temperature"):
229  temp = int(kwargs["temperature"])
230  fn = (
231  self._client_client.set_heating_setpoint
232  if self.hvac_modehvac_modehvac_modehvac_mode == HVACMode.HEAT
233  else self._client_client.set_cooling_setpoint
234  )
235  if not await fn(temp):
236  raise HomeAssistantError(
237  translation_domain=DOMAIN, translation_key="failed_to_set_temp"
238  )
239  self._attr_target_temperature_attr_target_temperature = temp
240 
241  # If we get here, we must have changed something unless HA allowed an
242  # invalid service call (without any recognized kwarg).
243  self._async_write_ha_state_async_write_ha_state()
244 
245  async def async_set_fan_mode(self, fan_mode: str) -> None:
246  """Set new target fan mode."""
247  if not await self._client_client.set_fan_mode(fan_mode):
248  raise HomeAssistantError(
249  translation_domain=DOMAIN, translation_key="failed_to_set_fan_mode"
250  )
251  self._attr_fan_mode_attr_fan_mode = fan_mode.lower()
252  self.async_write_ha_stateasync_write_ha_state()
None __init__(self, BryantEvolutionLocalClient client, int system_id, int zone_id, str sam_uid)
Definition: climate.py:88
None set_hvac_mode(self, HVACMode hvac_mode)
Definition: __init__.py:809
None async_setup_entry(HomeAssistant hass, BryantEvolutionConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:35
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88