Home Assistant Unofficial Reference 2024.12.1
climate.py
Go to the documentation of this file.
1 """Support for OpenTherm Gateway climate devices."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 import logging
7 from types import MappingProxyType
8 from typing import Any
9 
10 from pyotgw import vars as gw_vars
11 
13  PRESET_AWAY,
14  PRESET_NONE,
15  ClimateEntity,
16  ClimateEntityDescription,
17  ClimateEntityFeature,
18  HVACAction,
19  HVACMode,
20 )
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import ATTR_TEMPERATURE, CONF_ID, UnitOfTemperature
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.helpers.dispatcher import async_dispatcher_connect
25 from homeassistant.helpers.entity_platform import AddEntitiesCallback
26 
27 from . import OpenThermGatewayHub
28 from .const import (
29  CONF_READ_PRECISION,
30  CONF_SET_PRECISION,
31  CONF_TEMPORARY_OVRD_MODE,
32  DATA_GATEWAYS,
33  DATA_OPENTHERM_GW,
34  THERMOSTAT_DEVICE_DESCRIPTION,
35  OpenThermDataSource,
36 )
37 from .entity import OpenThermEntityDescription, OpenThermStatusEntity
38 
39 _LOGGER = logging.getLogger(__name__)
40 
41 DEFAULT_FLOOR_TEMP = False
42 
43 
44 @dataclass(frozen=True, kw_only=True)
46  ClimateEntityDescription, OpenThermEntityDescription
47 ):
48  """Describes an opentherm_gw climate entity."""
49 
50 
52  hass: HomeAssistant,
53  config_entry: ConfigEntry,
54  async_add_entities: AddEntitiesCallback,
55 ) -> None:
56  """Set up an OpenTherm Gateway climate entity."""
57  ents = []
58  ents.append(
60  hass.data[DATA_OPENTHERM_GW][DATA_GATEWAYS][config_entry.data[CONF_ID]],
62  key="thermostat_entity",
63  device_description=THERMOSTAT_DEVICE_DESCRIPTION,
64  ),
65  config_entry.options,
66  )
67  )
68 
69  async_add_entities(ents)
70 
71 
73  """Representation of a climate device."""
74 
75  _attr_supported_features = (
76  ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
77  )
78  _attr_temperature_unit = UnitOfTemperature.CELSIUS
79  _attr_hvac_modes = []
80  _attr_name = None
81  _attr_preset_modes = []
82  _attr_min_temp = 1
83  _attr_max_temp = 30
84  _attr_hvac_mode = HVACMode.HEAT
85  _away_mode_a: int | None = None
86  _away_mode_b: int | None = None
87  _away_state_a = False
88  _away_state_b = False
89  _enable_turn_on_off_backwards_compatibility = False
90  _target_temperature: float | None = None
91  _new_target_temperature: float | None = None
92  entity_description: OpenThermClimateEntityDescription
93 
94  def __init__(
95  self,
96  gw_hub: OpenThermGatewayHub,
97  description: OpenThermClimateEntityDescription,
98  options: MappingProxyType[str, Any],
99  ) -> None:
100  """Initialize the entity."""
101  super().__init__(gw_hub, description)
102  if CONF_READ_PRECISION in options:
103  self._attr_precision_attr_precision = options[CONF_READ_PRECISION]
104  self._attr_target_temperature_step_attr_target_temperature_step = options.get(CONF_SET_PRECISION)
105  self.temporary_ovrd_modetemporary_ovrd_mode = options.get(CONF_TEMPORARY_OVRD_MODE, True)
106 
107  @callback
108  def update_options(self, entry):
109  """Update climate entity options."""
110  self._attr_precision_attr_precision = entry.options[CONF_READ_PRECISION]
111  self._attr_target_temperature_step_attr_target_temperature_step = entry.options[CONF_SET_PRECISION]
112  self.temporary_ovrd_modetemporary_ovrd_mode = entry.options[CONF_TEMPORARY_OVRD_MODE]
113  self.async_write_ha_stateasync_write_ha_state()
114 
115  async def async_added_to_hass(self) -> None:
116  """Connect to the OpenTherm Gateway device."""
117  await super().async_added_to_hass()
118  self.async_on_removeasync_on_remove(
120  self.hasshass, self._gateway_gateway.options_update_signal, self.update_optionsupdate_options
121  )
122  )
123 
124  @callback
125  def receive_report(self, status: dict[OpenThermDataSource, dict]):
126  """Receive and handle a new report from the Gateway."""
127  ch_active = status[OpenThermDataSource.BOILER].get(gw_vars.DATA_SLAVE_CH_ACTIVE)
128  flame_on = status[OpenThermDataSource.BOILER].get(gw_vars.DATA_SLAVE_FLAME_ON)
129  cooling_active = status[OpenThermDataSource.BOILER].get(
130  gw_vars.DATA_SLAVE_COOLING_ACTIVE
131  )
132  if ch_active and flame_on:
133  self._attr_hvac_action_attr_hvac_action = HVACAction.HEATING
134  self._attr_hvac_mode_attr_hvac_mode = HVACMode.HEAT
135  elif cooling_active:
136  self._attr_hvac_action_attr_hvac_action = HVACAction.COOLING
137  self._attr_hvac_mode_attr_hvac_mode = HVACMode.COOL
138  else:
139  self._attr_hvac_action_attr_hvac_action = HVACAction.IDLE
140 
141  self._attr_current_temperature_attr_current_temperature = status[OpenThermDataSource.THERMOSTAT].get(
142  gw_vars.DATA_ROOM_TEMP
143  )
144  temp_upd = status[OpenThermDataSource.THERMOSTAT].get(
145  gw_vars.DATA_ROOM_SETPOINT
146  )
147 
148  if self._target_temperature_target_temperature != temp_upd:
149  self._new_target_temperature_new_target_temperature = None
150  self._target_temperature_target_temperature = temp_upd
151 
152  # GPIO mode 5: 0 == Away
153  # GPIO mode 6: 1 == Away
154  gpio_a_state = status[OpenThermDataSource.GATEWAY].get(gw_vars.OTGW_GPIO_A)
155  gpio_b_state = status[OpenThermDataSource.GATEWAY].get(gw_vars.OTGW_GPIO_B)
156  self._away_mode_a_away_mode_a = gpio_a_state - 5 if gpio_a_state in (5, 6) else None
157  self._away_mode_b_away_mode_b = gpio_b_state - 5 if gpio_b_state in (5, 6) else None
158  self._away_state_a_away_state_a_away_state_a = (
159  (
160  status[OpenThermDataSource.GATEWAY].get(gw_vars.OTGW_GPIO_A_STATE)
161  == self._away_mode_a_away_mode_a
162  )
163  if self._away_mode_a_away_mode_a is not None
164  else False
165  )
166  self._away_state_b_away_state_b_away_state_b = (
167  (
168  status[OpenThermDataSource.GATEWAY].get(gw_vars.OTGW_GPIO_B_STATE)
169  == self._away_mode_b_away_mode_b
170  )
171  if self._away_mode_b_away_mode_b is not None
172  else False
173  )
174  self.async_write_ha_stateasync_write_ha_state()
175 
176  @property
177  def target_temperature(self) -> float | None:
178  """Return the temperature we try to reach."""
179  return self._new_target_temperature_new_target_temperature or self._target_temperature_target_temperature
180 
181  @property
182  def preset_mode(self) -> str:
183  """Return current preset mode."""
184  if self._away_state_a_away_state_a_away_state_a or self._away_state_b_away_state_b_away_state_b:
185  return PRESET_AWAY
186  return PRESET_NONE
187 
188  def set_preset_mode(self, preset_mode: str) -> None:
189  """Set the preset mode."""
190  _LOGGER.warning("Changing preset mode is not supported")
191 
192  async def async_set_temperature(self, **kwargs: Any) -> None:
193  """Set new target temperature."""
194  if ATTR_TEMPERATURE in kwargs:
195  temp = float(kwargs[ATTR_TEMPERATURE])
196  if temp == self.target_temperaturetarget_temperaturetarget_temperature:
197  return
198  self._new_target_temperature_new_target_temperature = await self._gateway_gateway.gateway.set_target_temp(
199  temp, self.temporary_ovrd_modetemporary_ovrd_mode
200  )
201  self.async_write_ha_stateasync_write_ha_state()
def receive_report(self, dict[OpenThermDataSource, dict] status)
Definition: climate.py:125
None __init__(self, OpenThermGatewayHub gw_hub, OpenThermClimateEntityDescription description, MappingProxyType[str, Any] options)
Definition: climate.py:99
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: climate.py:55
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103