Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Support for Xiaomi Gateways."""
2 
3 from datetime import timedelta
4 import logging
5 from typing import Any
6 
7 from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, CONF_MAC
8 from homeassistant.core import callback
9 from homeassistant.helpers import device_registry as dr
10 from homeassistant.helpers.device_registry import DeviceInfo, format_mac
11 from homeassistant.helpers.entity import Entity
12 from homeassistant.helpers.event import async_track_point_in_utc_time
13 from homeassistant.util.dt import utcnow
14 
15 from .const import DOMAIN
16 
17 _LOGGER = logging.getLogger(__name__)
18 
19 TIME_TILL_UNAVAILABLE = timedelta(minutes=150)
20 
21 
23  """Representation a base Xiaomi device."""
24 
25  _attr_should_poll = False
26 
27  def __init__(self, device, device_type, xiaomi_hub, config_entry):
28  """Initialize the Xiaomi device."""
29  self._state_state = None
30  self._is_available_is_available = True
31  self._sid_sid = device["sid"]
32  self._model_model = device["model"]
33  self._protocol_protocol = device["proto"]
34  self._name_name = f"{device_type}_{self._sid}"
35  self._device_name_device_name = f"{self._model}_{self._sid}"
36  self._type_type = device_type
37  self._write_to_hub_write_to_hub = xiaomi_hub.write_to_hub
38  self._get_from_hub_get_from_hub = xiaomi_hub.get_from_hub
39  self._extra_state_attributes_extra_state_attributes = {}
40  self._remove_unavailability_tracker_remove_unavailability_tracker = None
41  self._xiaomi_hub_xiaomi_hub = xiaomi_hub
42  self.parse_dataparse_data(device["data"], device["raw_data"])
43  self.parse_voltageparse_voltage(device["data"])
44 
45  if hasattr(self, "_data_key") and self._data_key:
46  self._unique_id_unique_id = f"{self._data_key}{self._sid}"
47  else:
48  self._unique_id_unique_id = f"{self._type}{self._sid}"
49 
50  self._gateway_id_gateway_id = config_entry.unique_id
51  if config_entry.data[CONF_MAC] == format_mac(self._sid_sid):
52  # this entity belongs to the gateway itself
53  self._is_gateway_is_gateway = True
54  self._device_id_device_id = config_entry.unique_id
55  else:
56  # this entity is connected through zigbee
57  self._is_gateway_is_gateway = False
58  self._device_id_device_id = self._sid_sid
59 
60  async def async_added_to_hass(self):
61  """Start unavailability tracking."""
62  self._xiaomi_hub_xiaomi_hub.callbacks[self._sid_sid].append(self.push_datapush_data)
63  self._async_track_unavailable_async_track_unavailable()
64 
65  @property
66  def name(self):
67  """Return the name of the device."""
68  return self._name_name
69 
70  @property
71  def unique_id(self) -> str:
72  """Return a unique ID."""
73  return self._unique_id_unique_id
74 
75  @property
76  def device_id(self):
77  """Return the device id of the Xiaomi Aqara device."""
78  return self._device_id_device_id
79 
80  @property
81  def device_info(self) -> DeviceInfo:
82  """Return the device info of the Xiaomi Aqara device."""
83  if self._is_gateway_is_gateway:
84  device_info = DeviceInfo(
85  identifiers={(DOMAIN, self._device_id_device_id)},
86  connections={(dr.CONNECTION_NETWORK_MAC, self._device_id_device_id)},
87  model=self._model_model,
88  )
89  else:
90  device_info = DeviceInfo(
91  connections={(dr.CONNECTION_ZIGBEE, self._device_id_device_id)},
92  identifiers={(DOMAIN, self._device_id_device_id)},
93  manufacturer="Xiaomi Aqara",
94  model=self._model_model,
95  name=self._device_name_device_name,
96  sw_version=self._protocol_protocol,
97  via_device=(DOMAIN, self._gateway_id_gateway_id),
98  )
99 
100  return device_info
101 
102  @property
103  def available(self):
104  """Return True if entity is available."""
105  return self._is_available_is_available
106 
107  @property
109  """Return the state attributes."""
110  return self._extra_state_attributes_extra_state_attributes
111 
112  @callback
113  def _async_set_unavailable(self, now):
114  """Set state to UNAVAILABLE."""
115  self._remove_unavailability_tracker_remove_unavailability_tracker = None
116  self._is_available_is_available = False
117  self.async_write_ha_stateasync_write_ha_state()
118 
119  @callback
121  if self._remove_unavailability_tracker_remove_unavailability_tracker:
122  self._remove_unavailability_tracker_remove_unavailability_tracker()
123  self._remove_unavailability_tracker_remove_unavailability_tracker = async_track_point_in_utc_time(
124  self.hasshass, self._async_set_unavailable_async_set_unavailable, utcnow() + TIME_TILL_UNAVAILABLE
125  )
126  if not self._is_available_is_available:
127  self._is_available_is_available = True
128  return True
129  return False
130 
131  def push_data(self, data: dict[str, Any], raw_data: dict[Any, Any]) -> None:
132  """Push from Hub running in another thread."""
133  self.hasshass.loop.call_soon_threadsafe(self.async_push_dataasync_push_data, data, raw_data)
134 
135  @callback
136  def async_push_data(self, data: dict[str, Any], raw_data: dict[Any, Any]) -> None:
137  """Push from Hub handled in the event loop."""
138  _LOGGER.debug("PUSH >> %s: %s", self, data)
139  was_unavailable = self._async_track_unavailable_async_track_unavailable()
140  is_data = self.parse_dataparse_data(data, raw_data)
141  is_voltage = self.parse_voltageparse_voltage(data)
142  if is_data or is_voltage or was_unavailable:
143  self.async_write_ha_stateasync_write_ha_state()
144 
145  def parse_voltage(self, data):
146  """Parse battery level data sent by gateway."""
147  if "voltage" in data:
148  voltage_key = "voltage"
149  elif "battery_voltage" in data:
150  voltage_key = "battery_voltage"
151  else:
152  return False
153 
154  max_volt = 3300
155  min_volt = 2800
156  voltage = data[voltage_key]
157  self._extra_state_attributes_extra_state_attributes[ATTR_VOLTAGE] = round(voltage / 1000.0, 2)
158  voltage = min(voltage, max_volt)
159  voltage = max(voltage, min_volt)
160  percent = ((voltage - min_volt) / (max_volt - min_volt)) * 100
161  self._extra_state_attributes_extra_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1)
162  return True
163 
164  def parse_data(self, data, raw_data):
165  """Parse data sent by gateway."""
166  raise NotImplementedError
def __init__(self, device, device_type, xiaomi_hub, config_entry)
Definition: entity.py:27
None push_data(self, dict[str, Any] data, dict[Any, Any] raw_data)
Definition: entity.py:131
None async_push_data(self, dict[str, Any] data, dict[Any, Any] raw_data)
Definition: entity.py:136
CALLBACK_TYPE async_track_point_in_utc_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)
Definition: event.py:1542