Home Assistant Unofficial Reference 2024.12.1
coordinator.py
Go to the documentation of this file.
1 """Data update coordination for Rainforest RAVEn devices."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from dataclasses import asdict
7 from datetime import timedelta
8 import logging
9 from typing import Any
10 
11 from aioraven.data import DeviceInfo as RAVEnDeviceInfo
12 from aioraven.device import RAVEnConnectionError
13 from aioraven.serial import RAVEnSerialDevice
14 
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import CONF_DEVICE, CONF_MAC
17 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers.device_registry import DeviceInfo
19 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
20 
21 from .const import DOMAIN
22 
23 type RAVEnConfigEntry = ConfigEntry[RAVEnDataCoordinator]
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 
28 async def _get_meter_data(
29  device: RAVEnSerialDevice, meter: bytes
30 ) -> dict[str, dict[str, Any]]:
31  data = {}
32 
33  sum_info = await device.get_current_summation_delivered(meter=meter)
34  demand_info = await device.get_instantaneous_demand(meter=meter)
35  price_info = await device.get_current_price(meter=meter)
36 
37  if sum_info and sum_info.meter_mac_id == meter:
38  data["CurrentSummationDelivered"] = asdict(sum_info)
39 
40  if demand_info and demand_info.meter_mac_id == meter:
41  data["InstantaneousDemand"] = asdict(demand_info)
42 
43  if price_info and price_info.meter_mac_id == meter:
44  data["PriceCluster"] = asdict(price_info)
45 
46  return data
47 
48 
49 async def _get_all_data(
50  device: RAVEnSerialDevice, meter_macs: list[str]
51 ) -> dict[str, dict[str, Any]]:
52  data: dict[str, dict[str, Any]] = {"Meters": {}}
53 
54  for meter_mac in meter_macs:
55  data["Meters"][meter_mac] = await _get_meter_data(
56  device, bytes.fromhex(meter_mac)
57  )
58 
59  network_info = await device.get_network_info()
60 
61  if network_info and network_info.link_strength:
62  data["NetworkInfo"] = asdict(network_info)
63 
64  return data
65 
66 
68  """Communication coordinator for a Rainforest RAVEn device."""
69 
70  _raven_device: RAVEnSerialDevice | None = None
71  _device_info: RAVEnDeviceInfo | None = None
72  config_entry: RAVEnConfigEntry
73 
74  def __init__(self, hass: HomeAssistant, entry: RAVEnConfigEntry) -> None:
75  """Initialize the data object."""
76  super().__init__(
77  hass,
78  _LOGGER,
79  config_entry=entry,
80  name=DOMAIN,
81  update_interval=timedelta(seconds=30),
82  )
83 
84  @property
85  def device_mac_address(self) -> str | None:
86  """Return the MAC address of the device."""
87  if self._device_info_device_info and self._device_info_device_info.device_mac_id:
88  return self._device_info_device_info.device_mac_id.hex()
89  return None
90 
91  @property
92  def device_info(self) -> DeviceInfo | None:
93  """Return device info."""
94  if (device_info := self._device_info_device_info) and (
95  mac_address := self.device_mac_addressdevice_mac_address
96  ):
97  return DeviceInfo(
98  identifiers={(DOMAIN, mac_address)},
99  manufacturer=device_info.manufacturer,
100  model=device_info.model_id,
101  model_id=device_info.model_id,
102  name="RAVEn Device",
103  sw_version=device_info.fw_version,
104  hw_version=device_info.hw_version,
105  )
106  return None
107 
108  async def async_shutdown(self) -> None:
109  """Shutdown the coordinator."""
110  await self._cleanup_device_cleanup_device()
111  await super().async_shutdown()
112 
113  async def _async_update_data(self) -> dict[str, Any]:
114  try:
115  device = await self._get_device_get_device()
116  async with asyncio.timeout(5):
117  return await _get_all_data(device, self.config_entryconfig_entry.data[CONF_MAC])
118  except RAVEnConnectionError as err:
119  await self._cleanup_device_cleanup_device()
120  raise UpdateFailed(f"RAVEnConnectionError: {err}") from err
121  except TimeoutError:
122  await self._cleanup_device_cleanup_device()
123  raise
124 
125  async def _cleanup_device(self) -> None:
126  device, self._raven_device_raven_device = self._raven_device_raven_device, None
127  if device is not None:
128  await device.close()
129 
130  async def _get_device(self) -> RAVEnSerialDevice:
131  if self._raven_device_raven_device is not None:
132  return self._raven_device_raven_device
133 
134  device = RAVEnSerialDevice(self.config_entryconfig_entry.data[CONF_DEVICE])
135 
136  try:
137  async with asyncio.timeout(5):
138  await device.open()
139  await device.synchronize()
140  self._device_info_device_info = await device.get_device_info()
141  except:
142  await device.abort()
143  raise
144 
145  self._raven_device_raven_device = device
146  return device
None __init__(self, HomeAssistant hass, RAVEnConfigEntry entry)
Definition: coordinator.py:74
dict[str, dict[str, Any]] _get_all_data(RAVEnSerialDevice device, list[str] meter_macs)
Definition: coordinator.py:51
dict[str, dict[str, Any]] _get_meter_data(RAVEnSerialDevice device, bytes meter)
Definition: coordinator.py:30