Home Assistant Unofficial Reference 2024.12.1
updater.py
Go to the documentation of this file.
1 """Support for fetching data from Broadlink devices."""
2 
3 from __future__ import annotations
4 
5 from abc import ABC, abstractmethod
6 from datetime import datetime, timedelta
7 import logging
8 from typing import TYPE_CHECKING, Any, Generic
9 
10 import broadlink as blk
11 from broadlink.exceptions import AuthorizationError, BroadlinkException
12 from typing_extensions import TypeVar
13 
14 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
15 from homeassistant.util import dt as dt_util
16 
17 if TYPE_CHECKING:
18  from .device import BroadlinkDevice
19 
20 _ApiT = TypeVar("_ApiT", bound=blk.Device)
21 
22 _LOGGER = logging.getLogger(__name__)
23 
24 
25 def get_update_manager(device: BroadlinkDevice[_ApiT]) -> BroadlinkUpdateManager[_ApiT]:
26  """Return an update manager for a given Broadlink device."""
27  update_managers: dict[str, type[BroadlinkUpdateManager]] = {
28  "A1": BroadlinkA1UpdateManager,
29  "BG1": BroadlinkBG1UpdateManager,
30  "HYS": BroadlinkThermostatUpdateManager,
31  "LB1": BroadlinkLB1UpdateManager,
32  "LB2": BroadlinkLB1UpdateManager,
33  "MP1": BroadlinkMP1UpdateManager,
34  "MP1S": BroadlinkMP1SUpdateManager,
35  "RM4MINI": BroadlinkRMUpdateManager,
36  "RM4PRO": BroadlinkRMUpdateManager,
37  "RMMINI": BroadlinkRMUpdateManager,
38  "RMMINIB": BroadlinkRMUpdateManager,
39  "RMPRO": BroadlinkRMUpdateManager,
40  "SP1": BroadlinkSP1UpdateManager,
41  "SP2": BroadlinkSP2UpdateManager,
42  "SP2S": BroadlinkSP2UpdateManager,
43  "SP3": BroadlinkSP2UpdateManager,
44  "SP3S": BroadlinkSP2UpdateManager,
45  "SP4": BroadlinkSP4UpdateManager,
46  "SP4B": BroadlinkSP4UpdateManager,
47  }
48  return update_managers[device.api.type](device)
49 
50 
51 class BroadlinkUpdateManager(ABC, Generic[_ApiT]):
52  """Representation of a Broadlink update manager.
53 
54  Implement this class to manage fetching data from the device and to
55  monitor device availability.
56  """
57 
58  SCAN_INTERVAL = timedelta(minutes=1)
59 
60  def __init__(self, device: BroadlinkDevice[_ApiT]) -> None:
61  """Initialize the update manager."""
62  self.devicedevice = device
64  device.hass,
65  _LOGGER,
66  name=f"{device.name} ({device.api.model} at {device.api.host[0]})",
67  update_method=self.async_updateasync_update,
68  update_interval=self.SCAN_INTERVALSCAN_INTERVAL,
69  )
70  self.availableavailable: bool | None = None
71  self.last_updatelast_update: datetime | None = None
72 
73  async def async_update(self) -> dict[str, Any] | None:
74  """Fetch data from the device and update availability."""
75  try:
76  data = await self.async_fetch_dataasync_fetch_data()
77 
78  except (BroadlinkException, OSError) as err:
79  if (
80  self.availableavailable
81  and self.last_updatelast_update
82  and (
83  dt_util.utcnow() - self.last_updatelast_update > self.SCAN_INTERVALSCAN_INTERVAL * 3
84  or isinstance(err, (AuthorizationError, OSError))
85  )
86  ):
87  self.availableavailable = False
88  _LOGGER.warning(
89  "Disconnected from %s (%s at %s)",
90  self.devicedevice.name,
91  self.devicedevice.api.model,
92  self.devicedevice.api.host[0],
93  )
94  raise UpdateFailed(err) from err
95 
96  if self.availableavailable is False:
97  _LOGGER.warning(
98  "Connected to %s (%s at %s)",
99  self.devicedevice.name,
100  self.devicedevice.api.model,
101  self.devicedevice.api.host[0],
102  )
103  self.availableavailable = True
104  self.last_updatelast_update = dt_util.utcnow()
105  return data
106 
107  @abstractmethod
108  async def async_fetch_data(self) -> dict[str, Any] | None:
109  """Fetch data from the device."""
110 
111 
112 class BroadlinkA1UpdateManager(BroadlinkUpdateManager[blk.a1]):
113  """Manages updates for Broadlink A1 devices."""
114 
115  SCAN_INTERVAL = timedelta(seconds=10)
116 
117  async def async_fetch_data(self) -> dict[str, Any]:
118  """Fetch data from the device."""
119  return await self.devicedevice.async_request(self.devicedevice.api.check_sensors_raw)
120 
121 
123  """Manages updates for Broadlink MP1 devices."""
124 
125  async def async_fetch_data(self) -> dict[str, Any]:
126  """Fetch data from the device."""
127  return await self.devicedevice.async_request(self.devicedevice.api.check_power)
128 
129 
131  """Manages updates for Broadlink MP1 devices."""
132 
133  async def async_fetch_data(self) -> dict[str, Any]:
134  """Fetch data from the device."""
135  power = await self.devicedevice.async_request(self.devicedevice.api.check_power)
136  sensors = await self.devicedevice.async_request(self.devicedevice.api.get_state)
137  return {**power, **sensors}
138 
139 
141  """Manages updates for Broadlink remotes."""
142 
143  async def async_fetch_data(self) -> dict[str, Any]:
144  """Fetch data from the device."""
145  device = self.devicedevice
146 
147  if hasattr(device.api, "check_sensors"):
148  data = await device.async_request(device.api.check_sensors)
149  return self.normalizenormalize(data, self.coordinatorcoordinator.data)
150 
151  await device.async_request(device.api.update)
152  return {}
153 
154  @staticmethod
156  data: dict[str, Any], previous_data: dict[str, Any] | None
157  ) -> dict[str, Any]:
158  """Fix firmware issue.
159 
160  See https://github.com/home-assistant/core/issues/42100.
161  """
162  if data["temperature"] == -7:
163  if previous_data is None or previous_data["temperature"] is None:
164  data["temperature"] = None
165  elif abs(previous_data["temperature"] - data["temperature"]) > 3:
166  data["temperature"] = previous_data["temperature"]
167  return data
168 
169 
171  """Manages updates for Broadlink SP1 devices."""
172 
173  async def async_fetch_data(self) -> dict[str, Any] | None:
174  """Fetch data from the device."""
175  return None
176 
177 
179  """Manages updates for Broadlink SP2 devices."""
180 
181  async def async_fetch_data(self) -> dict[str, Any]:
182  """Fetch data from the device."""
183  device = self.devicedevice
184 
185  data = {}
186  data["pwr"] = await device.async_request(device.api.check_power)
187 
188  if hasattr(device.api, "get_energy"):
189  data["power"] = await device.async_request(device.api.get_energy)
190 
191  return data
192 
193 
195  """Manages updates for Broadlink BG1 devices."""
196 
197  async def async_fetch_data(self) -> dict[str, Any]:
198  """Fetch data from the device."""
199  return await self.devicedevice.async_request(self.devicedevice.api.get_state)
200 
201 
203  """Manages updates for Broadlink SP4 devices."""
204 
205  async def async_fetch_data(self) -> dict[str, Any]:
206  """Fetch data from the device."""
207  return await self.devicedevice.async_request(self.devicedevice.api.get_state)
208 
209 
211  """Manages updates for Broadlink LB1 devices."""
212 
213  async def async_fetch_data(self) -> dict[str, Any]:
214  """Fetch data from the device."""
215  return await self.devicedevice.async_request(self.devicedevice.api.get_state)
216 
217 
219  """Manages updates for thermostats with Broadlink DNA."""
220 
221  async def async_fetch_data(self) -> dict[str, Any]:
222  """Fetch data from the device."""
223  return await self.devicedevice.async_request(self.devicedevice.api.get_full_status)