Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Homematic base entity."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 from datetime import timedelta
7 import logging
8 
9 from pyhomematic import HMConnection
10 from pyhomematic.devicetypes.generic import HMGeneric
11 
12 from homeassistant.const import ATTR_NAME
14 from homeassistant.helpers.entity import Entity, EntityDescription
15 from homeassistant.helpers.event import track_time_interval
16 
17 from .const import (
18  ATTR_ADDRESS,
19  ATTR_CHANNEL,
20  ATTR_INTERFACE,
21  ATTR_PARAM,
22  ATTR_UNIQUE_ID,
23  DATA_HOMEMATIC,
24  DOMAIN,
25  HM_ATTRIBUTE_SUPPORT,
26 )
27 
28 _LOGGER = logging.getLogger(__name__)
29 
30 SCAN_INTERVAL_HUB = timedelta(seconds=300)
31 SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
32 
33 
35  """The HomeMatic device base object."""
36 
37  _homematic: HMConnection
38  _hmdevice: HMGeneric
39  _attr_should_poll = False
40 
41  def __init__(
42  self,
43  config: dict[str, str],
44  entity_description: EntityDescription | None = None,
45  ) -> None:
46  """Initialize a generic HomeMatic device."""
47  self._name_name = config.get(ATTR_NAME)
48  self._address_address = config.get(ATTR_ADDRESS)
49  self._interface_interface = config.get(ATTR_INTERFACE)
50  self._channel_channel = config.get(ATTR_CHANNEL)
51  self._state_state = config.get(ATTR_PARAM)
52  self._unique_id_unique_id = config.get(ATTR_UNIQUE_ID)
53  self._data: dict[str, str] = {}
54  self._connected_connected = False
55  self._available_available = False
56  self._channel_map: dict[str, str] = {}
57 
58  if entity_description is not None:
59  self.entity_descriptionentity_description = entity_description
60 
61  # Set parameter to uppercase
62  if self._state_state:
63  self._state_state = self._state_state.upper()
64 
65  async def async_added_to_hass(self):
66  """Load data init callbacks."""
67  self._subscribe_homematic_events_subscribe_homematic_events()
68 
69  @property
70  def unique_id(self):
71  """Return unique ID. HomeMatic entity IDs are unique by default."""
72  return self._unique_id_unique_id.replace(" ", "_")
73 
74  @property
75  def name(self):
76  """Return the name of the device."""
77  return self._name_name
78 
79  @property
80  def available(self):
81  """Return true if device is available."""
82  return self._available_available
83 
84  @property
86  """Return device specific state attributes."""
87  # Static attributes
88  attr = {
89  "id": self._hmdevice_hmdevice.ADDRESS,
90  "interface": self._interface_interface,
91  }
92 
93  # Generate a dictionary with attributes
94  for node, data in HM_ATTRIBUTE_SUPPORT.items():
95  # Is an attribute and exists for this object
96  if node in self._data:
97  value = data[1].get(self._data[node], self._data[node])
98  attr[data[0]] = value
99 
100  return attr
101 
102  def update(self):
103  """Connect to HomeMatic init values."""
104  if self._connected_connected:
105  return True
106 
107  # Initialize
108  self._homematic_homematic = self.hasshass.data[DATA_HOMEMATIC]
109  self._hmdevice_hmdevice = self._homematic_homematic.devices[self._interface_interface][self._address_address]
110  self._connected_connected = True
111 
112  try:
113  # Initialize datapoints of this object
114  self._init_data_init_data()
115  self._load_data_from_hm_load_data_from_hm()
116 
117  # Link events from pyhomematic
118  self._available_available = not self._hmdevice_hmdevice.UNREACH
119  except Exception as err: # noqa: BLE001
120  self._connected_connected = False
121  _LOGGER.error("Exception while linking %s: %s", self._address_address, str(err))
122 
123  def _hm_event_callback(self, device, caller, attribute, value):
124  """Handle all pyhomematic device events."""
125  has_changed = False
126 
127  # Is data needed for this instance?
128  if device.partition(":")[2] == self._channel_map.get(attribute):
129  self._data[attribute] = value
130  has_changed = True
131 
132  # Availability has changed
133  if self.availableavailableavailable != (not self._hmdevice_hmdevice.UNREACH):
134  self._available_available = not self._hmdevice_hmdevice.UNREACH
135  has_changed = True
136 
137  # If it has changed data point, update Home Assistant
138  if has_changed:
139  self.schedule_update_ha_stateschedule_update_ha_state()
140 
142  """Subscribe all required events to handle job."""
143  for metadata in (
144  self._hmdevice_hmdevice.ACTIONNODE,
145  self._hmdevice_hmdevice.EVENTNODE,
146  self._hmdevice_hmdevice.WRITENODE,
147  self._hmdevice_hmdevice.ATTRIBUTENODE,
148  self._hmdevice_hmdevice.BINARYNODE,
149  self._hmdevice_hmdevice.SENSORNODE,
150  ):
151  for node, channels in metadata.items():
152  # Data is needed for this instance
153  if node in self._data:
154  # chan is current channel
155  if len(channels) == 1:
156  channel = channels[0]
157  else:
158  channel = self._channel_channel
159  # Remember the channel for this attribute to ignore invalid events later
160  self._channel_map[node] = str(channel)
161 
162  _LOGGER.debug("Channel map for %s: %s", self._address_address, str(self._channel_map))
163 
164  # Set callbacks
165  self._hmdevice_hmdevice.setEventCallback(callback=self._hm_event_callback_hm_event_callback, bequeath=True)
166 
168  """Load first value from pyhomematic."""
169  if not self._connected_connected:
170  return False
171 
172  # Read data from pyhomematic
173  for metadata, funct in (
174  (self._hmdevice_hmdevice.ATTRIBUTENODE, self._hmdevice_hmdevice.getAttributeData),
175  (self._hmdevice_hmdevice.WRITENODE, self._hmdevice_hmdevice.getWriteData),
176  (self._hmdevice_hmdevice.SENSORNODE, self._hmdevice_hmdevice.getSensorData),
177  (self._hmdevice_hmdevice.BINARYNODE, self._hmdevice_hmdevice.getBinaryData),
178  ):
179  for node in metadata:
180  if metadata[node] and node in self._data:
181  self._data[node] = funct(name=node, channel=self._channel_channel)
182 
183  return True
184 
185  def _hm_set_state(self, value):
186  """Set data to main datapoint."""
187  if self._state_state in self._data:
188  self._data[self._state_state] = value
189 
190  def _hm_get_state(self):
191  """Get data from main datapoint."""
192  if self._state_state in self._data:
193  return self._data[self._state_state]
194  return None
195 
196  def _init_data(self):
197  """Generate a data dict (self._data) from the HomeMatic metadata."""
198  # Add all attributes to data dictionary
199  for data_note in self._hmdevice_hmdevice.ATTRIBUTENODE:
200  self._data.update({data_note: None})
201 
202  # Initialize device specific data
203  self._init_data_struct_init_data_struct()
204 
205  @abstractmethod
206  def _init_data_struct(self):
207  """Generate a data dictionary from the HomeMatic device metadata."""
208 
209 
210 class HMHub(Entity):
211  """The HomeMatic hub. (CCU2/HomeGear)."""
212 
213  _attr_should_poll = False
214 
215  def __init__(self, hass, homematic, name):
216  """Initialize HomeMatic hub."""
217  self.hasshasshass = hass
218  self.entity_identity_identity_id = f"{DOMAIN}.{name.lower()}"
219  self._homematic_homematic = homematic
220  self._variables_variables = {}
221  self._name_name = name
222  self._state_state = None
223 
224  # Load data
225  track_time_interval(self.hasshasshass, self._update_hub_update_hub, SCAN_INTERVAL_HUB)
226  self.hasshasshass.add_job(self._update_hub_update_hub, None)
227 
228  track_time_interval(self.hasshasshass, self._update_variables_update_variables, SCAN_INTERVAL_VARIABLES)
229  self.hasshasshass.add_job(self._update_variables_update_variables, None)
230 
231  @property
232  def name(self):
233  """Return the name of the device."""
234  return self._name_name
235 
236  @property
237  def state(self):
238  """Return the state of the entity."""
239  return self._state_state
240 
241  @property
243  """Return the state attributes."""
244  return self._variables_variables.copy()
245 
246  @property
247  def icon(self):
248  """Return the icon to use in the frontend, if any."""
249  return "mdi:gradient-vertical"
250 
251  def _update_hub(self, now):
252  """Retrieve latest state."""
253  service_message = self._homematic_homematic.getServiceMessages(self._name_name)
254  state = None if service_message is None else len(service_message)
255 
256  # state have change?
257  if self._state_state != state:
258  self._state_state = state
259  self.schedule_update_ha_stateschedule_update_ha_state()
260 
261  def _update_variables(self, now):
262  """Retrieve all variable data and update hmvariable states."""
263  variables = self._homematic_homematic.getAllSystemVariables(self._name_name)
264  if variables is None:
265  return
266 
267  state_change = False
268  for key, value in variables.items():
269  if key in self._variables_variables and value == self._variables_variables[key]:
270  continue
271 
272  state_change = True
273  self._variables_variables.update({key: value})
274 
275  if state_change:
276  self.schedule_update_ha_stateschedule_update_ha_state()
277 
278  def hm_set_variable(self, name, value):
279  """Set variable value on CCU/Homegear."""
280  if name not in self._variables_variables:
281  _LOGGER.error("Variable %s not found on %s", name, self.namenamename)
282  return
283  old_value = self._variables_variables.get(name)
284  if isinstance(old_value, bool):
285  value = cv.boolean(value)
286  else:
287  value = float(value)
288  self._homematic_homematic.setSystemVariable(self.namenamename, name, value)
289 
290  self._variables_variables.update({name: value})
291  self.schedule_update_ha_stateschedule_update_ha_state()
None __init__(self, dict[str, str] config, EntityDescription|None entity_description=None)
Definition: entity.py:45
def _hm_event_callback(self, device, caller, attribute, value)
Definition: entity.py:123
def __init__(self, hass, homematic, name)
Definition: entity.py:215
None schedule_update_ha_state(self, bool force_refresh=False)
Definition: entity.py:1244
str|UndefinedType|None name(self)
Definition: entity.py:738
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88