Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Representation of Z-Wave binary sensors."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 
7 from zwave_js_server.client import Client as ZwaveClient
8 from zwave_js_server.const import CommandClass
9 from zwave_js_server.const.command_class.lock import DOOR_STATUS_PROPERTY
10 from zwave_js_server.const.command_class.notification import (
11  CC_SPECIFIC_NOTIFICATION_TYPE,
12 )
13 from zwave_js_server.model.driver import Driver
14 
16  DOMAIN as BINARY_SENSOR_DOMAIN,
17  BinarySensorDeviceClass,
18  BinarySensorEntity,
19  BinarySensorEntityDescription,
20 )
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import EntityCategory
23 from homeassistant.core import HomeAssistant, callback
24 from homeassistant.helpers.dispatcher import async_dispatcher_connect
25 from homeassistant.helpers.entity_platform import AddEntitiesCallback
26 
27 from .const import DATA_CLIENT, DOMAIN
28 from .discovery import ZwaveDiscoveryInfo
29 from .entity import ZWaveBaseEntity
30 
31 PARALLEL_UPDATES = 0
32 
33 
34 NOTIFICATION_SMOKE_ALARM = "1"
35 NOTIFICATION_CARBON_MONOOXIDE = "2"
36 NOTIFICATION_CARBON_DIOXIDE = "3"
37 NOTIFICATION_HEAT = "4"
38 NOTIFICATION_WATER = "5"
39 NOTIFICATION_ACCESS_CONTROL = "6"
40 NOTIFICATION_HOME_SECURITY = "7"
41 NOTIFICATION_POWER_MANAGEMENT = "8"
42 NOTIFICATION_SYSTEM = "9"
43 NOTIFICATION_EMERGENCY = "10"
44 NOTIFICATION_CLOCK = "11"
45 NOTIFICATION_APPLIANCE = "12"
46 NOTIFICATION_HOME_HEALTH = "13"
47 NOTIFICATION_SIREN = "14"
48 NOTIFICATION_WATER_VALVE = "15"
49 NOTIFICATION_WEATHER = "16"
50 NOTIFICATION_IRRIGATION = "17"
51 NOTIFICATION_GAS = "18"
52 
53 
54 @dataclass(frozen=True)
56  """Represent a Z-Wave JS binary sensor entity description."""
57 
58  off_state: str = "0"
59  states: tuple[str, ...] | None = None
60 
61 
62 @dataclass(frozen=True, kw_only=True)
64  """Represent the entity description for property name sensors."""
65 
66  on_states: tuple[str, ...]
67 
68 
69 # Mappings for Notification sensors
70 # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/notifications.json
71 NOTIFICATION_SENSOR_MAPPINGS: tuple[NotificationZWaveJSEntityDescription, ...] = (
73  # NotificationType 1: Smoke Alarm - State Id's 1 and 2 - Smoke detected
74  key=NOTIFICATION_SMOKE_ALARM,
75  states=("1", "2"),
76  device_class=BinarySensorDeviceClass.SMOKE,
77  ),
79  # NotificationType 1: Smoke Alarm - All other State Id's
80  key=NOTIFICATION_SMOKE_ALARM,
81  device_class=BinarySensorDeviceClass.PROBLEM,
82  ),
84  # NotificationType 2: Carbon Monoxide - State Id's 1 and 2
85  key=NOTIFICATION_CARBON_MONOOXIDE,
86  states=("1", "2"),
87  device_class=BinarySensorDeviceClass.CO,
88  ),
90  # NotificationType 2: Carbon Monoxide - All other State Id's
91  key=NOTIFICATION_CARBON_MONOOXIDE,
92  device_class=BinarySensorDeviceClass.PROBLEM,
93  ),
95  # NotificationType 3: Carbon Dioxide - State Id's 1 and 2
96  key=NOTIFICATION_CARBON_DIOXIDE,
97  states=("1", "2"),
98  device_class=BinarySensorDeviceClass.GAS,
99  ),
101  # NotificationType 3: Carbon Dioxide - All other State Id's
102  key=NOTIFICATION_CARBON_DIOXIDE,
103  device_class=BinarySensorDeviceClass.PROBLEM,
104  ),
106  # NotificationType 4: Heat - State Id's 1, 2, 5, 6 (heat/underheat)
107  key=NOTIFICATION_HEAT,
108  states=("1", "2", "5", "6"),
109  device_class=BinarySensorDeviceClass.HEAT,
110  ),
112  # NotificationType 4: Heat - All other State Id's
113  key=NOTIFICATION_HEAT,
114  device_class=BinarySensorDeviceClass.PROBLEM,
115  ),
117  # NotificationType 5: Water - State Id's 1, 2, 3, 4
118  key=NOTIFICATION_WATER,
119  states=("1", "2", "3", "4"),
120  device_class=BinarySensorDeviceClass.MOISTURE,
121  ),
123  # NotificationType 5: Water - All other State Id's
124  key=NOTIFICATION_WATER,
125  device_class=BinarySensorDeviceClass.PROBLEM,
126  ),
128  # NotificationType 6: Access Control - State Id's 1, 2, 3, 4 (Lock)
129  key=NOTIFICATION_ACCESS_CONTROL,
130  states=("1", "2", "3", "4"),
131  device_class=BinarySensorDeviceClass.LOCK,
132  ),
134  # NotificationType 6: Access Control - State Id's 11 (Lock jammed)
135  key=NOTIFICATION_ACCESS_CONTROL,
136  states=("11",),
137  device_class=BinarySensorDeviceClass.PROBLEM,
138  entity_category=EntityCategory.DIAGNOSTIC,
139  ),
141  # NotificationType 6: Access Control - State Id 22 (door/window open)
142  key=NOTIFICATION_ACCESS_CONTROL,
143  off_state="23",
144  states=("22", "23"),
145  device_class=BinarySensorDeviceClass.DOOR,
146  ),
148  # NotificationType 7: Home Security - State Id's 1, 2 (intrusion)
149  key=NOTIFICATION_HOME_SECURITY,
150  states=("1", "2"),
151  device_class=BinarySensorDeviceClass.SAFETY,
152  ),
154  # NotificationType 7: Home Security - State Id's 3, 4, 9 (tampering)
155  key=NOTIFICATION_HOME_SECURITY,
156  states=("3", "4", "9"),
157  device_class=BinarySensorDeviceClass.TAMPER,
158  entity_category=EntityCategory.DIAGNOSTIC,
159  ),
161  # NotificationType 7: Home Security - State Id's 5, 6 (glass breakage)
162  key=NOTIFICATION_HOME_SECURITY,
163  states=("5", "6"),
164  device_class=BinarySensorDeviceClass.SAFETY,
165  ),
167  # NotificationType 7: Home Security - State Id's 7, 8 (motion)
168  key=NOTIFICATION_HOME_SECURITY,
169  states=("7", "8"),
170  device_class=BinarySensorDeviceClass.MOTION,
171  ),
173  # NotificationType 8: Power Management -
174  # State Id's 2, 3 (Mains status)
175  key=NOTIFICATION_POWER_MANAGEMENT,
176  off_state="2",
177  states=("2", "3"),
178  device_class=BinarySensorDeviceClass.PLUG,
179  entity_category=EntityCategory.DIAGNOSTIC,
180  ),
182  # NotificationType 8: Power Management -
183  # State Id's 6, 7, 8, 9 (power status)
184  key=NOTIFICATION_POWER_MANAGEMENT,
185  states=("6", "7", "8", "9"),
186  device_class=BinarySensorDeviceClass.SAFETY,
187  entity_category=EntityCategory.DIAGNOSTIC,
188  ),
190  # NotificationType 8: Power Management -
191  # State Id's 10, 11, 17 (Battery maintenance status)
192  key=NOTIFICATION_POWER_MANAGEMENT,
193  states=("10", "11", "17"),
194  device_class=BinarySensorDeviceClass.BATTERY,
195  entity_category=EntityCategory.DIAGNOSTIC,
196  ),
198  # NotificationType 9: System - State Id's 1, 2, 3, 4, 6, 7
199  key=NOTIFICATION_SYSTEM,
200  states=("1", "2", "3", "4", "6", "7"),
201  device_class=BinarySensorDeviceClass.PROBLEM,
202  entity_category=EntityCategory.DIAGNOSTIC,
203  ),
205  # NotificationType 10: Emergency - State Id's 1, 2, 3
206  key=NOTIFICATION_EMERGENCY,
207  states=("1", "2", "3"),
208  device_class=BinarySensorDeviceClass.PROBLEM,
209  ),
211  # NotificationType 14: Siren
212  key=NOTIFICATION_SIREN,
213  states=("1",),
214  device_class=BinarySensorDeviceClass.SOUND,
215  ),
217  # NotificationType 18: Gas
218  key=NOTIFICATION_GAS,
219  states=("1", "2", "3", "4"),
220  device_class=BinarySensorDeviceClass.GAS,
221  ),
223  # NotificationType 18: Gas
224  key=NOTIFICATION_GAS,
225  states=("6",),
226  device_class=BinarySensorDeviceClass.PROBLEM,
227  ),
228 )
229 
230 
231 # Mappings for property sensors
232 PROPERTY_SENSOR_MAPPINGS: dict[str, PropertyZWaveJSEntityDescription] = {
233  DOOR_STATUS_PROPERTY: PropertyZWaveJSEntityDescription(
234  key=DOOR_STATUS_PROPERTY,
235  on_states=("open",),
236  device_class=BinarySensorDeviceClass.DOOR,
237  ),
238 }
239 
240 
241 # Mappings for boolean sensors
242 BOOLEAN_SENSOR_MAPPINGS: dict[int, BinarySensorEntityDescription] = {
243  CommandClass.BATTERY: BinarySensorEntityDescription(
244  key=str(CommandClass.BATTERY),
245  device_class=BinarySensorDeviceClass.BATTERY,
246  entity_category=EntityCategory.DIAGNOSTIC,
247  ),
248 }
249 
250 
251 @callback
253  info: ZwaveDiscoveryInfo,
254 ) -> bool | NotificationZWaveJSEntityDescription:
255  """Return if the notification CC Value is valid as binary sensor."""
256  if not info.primary_value.metadata.states:
257  return False
258  return len(info.primary_value.metadata.states) > 1
259 
260 
262  hass: HomeAssistant,
263  config_entry: ConfigEntry,
264  async_add_entities: AddEntitiesCallback,
265 ) -> None:
266  """Set up Z-Wave binary sensor from config entry."""
267  client: ZwaveClient = config_entry.runtime_data[DATA_CLIENT]
268 
269  @callback
270  def async_add_binary_sensor(info: ZwaveDiscoveryInfo) -> None:
271  """Add Z-Wave Binary Sensor."""
272  driver = client.driver
273  assert driver is not None # Driver is ready before platforms are loaded.
274  entities: list[BinarySensorEntity] = []
275 
276  if info.platform_hint == "notification":
277  # ensure the notification CC Value is valid as binary sensor
279  return
280  # Get all sensors from Notification CC states
281  for state_key in info.primary_value.metadata.states:
282  # ignore idle key (0)
283  if state_key == "0":
284  continue
285  # get (optional) description for this state
286  notification_description: (
287  NotificationZWaveJSEntityDescription | None
288  ) = None
289  for description in NOTIFICATION_SENSOR_MAPPINGS:
290  if (
291  int(description.key)
292  == info.primary_value.metadata.cc_specific[
293  CC_SPECIFIC_NOTIFICATION_TYPE
294  ]
295  ) and (not description.states or state_key in description.states):
296  notification_description = description
297  break
298 
299  if (
300  notification_description
301  and notification_description.off_state == state_key
302  ):
303  continue
304  entities.append(
306  config_entry, driver, info, state_key, notification_description
307  )
308  )
309  elif (
310  info.platform_hint == "property"
311  and info.primary_value.property_name
312  and (
313  property_description := PROPERTY_SENSOR_MAPPINGS.get(
314  info.primary_value.property_name
315  )
316  )
317  ):
318  entities.append(
320  config_entry, driver, info, property_description
321  )
322  )
323  elif info.platform_hint == "config_parameter":
324  entities.append(
325  ZWaveConfigParameterBinarySensor(config_entry, driver, info)
326  )
327  else:
328  # boolean sensor
329  entities.append(ZWaveBooleanBinarySensor(config_entry, driver, info))
330 
331  async_add_entities(entities)
332 
333  config_entry.async_on_unload(
335  hass,
336  f"{DOMAIN}_{config_entry.entry_id}_add_{BINARY_SENSOR_DOMAIN}",
337  async_add_binary_sensor,
338  )
339  )
340 
341 
343  """Representation of a Z-Wave binary_sensor."""
344 
345  def __init__(
346  self,
347  config_entry: ConfigEntry,
348  driver: Driver,
349  info: ZwaveDiscoveryInfo,
350  ) -> None:
351  """Initialize a ZWaveBooleanBinarySensor entity."""
352  super().__init__(config_entry, driver, info)
353 
354  # Entity class attributes
355  self._attr_name_attr_name_attr_name = self.generate_namegenerate_name(include_value_name=True)
356  if description := BOOLEAN_SENSOR_MAPPINGS.get(
357  self.infoinfo.primary_value.command_class
358  ):
359  self.entity_descriptionentity_description = description
360 
361  @property
362  def is_on(self) -> bool | None:
363  """Return if the sensor is on or off."""
364  if self.infoinfo.primary_value.value is None:
365  return None
366  return bool(self.infoinfo.primary_value.value)
367 
368 
370  """Representation of a Z-Wave binary_sensor from Notification CommandClass."""
371 
372  def __init__(
373  self,
374  config_entry: ConfigEntry,
375  driver: Driver,
376  info: ZwaveDiscoveryInfo,
377  state_key: str,
378  description: NotificationZWaveJSEntityDescription | None = None,
379  ) -> None:
380  """Initialize a ZWaveNotificationBinarySensor entity."""
381  super().__init__(config_entry, driver, info)
382  self.state_keystate_key = state_key
383  if description:
384  self.entity_descriptionentity_description = description
385 
386  # Entity class attributes
387  self._attr_name_attr_name_attr_name = self.generate_namegenerate_name(
388  alternate_value_name=self.infoinfo.primary_value.metadata.states[self.state_keystate_key]
389  )
390  self._attr_unique_id_attr_unique_id_attr_unique_id = f"{self._attr_unique_id}.{self.state_key}"
391 
392  @property
393  def is_on(self) -> bool | None:
394  """Return if the sensor is on or off."""
395  if self.infoinfo.primary_value.value is None:
396  return None
397  return int(self.infoinfo.primary_value.value) == int(self.state_keystate_key)
398 
399 
401  """Representation of a Z-Wave binary_sensor from a property."""
402 
403  entity_description: PropertyZWaveJSEntityDescription
404 
405  def __init__(
406  self,
407  config_entry: ConfigEntry,
408  driver: Driver,
409  info: ZwaveDiscoveryInfo,
410  description: PropertyZWaveJSEntityDescription,
411  ) -> None:
412  """Initialize a ZWavePropertyBinarySensor entity."""
413  super().__init__(config_entry, driver, info)
414  self.entity_descriptionentity_description = description
415  self._attr_name_attr_name_attr_name = self.generate_namegenerate_name(include_value_name=True)
416 
417  @property
418  def is_on(self) -> bool | None:
419  """Return if the sensor is on or off."""
420  if self.infoinfo.primary_value.value is None:
421  return None
422  return self.infoinfo.primary_value.value in self.entity_descriptionentity_description.on_states
423 
424 
426  """Representation of a Z-Wave config parameter binary sensor."""
427 
428  _attr_entity_category = EntityCategory.DIAGNOSTIC
429 
430  def __init__(
431  self, config_entry: ConfigEntry, driver: Driver, info: ZwaveDiscoveryInfo
432  ) -> None:
433  """Initialize a ZWaveConfigParameterBinarySensor entity."""
434  super().__init__(config_entry, driver, info)
435 
436  property_key_name = self.infoinfo.primary_value.property_key_name
437  # Entity class attributes
438  self._attr_name_attr_name_attr_name_attr_name = self.generate_namegenerate_name(
439  alternate_value_name=self.infoinfo.primary_value.property_name,
440  additional_info=[property_key_name] if property_key_name else None,
441  )
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info)
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, str state_key, NotificationZWaveJSEntityDescription|None description=None)
None __init__(self, ConfigEntry config_entry, Driver driver, ZwaveDiscoveryInfo info, PropertyZWaveJSEntityDescription description)
str generate_name(self, bool include_value_name=False, str|None alternate_value_name=None, Sequence[str|None]|None additional_info=None, str|None name_prefix=None)
Definition: entity.py:163
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
bool|NotificationZWaveJSEntityDescription is_valid_notification_binary_sensor(ZwaveDiscoveryInfo info)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103