Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Support for RESTful binary sensors."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import ssl
7 from xml.parsers.expat import ExpatError
8 
9 import voluptuous as vol
10 
12  DOMAIN as BINARY_SENSOR_DOMAIN,
13  PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
14  BinarySensorEntity,
15 )
16 from homeassistant.const import (
17  CONF_DEVICE_CLASS,
18  CONF_FORCE_UPDATE,
19  CONF_ICON,
20  CONF_NAME,
21  CONF_RESOURCE,
22  CONF_RESOURCE_TEMPLATE,
23  CONF_UNIQUE_ID,
24  CONF_VALUE_TEMPLATE,
25 )
26 from homeassistant.core import HomeAssistant
27 from homeassistant.exceptions import PlatformNotReady
29 from homeassistant.helpers.entity_platform import AddEntitiesCallback
30 from homeassistant.helpers.template import Template
32  CONF_AVAILABILITY,
33  CONF_PICTURE,
34  ManualTriggerEntity,
35 )
36 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
37 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
38 
39 from . import async_get_config_and_coordinator, create_rest_data_from_config
40 from .const import DEFAULT_BINARY_SENSOR_NAME
41 from .data import RestData
42 from .entity import RestEntity
43 from .schema import BINARY_SENSOR_SCHEMA, RESOURCE_SCHEMA
44 
45 _LOGGER = logging.getLogger(__name__)
46 
47 PLATFORM_SCHEMA = vol.All(
48  BINARY_SENSOR_PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **BINARY_SENSOR_SCHEMA}),
49  cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE),
50 )
51 
52 TRIGGER_ENTITY_OPTIONS = (
53  CONF_AVAILABILITY,
54  CONF_DEVICE_CLASS,
55  CONF_ICON,
56  CONF_PICTURE,
57  CONF_UNIQUE_ID,
58 )
59 
60 
62  hass: HomeAssistant,
63  config: ConfigType,
64  async_add_entities: AddEntitiesCallback,
65  discovery_info: DiscoveryInfoType | None = None,
66 ) -> None:
67  """Set up the REST binary sensor."""
68  # Must update the sensor now (including fetching the rest resource) to
69  # ensure it's updating its state.
70  if discovery_info is not None:
71  conf, coordinator, rest = await async_get_config_and_coordinator(
72  hass, BINARY_SENSOR_DOMAIN, discovery_info
73  )
74  else:
75  conf = config
76  coordinator = None
77  rest = create_rest_data_from_config(hass, conf)
78  await rest.async_update(log_errors=False)
79 
80  if rest.data is None:
81  if rest.last_exception:
82  if isinstance(rest.last_exception, ssl.SSLError):
83  _LOGGER.error(
84  "Error connecting %s failed with %s",
85  rest.url,
86  rest.last_exception,
87  )
88  return
89  raise PlatformNotReady from rest.last_exception
90  raise PlatformNotReady
91 
92  name = conf.get(CONF_NAME) or Template(DEFAULT_BINARY_SENSOR_NAME, hass)
93 
94  trigger_entity_config = {CONF_NAME: name}
95 
96  for key in TRIGGER_ENTITY_OPTIONS:
97  if key not in conf:
98  continue
99  trigger_entity_config[key] = conf[key]
100 
102  [
104  hass,
105  coordinator,
106  rest,
107  conf,
108  trigger_entity_config,
109  )
110  ],
111  )
112 
113 
115  """Representation of a REST binary sensor."""
116 
117  def __init__(
118  self,
119  hass: HomeAssistant,
120  coordinator: DataUpdateCoordinator[None] | None,
121  rest: RestData,
122  config: ConfigType,
123  trigger_entity_config: ConfigType,
124  ) -> None:
125  """Initialize a REST binary sensor."""
126  ManualTriggerEntity.__init__(self, hass, trigger_entity_config)
127  RestEntity.__init__(
128  self,
129  coordinator,
130  rest,
131  config.get(CONF_RESOURCE_TEMPLATE),
132  config[CONF_FORCE_UPDATE],
133  )
134  self._previous_data_previous_data = None
135  self._value_template: Template | None = config.get(CONF_VALUE_TEMPLATE)
136 
137  @property
138  def available(self) -> bool:
139  """Return if entity is available."""
140  available1 = RestEntity.available.fget(self) # type: ignore[attr-defined]
141  available2 = ManualTriggerEntity.available.fget(self) # type: ignore[attr-defined]
142  return bool(available1 and available2)
143 
144  def _update_from_rest_data(self) -> None:
145  """Update state from the rest data."""
146  if self.restrest.data is None:
147  self._attr_is_on_attr_is_on = False
148  return
149 
150  try:
151  response = self.restrest.data_without_xml()
152  except ExpatError as err:
153  self._attr_is_on_attr_is_on = False
154  _LOGGER.warning(
155  "REST xml result could not be parsed and converted to JSON: %s", err
156  )
157  return
158 
159  raw_value = response
160 
161  if response is not None and self._value_template is not None:
162  response = self._value_template.async_render_with_possible_json_value(
163  response, False
164  )
165 
166  try:
167  self._attr_is_on_attr_is_on = bool(int(str(response)))
168  except ValueError:
169  self._attr_is_on_attr_is_on = {
170  "true": True,
171  "on": True,
172  "open": True,
173  "yes": True,
174  }.get(str(response).lower(), False)
175 
176  self._process_manual_data_process_manual_data(raw_value)
177  self.async_write_ha_stateasync_write_ha_state()
None __init__(self, HomeAssistant hass, DataUpdateCoordinator[None]|None coordinator, RestData rest, ConfigType config, ConfigType trigger_entity_config)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
RestData create_rest_data_from_config(HomeAssistant hass, ConfigType config)
Definition: __init__.py:190
tuple[ConfigType, DataUpdateCoordinator[None], RestData] async_get_config_and_coordinator(HomeAssistant hass, str platform_domain, DiscoveryInfoType discovery_info)
Definition: __init__.py:148