Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Update coordinators for rainbird."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from dataclasses import dataclass
7 import datetime
8 import logging
9 
10 import aiohttp
11 from pyrainbird.async_client import (
12  AsyncRainbirdController,
13  RainbirdApiException,
14  RainbirdDeviceBusyException,
15 )
16 from pyrainbird.data import ModelAndVersion, Schedule
17 
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers.debounce import Debouncer
21 from homeassistant.helpers.device_registry import DeviceInfo
22 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
23 
24 from .const import DOMAIN, MANUFACTURER, TIMEOUT_SECONDS
25 
26 UPDATE_INTERVAL = datetime.timedelta(minutes=1)
27 # The calendar data requires RPCs for each program/zone, and the data rarely
28 # changes, so we refresh it less often.
29 CALENDAR_UPDATE_INTERVAL = datetime.timedelta(minutes=15)
30 
31 # The valves state are not immediately reflected after issuing a command. We add
32 # small delay to give additional time to reflect the new state.
33 DEBOUNCER_COOLDOWN = 5
34 
35 # Rainbird devices can only accept a single request at a time
36 CONECTION_LIMIT = 1
37 
38 _LOGGER = logging.getLogger(__name__)
39 
40 
41 @dataclass
43  """Data retrieved from a Rain Bird device."""
44 
45  zones: set[int]
46  active_zones: set[int]
47  rain: bool
48  rain_delay: int
49 
50 
51 def async_create_clientsession() -> aiohttp.ClientSession:
52  """Create a rainbird async_create_clientsession with a connection limit."""
53  return aiohttp.ClientSession(
54  connector=aiohttp.TCPConnector(limit=CONECTION_LIMIT),
55  )
56 
57 
59  """Coordinator for rainbird API calls."""
60 
61  def __init__(
62  self,
63  hass: HomeAssistant,
64  name: str,
65  controller: AsyncRainbirdController,
66  unique_id: str | None,
67  model_info: ModelAndVersion,
68  ) -> None:
69  """Initialize RainbirdUpdateCoordinator."""
70  super().__init__(
71  hass,
72  _LOGGER,
73  name=name,
74  update_interval=UPDATE_INTERVAL,
75  request_refresh_debouncer=Debouncer(
76  hass, _LOGGER, cooldown=DEBOUNCER_COOLDOWN, immediate=False
77  ),
78  )
79  self._controller_controller = controller
80  self._unique_id_unique_id = unique_id
81  self._zones: set[int] | None = None
82  self._model_info_model_info = model_info
83 
84  @property
85  def controller(self) -> AsyncRainbirdController:
86  """Return the API client for the device."""
87  return self._controller_controller
88 
89  @property
90  def unique_id(self) -> str | None:
91  """Return the config entry unique id."""
92  return self._unique_id_unique_id
93 
94  @property
95  def device_name(self) -> str:
96  """Device name for the rainbird controller."""
97  return f"{MANUFACTURER} Controller"
98 
99  @property
100  def device_info(self) -> DeviceInfo | None:
101  """Return information about the device."""
102  if self._unique_id_unique_id is None:
103  return None
104  return DeviceInfo(
105  name=self.device_namedevice_name,
106  identifiers={(DOMAIN, self._unique_id_unique_id)},
107  manufacturer=MANUFACTURER,
108  model=self._model_info_model_info.model_name,
109  sw_version=f"{self._model_info.major}.{self._model_info.minor}",
110  )
111 
112  async def _async_update_data(self) -> RainbirdDeviceState:
113  """Fetch data from Rain Bird device."""
114  try:
115  async with asyncio.timeout(TIMEOUT_SECONDS):
116  return await self._fetch_data_fetch_data()
117  except RainbirdDeviceBusyException as err:
118  raise UpdateFailed("Rain Bird device is busy") from err
119  except RainbirdApiException as err:
120  raise UpdateFailed("Rain Bird device failure") from err
121 
122  async def _fetch_data(self) -> RainbirdDeviceState:
123  """Fetch data from the Rain Bird device.
124 
125  Rainbird devices can only reliably handle a single request at a time,
126  so the requests are sent serially.
127  """
128  available_stations = await self._controller_controller.get_available_stations()
129  states = await self._controller_controller.get_zone_states()
130  rain = await self._controller_controller.get_rain_sensor_state()
131  rain_delay = await self._controller_controller.get_rain_delay()
132  return RainbirdDeviceState(
133  zones=available_stations.active_set,
134  active_zones=states.active_set,
135  rain=rain,
136  rain_delay=rain_delay,
137  )
138 
139 
141  """Coordinator for rainbird irrigation schedule calls."""
142 
143  config_entry: ConfigEntry
144 
145  def __init__(
146  self,
147  hass: HomeAssistant,
148  name: str,
149  controller: AsyncRainbirdController,
150  ) -> None:
151  """Initialize ZoneStateUpdateCoordinator."""
152  super().__init__(
153  hass,
154  _LOGGER,
155  name=name,
156  update_method=self._async_update_data_async_update_data_async_update_data,
157  update_interval=CALENDAR_UPDATE_INTERVAL,
158  )
159  self._controller_controller = controller
160 
161  async def _async_update_data(self) -> Schedule:
162  """Fetch data from Rain Bird device."""
163  try:
164  async with asyncio.timeout(TIMEOUT_SECONDS):
165  return await self._controller_controller.get_schedule()
166  except RainbirdApiException as err:
167  raise UpdateFailed(f"Error communicating with Device: {err}") from err
None __init__(self, HomeAssistant hass, str name, AsyncRainbirdController controller)
Definition: coordinator.py:150
None __init__(self, HomeAssistant hass, str name, AsyncRainbirdController controller, str|None unique_id, ModelAndVersion model_info)
Definition: coordinator.py:68
aiohttp.ClientSession async_create_clientsession()
Definition: coordinator.py:51