Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for HomematicIP Cloud binary sensor."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from homematicip.aio.device import (
8  AsyncAccelerationSensor,
9  AsyncContactInterface,
10  AsyncDevice,
11  AsyncFullFlushContactInterface,
12  AsyncFullFlushContactInterface6,
13  AsyncMotionDetectorIndoor,
14  AsyncMotionDetectorOutdoor,
15  AsyncMotionDetectorPushButton,
16  AsyncPluggableMainsFailureSurveillance,
17  AsyncPresenceDetectorIndoor,
18  AsyncRainSensor,
19  AsyncRotaryHandleSensor,
20  AsyncShutterContact,
21  AsyncShutterContactMagnetic,
22  AsyncSmokeDetector,
23  AsyncTiltVibrationSensor,
24  AsyncWaterSensor,
25  AsyncWeatherSensor,
26  AsyncWeatherSensorPlus,
27  AsyncWeatherSensorPro,
28  AsyncWiredInput32,
29 )
30 from homematicip.aio.group import AsyncSecurityGroup, AsyncSecurityZoneGroup
31 from homematicip.base.enums import SmokeDetectorAlarmType, WindowState
32 
34  BinarySensorDeviceClass,
35  BinarySensorEntity,
36 )
37 from homeassistant.config_entries import ConfigEntry
38 from homeassistant.core import HomeAssistant
39 from homeassistant.helpers.device_registry import DeviceInfo
40 from homeassistant.helpers.entity_platform import AddEntitiesCallback
41 
42 from .const import DOMAIN
43 from .entity import HomematicipGenericEntity
44 from .hap import HomematicipHAP
45 
46 ATTR_ACCELERATION_SENSOR_MODE = "acceleration_sensor_mode"
47 ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position"
48 ATTR_ACCELERATION_SENSOR_SENSITIVITY = "acceleration_sensor_sensitivity"
49 ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE = "acceleration_sensor_trigger_angle"
50 ATTR_INTRUSION_ALARM = "intrusion_alarm"
51 ATTR_MOISTURE_DETECTED = "moisture_detected"
52 ATTR_MOTION_DETECTED = "motion_detected"
53 ATTR_POWER_MAINS_FAILURE = "power_mains_failure"
54 ATTR_PRESENCE_DETECTED = "presence_detected"
55 ATTR_SMOKE_DETECTOR_ALARM = "smoke_detector_alarm"
56 ATTR_TODAY_SUNSHINE_DURATION = "today_sunshine_duration_in_minutes"
57 ATTR_WATER_LEVEL_DETECTED = "water_level_detected"
58 ATTR_WINDOW_STATE = "window_state"
59 
60 GROUP_ATTRIBUTES = {
61  "moistureDetected": ATTR_MOISTURE_DETECTED,
62  "motionDetected": ATTR_MOTION_DETECTED,
63  "powerMainsFailure": ATTR_POWER_MAINS_FAILURE,
64  "presenceDetected": ATTR_PRESENCE_DETECTED,
65  "waterlevelDetected": ATTR_WATER_LEVEL_DETECTED,
66 }
67 
68 SAM_DEVICE_ATTRIBUTES = {
69  "accelerationSensorNeutralPosition": ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION,
70  "accelerationSensorMode": ATTR_ACCELERATION_SENSOR_MODE,
71  "accelerationSensorSensitivity": ATTR_ACCELERATION_SENSOR_SENSITIVITY,
72  "accelerationSensorTriggerAngle": ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE,
73 }
74 
75 
77  hass: HomeAssistant,
78  config_entry: ConfigEntry,
79  async_add_entities: AddEntitiesCallback,
80 ) -> None:
81  """Set up the HomematicIP Cloud binary sensor from a config entry."""
82  hap = hass.data[DOMAIN][config_entry.unique_id]
83  entities: list[HomematicipGenericEntity] = [HomematicipCloudConnectionSensor(hap)]
84  for device in hap.home.devices:
85  if isinstance(device, AsyncAccelerationSensor):
86  entities.append(HomematicipAccelerationSensor(hap, device))
87  if isinstance(device, AsyncTiltVibrationSensor):
88  entities.append(HomematicipTiltVibrationSensor(hap, device))
89  if isinstance(device, AsyncWiredInput32):
90  entities.extend(
91  HomematicipMultiContactInterface(hap, device, channel=channel)
92  for channel in range(1, 33)
93  )
94  elif isinstance(device, AsyncFullFlushContactInterface6):
95  entities.extend(
96  HomematicipMultiContactInterface(hap, device, channel=channel)
97  for channel in range(1, 7)
98  )
99  elif isinstance(
100  device, (AsyncContactInterface, AsyncFullFlushContactInterface)
101  ):
102  entities.append(HomematicipContactInterface(hap, device))
103  if isinstance(
104  device,
105  (AsyncShutterContact, AsyncShutterContactMagnetic),
106  ):
107  entities.append(HomematicipShutterContact(hap, device))
108  if isinstance(device, AsyncRotaryHandleSensor):
109  entities.append(HomematicipShutterContact(hap, device, True))
110  if isinstance(
111  device,
112  (
113  AsyncMotionDetectorIndoor,
114  AsyncMotionDetectorOutdoor,
115  AsyncMotionDetectorPushButton,
116  ),
117  ):
118  entities.append(HomematicipMotionDetector(hap, device))
119  if isinstance(device, AsyncPluggableMainsFailureSurveillance):
120  entities.append(
122  )
123  if isinstance(device, AsyncPresenceDetectorIndoor):
124  entities.append(HomematicipPresenceDetector(hap, device))
125  if isinstance(device, AsyncSmokeDetector):
126  entities.append(HomematicipSmokeDetector(hap, device))
127  if isinstance(device, AsyncWaterSensor):
128  entities.append(HomematicipWaterDetector(hap, device))
129  if isinstance(
130  device, (AsyncRainSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
131  ):
132  entities.append(HomematicipRainSensor(hap, device))
133  if isinstance(
134  device, (AsyncWeatherSensor, AsyncWeatherSensorPlus, AsyncWeatherSensorPro)
135  ):
136  entities.append(HomematicipStormSensor(hap, device))
137  entities.append(HomematicipSunshineSensor(hap, device))
138  if isinstance(device, AsyncDevice) and device.lowBat is not None:
139  entities.append(HomematicipBatterySensor(hap, device))
140 
141  for group in hap.home.groups:
142  if isinstance(group, AsyncSecurityGroup):
143  entities.append(HomematicipSecuritySensorGroup(hap, device=group))
144  elif isinstance(group, AsyncSecurityZoneGroup):
145  entities.append(HomematicipSecurityZoneSensorGroup(hap, device=group))
146 
147  async_add_entities(entities)
148 
149 
151  """Representation of the HomematicIP cloud connection sensor."""
152 
153  def __init__(self, hap: HomematicipHAP) -> None:
154  """Initialize the cloud connection sensor."""
155  super().__init__(hap, hap.home)
156 
157  @property
158  def name(self) -> str:
159  """Return the name cloud connection entity."""
160 
161  name = "Cloud Connection"
162  # Add a prefix to the name if the homematic ip home has a name.
163  return name if not self._home.name else f"{self._home.name} {name}"
164 
165  @property
166  def device_info(self) -> DeviceInfo:
167  """Return device specific attributes."""
168  # Adds a sensor to the existing HAP device
169  return DeviceInfo(
170  identifiers={
171  # Serial numbers of Homematic IP device
172  (DOMAIN, self._home.id)
173  }
174  )
175 
176  @property
177  def icon(self) -> str:
178  """Return the icon of the access point entity."""
179  return (
180  "mdi:access-point-network"
181  if self._home.connected
182  else "mdi:access-point-network-off"
183  )
184 
185  @property
186  def is_on(self) -> bool:
187  """Return true if hap is connected to cloud."""
188  return self._home.connected
189 
190  @property
191  def available(self) -> bool:
192  """Sensor is always available."""
193  return True
194 
195 
197  """Representation of the HomematicIP base action sensor."""
198 
199  _attr_device_class = BinarySensorDeviceClass.MOVING
200 
201  @property
202  def is_on(self) -> bool:
203  """Return true if acceleration is detected."""
204  return self._device_device.accelerationSensorTriggered
205 
206  @property
207  def extra_state_attributes(self) -> dict[str, Any]:
208  """Return the state attributes of the acceleration sensor."""
209  state_attr = super().extra_state_attributes
210 
211  for attr, attr_key in SAM_DEVICE_ATTRIBUTES.items():
212  if attr_value := getattr(self._device_device, attr, None):
213  state_attr[attr_key] = attr_value
214 
215  return state_attr
216 
217 
219  """Representation of the HomematicIP acceleration sensor."""
220 
221 
222 class HomematicipTiltVibrationSensor(HomematicipBaseActionSensor):
223  """Representation of the HomematicIP tilt vibration sensor."""
224 
225 
227  """Representation of the HomematicIP multi room/area contact interface."""
228 
229  _attr_device_class = BinarySensorDeviceClass.OPENING
230 
231  def __init__(
232  self,
233  hap: HomematicipHAP,
234  device,
235  channel=1,
236  is_multi_channel=True,
237  ) -> None:
238  """Initialize the multi contact entity."""
239  super().__init__(
240  hap, device, channel=channel, is_multi_channel=is_multi_channel
241  )
242 
243  @property
244  def is_on(self) -> bool | None:
245  """Return true if the contact interface is on/open."""
246  if self._device_device.functionalChannels[self._channel_channel].windowState is None:
247  return None
248  return (
249  self._device_device.functionalChannels[self._channel_channel].windowState
250  != WindowState.CLOSED
251  )
252 
253 
255  """Representation of the HomematicIP contact interface."""
256 
257  def __init__(self, hap: HomematicipHAP, device) -> None:
258  """Initialize the multi contact entity."""
259  super().__init__(hap, device, is_multi_channel=False)
260 
261 
263  """Representation of the HomematicIP shutter contact."""
264 
265  _attr_device_class = BinarySensorDeviceClass.DOOR
266 
267  def __init__(
268  self, hap: HomematicipHAP, device, has_additional_state: bool = False
269  ) -> None:
270  """Initialize the shutter contact."""
271  super().__init__(hap, device, is_multi_channel=False)
272  self.has_additional_statehas_additional_state = has_additional_state
273 
274  @property
275  def extra_state_attributes(self) -> dict[str, Any]:
276  """Return the state attributes of the Shutter Contact."""
277  state_attr = super().extra_state_attributes
278 
279  if self.has_additional_statehas_additional_state:
280  window_state = getattr(self._device_device, "windowState", None)
281  if window_state and window_state != WindowState.CLOSED:
282  state_attr[ATTR_WINDOW_STATE] = window_state
283 
284  return state_attr
285 
286 
288  """Representation of the HomematicIP motion detector."""
289 
290  _attr_device_class = BinarySensorDeviceClass.MOTION
291 
292  @property
293  def is_on(self) -> bool:
294  """Return true if motion is detected."""
295  return self._device_device.motionDetected
296 
297 
299  """Representation of the HomematicIP presence detector."""
300 
301  _attr_device_class = BinarySensorDeviceClass.PRESENCE
302 
303  @property
304  def is_on(self) -> bool:
305  """Return true if presence is detected."""
306  return self._device_device.presenceDetected
307 
308 
310  """Representation of the HomematicIP smoke detector."""
311 
312  _attr_device_class = BinarySensorDeviceClass.SMOKE
313 
314  @property
315  def is_on(self) -> bool:
316  """Return true if smoke is detected."""
317  if self._device_device.smokeDetectorAlarmType:
318  return (
319  self._device_device.smokeDetectorAlarmType
320  == SmokeDetectorAlarmType.PRIMARY_ALARM
321  )
322  return False
323 
324 
326  """Representation of the HomematicIP water detector."""
327 
328  _attr_device_class = BinarySensorDeviceClass.MOISTURE
329 
330  @property
331  def is_on(self) -> bool:
332  """Return true, if moisture or waterlevel is detected."""
333  return self._device_device.moistureDetected or self._device_device.waterlevelDetected
334 
335 
337  """Representation of the HomematicIP storm sensor."""
338 
339  def __init__(self, hap: HomematicipHAP, device) -> None:
340  """Initialize storm sensor."""
341  super().__init__(hap, device, "Storm")
342 
343  @property
344  def icon(self) -> str:
345  """Return the icon."""
346  return "mdi:weather-windy" if self.is_onis_on else "mdi:pinwheel-outline"
347 
348  @property
349  def is_on(self) -> bool:
350  """Return true, if storm is detected."""
351  return self._device_device.storm
352 
353 
355  """Representation of the HomematicIP rain sensor."""
356 
357  _attr_device_class = BinarySensorDeviceClass.MOISTURE
358 
359  def __init__(self, hap: HomematicipHAP, device) -> None:
360  """Initialize rain sensor."""
361  super().__init__(hap, device, "Raining")
362 
363  @property
364  def is_on(self) -> bool:
365  """Return true, if it is raining."""
366  return self._device_device.raining
367 
368 
370  """Representation of the HomematicIP sunshine sensor."""
371 
372  _attr_device_class = BinarySensorDeviceClass.LIGHT
373 
374  def __init__(self, hap: HomematicipHAP, device) -> None:
375  """Initialize sunshine sensor."""
376  super().__init__(hap, device, post="Sunshine")
377 
378  @property
379  def is_on(self) -> bool:
380  """Return true if sun is shining."""
381  return self._device_device.sunshine
382 
383  @property
384  def extra_state_attributes(self) -> dict[str, Any]:
385  """Return the state attributes of the illuminance sensor."""
386  state_attr = super().extra_state_attributes
387 
388  today_sunshine_duration = getattr(self._device_device, "todaySunshineDuration", None)
389  if today_sunshine_duration:
390  state_attr[ATTR_TODAY_SUNSHINE_DURATION] = today_sunshine_duration
391 
392  return state_attr
393 
394 
396  """Representation of the HomematicIP low battery sensor."""
397 
398  _attr_device_class = BinarySensorDeviceClass.BATTERY
399 
400  def __init__(self, hap: HomematicipHAP, device) -> None:
401  """Initialize battery sensor."""
402  super().__init__(hap, device, post="Battery")
403 
404  @property
405  def is_on(self) -> bool:
406  """Return true if battery is low."""
407  return self._device_device.lowBat
408 
409 
411  HomematicipGenericEntity, BinarySensorEntity
412 ):
413  """Representation of the HomematicIP pluggable mains failure surveillance sensor."""
414 
415  _attr_device_class = BinarySensorDeviceClass.POWER
416 
417  def __init__(self, hap: HomematicipHAP, device) -> None:
418  """Initialize pluggable mains failure surveillance sensor."""
419  super().__init__(hap, device)
420 
421  @property
422  def is_on(self) -> bool:
423  """Return true if power mains fails."""
424  return not self._device_device.powerMainsFailure
425 
426 
428  """Representation of the HomematicIP security zone sensor group."""
429 
430  _attr_device_class = BinarySensorDeviceClass.SAFETY
431 
432  def __init__(self, hap: HomematicipHAP, device, post: str = "SecurityZone") -> None:
433  """Initialize security zone group."""
434  device.modelType = f"HmIP-{post}"
435  super().__init__(hap, device, post=post)
436 
437  @property
438  def available(self) -> bool:
439  """Security-Group available."""
440  # A security-group must be available, and should not be affected by
441  # the individual availability of group members.
442  return True
443 
444  @property
445  def extra_state_attributes(self) -> dict[str, Any]:
446  """Return the state attributes of the security zone group."""
447  state_attr = super().extra_state_attributes
448 
449  for attr, attr_key in GROUP_ATTRIBUTES.items():
450  if attr_value := getattr(self._device_device, attr, None):
451  state_attr[attr_key] = attr_value
452 
453  window_state = getattr(self._device_device, "windowState", None)
454  if window_state and window_state != WindowState.CLOSED:
455  state_attr[ATTR_WINDOW_STATE] = str(window_state)
456 
457  return state_attr
458 
459  @property
460  def is_on(self) -> bool:
461  """Return true if security issue detected."""
462  if (
463  self._device_device.motionDetected
464  or self._device_device.presenceDetected
465  or self._device_device.unreach
466  or self._device_device.sabotage
467  ):
468  return True
469 
470  if (
471  self._device_device.windowState is not None
472  and self._device_device.windowState != WindowState.CLOSED
473  ):
474  return True
475  return False
476 
477 
479  HomematicipSecurityZoneSensorGroup, BinarySensorEntity
480 ):
481  """Representation of the HomematicIP security group."""
482 
483  def __init__(self, hap: HomematicipHAP, device) -> None:
484  """Initialize security group."""
485  super().__init__(hap, device, post="Sensors")
486 
487  @property
488  def extra_state_attributes(self) -> dict[str, Any]:
489  """Return the state attributes of the security group."""
490  state_attr = super().extra_state_attributes
491 
492  smoke_detector_at = getattr(self._device_device, "smokeDetectorAlarmType", None)
493  if smoke_detector_at:
494  if smoke_detector_at == SmokeDetectorAlarmType.PRIMARY_ALARM:
495  state_attr[ATTR_SMOKE_DETECTOR_ALARM] = str(smoke_detector_at)
496  if smoke_detector_at == SmokeDetectorAlarmType.INTRUSION_ALARM:
497  state_attr[ATTR_INTRUSION_ALARM] = str(smoke_detector_at)
498  return state_attr
499 
500  @property
501  def is_on(self) -> bool:
502  """Return true if safety issue detected."""
503  if super().is_on:
504  # parent is on
505  return True
506 
507  if (
508  self._device_device.powerMainsFailure
509  or self._device_device.moistureDetected
510  or self._device_device.waterlevelDetected
511  or self._device_device.lowBat
512  or self._device_device.dutyCycle
513  ):
514  return True
515 
516  if (
517  self._device_device.smokeDetectorAlarmType is not None
518  and self._device_device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF
519  ):
520  return True
521 
522  return False
None __init__(self, HomematicipHAP hap, device, channel=1, is_multi_channel=True)
None __init__(self, HomematicipHAP hap, device, str post="SecurityZone")
None __init__(self, HomematicipHAP hap, device, bool has_additional_state=False)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)