Home Assistant Unofficial Reference 2024.12.1
tado_connector.py
Go to the documentation of this file.
1 """Tado Connector a class to store the data as an object."""
2 
3 from datetime import datetime, timedelta
4 import logging
5 from typing import Any
6 
7 from PyTado.interface import Tado
8 from requests import RequestException
9 
10 from homeassistant.components.climate import PRESET_AWAY, PRESET_HOME
11 from homeassistant.core import HomeAssistant
12 from homeassistant.exceptions import HomeAssistantError
13 from homeassistant.helpers.dispatcher import dispatcher_send
14 from homeassistant.util import Throttle
15 
16 from .const import (
17  INSIDE_TEMPERATURE_MEASUREMENT,
18  PRESET_AUTO,
19  SIGNAL_TADO_MOBILE_DEVICE_UPDATE_RECEIVED,
20  SIGNAL_TADO_UPDATE_RECEIVED,
21  TEMP_OFFSET,
22 )
23 
24 MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=4)
25 SCAN_INTERVAL = timedelta(minutes=5)
26 SCAN_MOBILE_DEVICE_INTERVAL = timedelta(seconds=30)
27 
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 
33  """An object to store the Tado data."""
34 
35  def __init__(
36  self, hass: HomeAssistant, username: str, password: str, fallback: str
37  ) -> None:
38  """Initialize Tado Connector."""
39  self.hasshass = hass
40  self._username_username = username
41  self._password_password = password
42  self._fallback_fallback = fallback
43 
44  self.home_idhome_id: int = 0
45  self.home_namehome_name = None
46  self.tadotado = None
47  self.zoneszones: list[dict[Any, Any]] = []
48  self.devicesdevices: list[dict[Any, Any]] = []
49  self.data: dict[str, dict] = {
50  "device": {},
51  "mobile_device": {},
52  "weather": {},
53  "geofence": {},
54  "zone": {},
55  }
56 
57  @property
58  def fallback(self):
59  """Return fallback flag to Smart Schedule."""
60  return self._fallback_fallback
61 
62  def setup(self):
63  """Connect to Tado and fetch the zones."""
64  self.tadotado = Tado(self._username_username, self._password_password)
65  # Load zones and devices
66  self.zoneszones = self.tadotado.get_zones()
67  self.devicesdevices = self.tadotado.get_devices()
68  tado_home = self.tadotado.get_me()["homes"][0]
69  self.home_idhome_id = tado_home["id"]
70  self.home_namehome_name = tado_home["name"]
71 
72  def get_mobile_devices(self):
73  """Return the Tado mobile devices."""
74  return self.tadotado.get_mobile_devices()
75 
76  @Throttle(MIN_TIME_BETWEEN_UPDATES)
77  def update(self):
78  """Update the registered zones."""
79  self.update_devicesupdate_devices()
80  self.update_mobile_devicesupdate_mobile_devices()
81  self.update_zonesupdate_zones()
82  self.update_homeupdate_home()
83 
84  def update_mobile_devices(self) -> None:
85  """Update the mobile devices."""
86  try:
87  mobile_devices = self.get_mobile_devicesget_mobile_devices()
88  except RuntimeError:
89  _LOGGER.error("Unable to connect to Tado while updating mobile devices")
90  return
91 
92  if not mobile_devices:
93  _LOGGER.debug("No linked mobile devices found for home ID %s", self.home_idhome_id)
94  return
95 
96  # Errors are planned to be converted to exceptions
97  # in PyTado library, so this can be removed
98  if isinstance(mobile_devices, dict) and mobile_devices.get("errors"):
99  _LOGGER.error(
100  "Error for home ID %s while updating mobile devices: %s",
101  self.home_idhome_id,
102  mobile_devices["errors"],
103  )
104  return
105 
106  for mobile_device in mobile_devices:
107  self.data["mobile_device"][mobile_device["id"]] = mobile_device
108  _LOGGER.debug(
109  "Dispatching update to %s mobile device: %s",
110  self.home_idhome_id,
111  mobile_device,
112  )
113 
115  self.hasshass,
116  SIGNAL_TADO_MOBILE_DEVICE_UPDATE_RECEIVED.format(self.home_idhome_id),
117  )
118 
119  def update_devices(self):
120  """Update the device data from Tado."""
121  try:
122  devices = self.tadotado.get_devices()
123  except RuntimeError:
124  _LOGGER.error("Unable to connect to Tado while updating devices")
125  return
126 
127  if not devices:
128  _LOGGER.debug("No linked devices found for home ID %s", self.home_idhome_id)
129  return
130 
131  # Errors are planned to be converted to exceptions
132  # in PyTado library, so this can be removed
133  if isinstance(devices, dict) and devices.get("errors"):
134  _LOGGER.error(
135  "Error for home ID %s while updating devices: %s",
136  self.home_idhome_id,
137  devices["errors"],
138  )
139  return
140 
141  for device in devices:
142  device_short_serial_no = device["shortSerialNo"]
143  _LOGGER.debug("Updating device %s", device_short_serial_no)
144  try:
145  if (
146  INSIDE_TEMPERATURE_MEASUREMENT
147  in device["characteristics"]["capabilities"]
148  ):
149  device[TEMP_OFFSET] = self.tadotado.get_device_info(
150  device_short_serial_no, TEMP_OFFSET
151  )
152  except RuntimeError:
153  _LOGGER.error(
154  "Unable to connect to Tado while updating device %s",
155  device_short_serial_no,
156  )
157  return
158 
159  self.data["device"][device_short_serial_no] = device
160 
161  _LOGGER.debug(
162  "Dispatching update to %s device %s: %s",
163  self.home_idhome_id,
164  device_short_serial_no,
165  device,
166  )
168  self.hasshass,
169  SIGNAL_TADO_UPDATE_RECEIVED.format(
170  self.home_idhome_id, "device", device_short_serial_no
171  ),
172  )
173 
174  def update_zones(self):
175  """Update the zone data from Tado."""
176  try:
177  zone_states = self.tadotado.get_zone_states()["zoneStates"]
178  except RuntimeError:
179  _LOGGER.error("Unable to connect to Tado while updating zones")
180  return
181 
182  for zone in zone_states:
183  self.update_zoneupdate_zone(int(zone))
184 
185  def update_zone(self, zone_id):
186  """Update the internal data from Tado."""
187  _LOGGER.debug("Updating zone %s", zone_id)
188  try:
189  data = self.tadotado.get_zone_state(zone_id)
190  except RuntimeError:
191  _LOGGER.error("Unable to connect to Tado while updating zone %s", zone_id)
192  return
193 
194  self.data["zone"][zone_id] = data
195 
196  _LOGGER.debug(
197  "Dispatching update to %s zone %s: %s",
198  self.home_idhome_id,
199  zone_id,
200  data,
201  )
203  self.hasshass,
204  SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_idhome_id, "zone", zone_id),
205  )
206 
207  def update_home(self):
208  """Update the home data from Tado."""
209  try:
210  self.data["weather"] = self.tadotado.get_weather()
211  self.data["geofence"] = self.tadotado.get_home_state()
213  self.hasshass,
214  SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_idhome_id, "home", "data"),
215  )
216  except RuntimeError:
217  _LOGGER.error(
218  "Unable to connect to Tado while updating weather and geofence data"
219  )
220  return
221 
222  def get_capabilities(self, zone_id):
223  """Return the capabilities of the devices."""
224  return self.tadotado.get_capabilities(zone_id)
225 
227  """Return whether the Tado Home supports auto geofencing."""
228  return self.tadotado.get_auto_geofencing_supported()
229 
230  def reset_zone_overlay(self, zone_id):
231  """Reset the zone back to the default operation."""
232  self.tadotado.reset_zone_overlay(zone_id)
233  self.update_zoneupdate_zone(zone_id)
234 
236  self,
237  presence=PRESET_HOME,
238  ):
239  """Set the presence to home, away or auto."""
240  if presence == PRESET_AWAY:
241  self.tadotado.set_away()
242  elif presence == PRESET_HOME:
243  self.tadotado.set_home()
244  elif presence == PRESET_AUTO:
245  self.tadotado.set_auto()
246 
247  # Update everything when changing modes
248  self.update_zonesupdate_zones()
249  self.update_homeupdate_home()
250 
252  self,
253  zone_id=None,
254  overlay_mode=None,
255  temperature=None,
256  duration=None,
257  device_type="HEATING",
258  mode=None,
259  fan_speed=None,
260  swing=None,
261  fan_level=None,
262  vertical_swing=None,
263  horizontal_swing=None,
264  ):
265  """Set a zone overlay."""
266  _LOGGER.debug(
267  (
268  "Set overlay for zone %s: overlay_mode=%s, temp=%s, duration=%s,"
269  " type=%s, mode=%s fan_speed=%s swing=%s fan_level=%s vertical_swing=%s horizontal_swing=%s"
270  ),
271  zone_id,
272  overlay_mode,
273  temperature,
274  duration,
275  device_type,
276  mode,
277  fan_speed,
278  swing,
279  fan_level,
280  vertical_swing,
281  horizontal_swing,
282  )
283 
284  try:
285  self.tadotado.set_zone_overlay(
286  zone_id,
287  overlay_mode,
288  temperature,
289  duration,
290  device_type,
291  "ON",
292  mode,
293  fan_speed=fan_speed,
294  swing=swing,
295  fan_level=fan_level,
296  vertical_swing=vertical_swing,
297  horizontal_swing=horizontal_swing,
298  )
299 
300  except RequestException as exc:
301  _LOGGER.error("Could not set zone overlay: %s", exc)
302 
303  self.update_zoneupdate_zone(zone_id)
304 
305  def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"):
306  """Set a zone to off."""
307  try:
308  self.tadotado.set_zone_overlay(
309  zone_id, overlay_mode, None, None, device_type, "OFF"
310  )
311  except RequestException as exc:
312  _LOGGER.error("Could not set zone overlay: %s", exc)
313 
314  self.update_zoneupdate_zone(zone_id)
315 
316  def set_temperature_offset(self, device_id, offset):
317  """Set temperature offset of device."""
318  try:
319  self.tadotado.set_temp_offset(device_id, offset)
320  except RequestException as exc:
321  _LOGGER.error("Could not set temperature offset: %s", exc)
322 
323  def set_meter_reading(self, reading: int) -> dict[str, Any]:
324  """Send meter reading to Tado."""
325  dt: str = datetime.now().strftime("%Y-%m-%d")
326  if self.tadotado is None:
327  raise HomeAssistantError("Tado client is not initialized")
328 
329  try:
330  return self.tadotado.set_eiq_meter_readings(date=dt, reading=reading)
331  except RequestException as exc:
332  raise HomeAssistantError("Could not set meter reading") from exc
None __init__(self, HomeAssistant hass, str username, str password, str fallback)
def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING")
def set_zone_overlay(self, zone_id=None, overlay_mode=None, temperature=None, duration=None, device_type="HEATING", mode=None, fan_speed=None, swing=None, fan_level=None, vertical_swing=None, horizontal_swing=None)
DeviceInfo get_device_info(str coordinates, str name)
Definition: __init__.py:156
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137