Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Helper and wrapper classes for Gree module."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime, timedelta
6 import logging
7 from typing import Any
8 
9 from greeclimate.device import Device, DeviceInfo
10 from greeclimate.discovery import Discovery, Listener
11 from greeclimate.exceptions import DeviceNotBoundError, DeviceTimeoutError
12 from greeclimate.network import Response
13 
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers.dispatcher import async_dispatcher_send
16 from homeassistant.helpers.json import json_dumps
17 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
18 from homeassistant.util.dt import utcnow
19 
20 from .const import (
21  COORDINATORS,
22  DISCOVERY_TIMEOUT,
23  DISPATCH_DEVICE_DISCOVERED,
24  DOMAIN,
25  MAX_ERRORS,
26  UPDATE_INTERVAL,
27 )
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 
33  """Manages polling for state changes from the device."""
34 
35  def __init__(self, hass: HomeAssistant, device: Device) -> None:
36  """Initialize the data update coordinator."""
37  DataUpdateCoordinator.__init__(
38  self,
39  hass,
40  _LOGGER,
41  name=f"{DOMAIN}-{device.device_info.name}",
42  update_interval=timedelta(seconds=UPDATE_INTERVAL),
43  always_update=False,
44  )
45  self.devicedevice = device
46  self.devicedevice.add_handler(Response.DATA, self.device_state_updateddevice_state_updated)
47  self.devicedevice.add_handler(Response.RESULT, self.device_state_updateddevice_state_updated)
48 
49  self._error_count_error_count: int = 0
50  self._last_response_time_last_response_time: datetime = utcnow()
51  self._last_error_time_last_error_time: datetime | None = None
52 
53  def device_state_updated(self, *args: Any) -> None:
54  """Handle device state updates."""
55  _LOGGER.debug("Device state updated: %s", json_dumps(args))
56  self._error_count_error_count = 0
57  self._last_response_time_last_response_time = utcnow()
58  self.async_set_updated_dataasync_set_updated_data(self.devicedevice.raw_properties)
59 
60  async def _async_update_data(self) -> dict[str, Any]:
61  """Update the state of the device."""
62  _LOGGER.debug(
63  "Updating device state: %s, error count: %d", self.namename, self._error_count_error_count
64  )
65  try:
66  await self.devicedevice.update_state()
67  except DeviceNotBoundError as error:
68  raise UpdateFailed(
69  f"Device {self.name} is unavailable, device is not bound."
70  ) from error
71  except DeviceTimeoutError as error:
72  self._error_count_error_count += 1
73 
74  # Under normal conditions GREE units timeout every once in a while
75  if self.last_update_successlast_update_success and self._error_count_error_count >= MAX_ERRORS:
76  _LOGGER.warning(
77  "Device %s is unavailable: %s", self.namename, self.devicedevice.device_info
78  )
79  raise UpdateFailed(
80  f"Device {self.name} is unavailable, could not send update request"
81  ) from error
82  else:
83  # raise update failed if time for more than MAX_ERRORS has passed since last update
84  now = utcnow()
85  elapsed_success = now - self._last_response_time_last_response_time
86  if self.update_intervalupdate_intervalupdate_intervalupdate_interval and elapsed_success >= self.update_intervalupdate_intervalupdate_intervalupdate_interval:
87  if not self._last_error_time_last_error_time or (
88  (now - self.update_intervalupdate_intervalupdate_intervalupdate_interval) >= self._last_error_time_last_error_time
89  ):
90  self._last_error_time_last_error_time = now
91  self._error_count_error_count += 1
92 
93  _LOGGER.warning(
94  "Device %s is unresponsive for %s seconds",
95  self.namename,
96  elapsed_success,
97  )
98  if self.last_update_successlast_update_success and self._error_count_error_count >= MAX_ERRORS:
99  raise UpdateFailed(
100  f"Device {self.name} is unresponsive for too long and now unavailable"
101  )
102 
103  return self.devicedevice.raw_properties
104 
105  async def push_state_update(self):
106  """Send state updates to the physical device."""
107  try:
108  return await self.devicedevice.push_state_update()
109  except DeviceTimeoutError:
110  _LOGGER.warning(
111  "Timeout send state update to: %s (%s)",
112  self.namename,
113  self.devicedevice.device_info,
114  )
115 
116 
117 class DiscoveryService(Listener):
118  """Discovery event handler for gree devices."""
119 
120  def __init__(self, hass: HomeAssistant) -> None:
121  """Initialize discovery service."""
122  super().__init__()
123  self.hasshass = hass
124 
125  self.discoverydiscovery = Discovery(DISCOVERY_TIMEOUT)
126  self.discoverydiscovery.add_listener(self)
127 
128  hass.data[DOMAIN].setdefault(COORDINATORS, [])
129 
130  async def device_found(self, device_info: DeviceInfo) -> None:
131  """Handle new device found on the network."""
132 
133  device = Device(device_info)
134  try:
135  await device.bind()
136  except DeviceNotBoundError:
137  _LOGGER.error("Unable to bind to gree device: %s", device_info)
138  except DeviceTimeoutError:
139  _LOGGER.error("Timeout trying to bind to gree device: %s", device_info)
140 
141  _LOGGER.debug(
142  "Adding Gree device %s at %s:%i",
143  device.device_info.name,
144  device.device_info.ip,
145  device.device_info.port,
146  )
147  coordo = DeviceDataUpdateCoordinator(self.hasshass, device)
148  self.hasshass.data[DOMAIN][COORDINATORS].append(coordo)
149  await coordo.async_refresh()
150 
151  async_dispatcher_send(self.hasshass, DISPATCH_DEVICE_DISCOVERED, coordo)
152 
153  async def device_update(self, device_info: DeviceInfo) -> None:
154  """Handle updates in device information, update if ip has changed."""
155  for coordinator in self.hasshass.data[DOMAIN][COORDINATORS]:
156  if coordinator.device.device_info.mac == device_info.mac:
157  coordinator.device.device_info.ip = device_info.ip
158  await coordinator.async_refresh()
None __init__(self, HomeAssistant hass, Device device)
Definition: coordinator.py:35
None device_update(self, DeviceInfo device_info)
Definition: coordinator.py:153
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
str json_dumps(Any data)
Definition: json.py:149