Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for RFXtrx binary sensors."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 import RFXtrx as rfxtrxmod
9 
11  BinarySensorDeviceClass,
12  BinarySensorEntity,
13  BinarySensorEntityDescription,
14 )
15 from homeassistant.config_entries import ConfigEntry
16 from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON, STATE_ON
17 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
18 from homeassistant.helpers import event as evt
19 from homeassistant.helpers.entity import Entity
20 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21 
22 from . import DeviceTuple, async_setup_platform_entry, get_pt2262_cmd
23 from .const import (
24  COMMAND_OFF_LIST,
25  COMMAND_ON_LIST,
26  CONF_DATA_BITS,
27  CONF_OFF_DELAY,
28  DEVICE_PACKET_TYPE_LIGHTING4,
29 )
30 from .entity import RfxtrxEntity
31 
32 _LOGGER = logging.getLogger(__name__)
33 
34 
35 SENSOR_STATUS_ON = [
36  "Panic",
37  "Motion",
38  "Motion Tamper",
39  "Light Detected",
40  "Alarm",
41  "Alarm Tamper",
42 ]
43 
44 SENSOR_STATUS_OFF = [
45  "End Panic",
46  "No Motion",
47  "No Motion Tamper",
48  "Dark Detected",
49  "Normal",
50  "Normal Tamper",
51 ]
52 
53 SENSOR_TYPES = (
55  key="X10 Security Motion Detector",
56  device_class=BinarySensorDeviceClass.MOTION,
57  ),
59  key="KD101 Smoke Detector",
60  device_class=BinarySensorDeviceClass.SMOKE,
61  ),
63  key="Visonic Powercode Motion Detector",
64  device_class=BinarySensorDeviceClass.MOTION,
65  ),
67  key="Alecto SA30 Smoke Detector",
68  device_class=BinarySensorDeviceClass.SMOKE,
69  ),
71  key="RM174RF Smoke Detector",
72  device_class=BinarySensorDeviceClass.SMOKE,
73  ),
74 )
75 
76 SENSOR_TYPES_DICT = {desc.key: desc for desc in SENSOR_TYPES}
77 
78 
79 def supported(event: rfxtrxmod.RFXtrxEvent) -> bool:
80  """Return whether an event supports binary_sensor."""
81  if isinstance(event, rfxtrxmod.ControlEvent):
82  return True
83  if isinstance(event, rfxtrxmod.SensorEvent):
84  return event.values.get("Sensor Status") in [
85  *SENSOR_STATUS_ON,
86  *SENSOR_STATUS_OFF,
87  ]
88  return False
89 
90 
92  hass: HomeAssistant,
93  config_entry: ConfigEntry,
94  async_add_entities: AddEntitiesCallback,
95 ) -> None:
96  """Set up config entry."""
97 
98  def get_sensor_description(type_string: str) -> BinarySensorEntityDescription:
99  if (description := SENSOR_TYPES_DICT.get(type_string)) is None:
100  return BinarySensorEntityDescription(key=type_string)
101  return description
102 
103  def _constructor(
104  event: rfxtrxmod.RFXtrxEvent,
105  auto: rfxtrxmod.RFXtrxEvent | None,
106  device_id: DeviceTuple,
107  entity_info: dict[str, Any],
108  ) -> list[Entity]:
109  return [
111  event.device,
112  device_id,
113  get_sensor_description(event.device.type_string),
114  entity_info.get(CONF_OFF_DELAY),
115  entity_info.get(CONF_DATA_BITS),
116  entity_info.get(CONF_COMMAND_ON),
117  entity_info.get(CONF_COMMAND_OFF),
118  event=event if auto else None,
119  )
120  ]
121 
123  hass, config_entry, async_add_entities, supported, _constructor
124  )
125 
126 
128  """A representation of a RFXtrx binary sensor.
129 
130  Since all repeated events have meaning, these types of sensors
131  need to have force update enabled.
132  """
133 
134  _attr_force_update = True
135  _attr_name = None
136 
137  def __init__(
138  self,
139  device: rfxtrxmod.RFXtrxDevice,
140  device_id: DeviceTuple,
141  entity_description: BinarySensorEntityDescription,
142  off_delay: float | None = None,
143  data_bits: int | None = None,
144  cmd_on: int | None = None,
145  cmd_off: int | None = None,
146  event: rfxtrxmod.RFXtrxEvent | None = None,
147  ) -> None:
148  """Initialize the RFXtrx sensor."""
149  super().__init__(device, device_id, event=event)
150  self.entity_descriptionentity_description = entity_description
151  self._data_bits_data_bits = data_bits
152  self._off_delay_off_delay = off_delay
153  self._delay_listener_delay_listener: CALLBACK_TYPE | None = None
154  self._cmd_on_cmd_on = cmd_on
155  self._cmd_off_cmd_off = cmd_off
156 
157  async def async_added_to_hass(self) -> None:
158  """Restore device state."""
159  await super().async_added_to_hass()
160 
161  if self._event_event is None:
162  old_state = await self.async_get_last_stateasync_get_last_state()
163  if old_state is not None:
164  self._attr_is_on_attr_is_on = old_state.state == STATE_ON
165 
166  if self.is_on and self._off_delay_off_delay is not None:
167  self._attr_is_on_attr_is_on = False
168 
169  def _apply_event_lighting4(self, event: rfxtrxmod.RFXtrxEvent) -> None:
170  """Apply event for a lighting 4 device."""
171  if self._data_bits_data_bits is not None:
172  cmdstr = get_pt2262_cmd(event.device.id_string, self._data_bits_data_bits)
173  assert cmdstr
174  cmd = int(cmdstr, 16)
175  if cmd == self._cmd_on_cmd_on:
176  self._attr_is_on_attr_is_on = True
177  elif cmd == self._cmd_off_cmd_off:
178  self._attr_is_on_attr_is_on = False
179  else:
180  self._attr_is_on_attr_is_on = True
181 
182  def _apply_event_standard(self, event: rfxtrxmod.RFXtrxEvent) -> None:
183  assert isinstance(event, (rfxtrxmod.SensorEvent, rfxtrxmod.ControlEvent))
184  if event.values.get("Command") in COMMAND_ON_LIST:
185  self._attr_is_on_attr_is_on = True
186  elif event.values.get("Command") in COMMAND_OFF_LIST:
187  self._attr_is_on_attr_is_on = False
188  elif event.values.get("Sensor Status") in SENSOR_STATUS_ON:
189  self._attr_is_on_attr_is_on = True
190  elif event.values.get("Sensor Status") in SENSOR_STATUS_OFF:
191  self._attr_is_on_attr_is_on = False
192 
193  def _apply_event(self, event: rfxtrxmod.RFXtrxEvent) -> None:
194  """Apply command from rfxtrx."""
195  super()._apply_event(event)
196  if event.device.packettype == DEVICE_PACKET_TYPE_LIGHTING4:
197  self._apply_event_lighting4_apply_event_lighting4(event)
198  else:
199  self._apply_event_standard_apply_event_standard(event)
200 
201  @callback
203  self, event: rfxtrxmod.RFXtrxEvent, device_id: DeviceTuple
204  ) -> None:
205  """Check if event applies to me and update."""
206  if not self._event_applies_event_applies(event, device_id):
207  return
208 
209  _LOGGER.debug(
210  "Binary sensor update (Device ID: %s Class: %s Sub: %s)",
211  event.device.id_string,
212  event.device.__class__.__name__,
213  event.device.subtype,
214  )
215 
216  self._apply_event_apply_event_apply_event(event)
217 
218  self.async_write_ha_stateasync_write_ha_state()
219 
220  if self._delay_listener_delay_listener:
221  self._delay_listener_delay_listener()
222  self._delay_listener_delay_listener = None
223 
224  if self.is_on and self._off_delay_off_delay is not None:
225 
226  @callback
227  def off_delay_listener(now: Any) -> None:
228  """Switch device off after a delay."""
229  self._delay_listener_delay_listener = None
230  self._attr_is_on_attr_is_on = False
231  self.async_write_ha_stateasync_write_ha_state()
232 
233  self._delay_listener_delay_listener = evt.async_call_later(
234  self.hasshass, self._off_delay_off_delay, off_delay_listener
235  )
None _apply_event_standard(self, rfxtrxmod.RFXtrxEvent event)
None _apply_event_lighting4(self, rfxtrxmod.RFXtrxEvent event)
None _handle_event(self, rfxtrxmod.RFXtrxEvent event, DeviceTuple device_id)
None __init__(self, rfxtrxmod.RFXtrxDevice device, DeviceTuple device_id, BinarySensorEntityDescription entity_description, float|None off_delay=None, int|None data_bits=None, int|None cmd_on=None, int|None cmd_off=None, rfxtrxmod.RFXtrxEvent|None event=None)
None _apply_event(self, rfxtrxmod.RFXtrxEvent event)
Definition: entity.py:91
bool _event_applies(self, rfxtrxmod.RFXtrxEvent event, DeviceTuple device_id)
Definition: entity.py:77
bool supported(rfxtrxmod.RFXtrxEvent event)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
str|None get_pt2262_cmd(str device_id, int data_bits)
Definition: __init__.py:377
None async_setup_platform_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities, Callable[[rfxtrxmod.RFXtrxEvent], bool] supported, Callable[[rfxtrxmod.RFXtrxEvent, rfxtrxmod.RFXtrxEvent|None, DeviceTuple, dict[str, Any],], list[Entity],] constructor)
Definition: __init__.py:308