Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Support for RESTful API sensors."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import ssl
7 from typing import Any
8 from xml.parsers.expat import ExpatError
9 
10 import voluptuous as vol
11 
13  CONF_STATE_CLASS,
14  DOMAIN as SENSOR_DOMAIN,
15  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
16  SensorDeviceClass,
17 )
18 from homeassistant.components.sensor.helpers import async_parse_date_datetime
19 from homeassistant.const import (
20  CONF_DEVICE_CLASS,
21  CONF_FORCE_UPDATE,
22  CONF_ICON,
23  CONF_NAME,
24  CONF_RESOURCE,
25  CONF_RESOURCE_TEMPLATE,
26  CONF_UNIQUE_ID,
27  CONF_UNIT_OF_MEASUREMENT,
28  CONF_VALUE_TEMPLATE,
29 )
30 from homeassistant.core import HomeAssistant
31 from homeassistant.exceptions import PlatformNotReady
33 from homeassistant.helpers.entity_platform import AddEntitiesCallback
34 from homeassistant.helpers.template import Template
36  CONF_AVAILABILITY,
37  CONF_PICTURE,
38  ManualTriggerSensorEntity,
39 )
40 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
41 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
42 
43 from . import async_get_config_and_coordinator, create_rest_data_from_config
44 from .const import CONF_JSON_ATTRS, CONF_JSON_ATTRS_PATH, DEFAULT_SENSOR_NAME
45 from .data import RestData
46 from .entity import RestEntity
47 from .schema import RESOURCE_SCHEMA, SENSOR_SCHEMA
48 from .util import parse_json_attributes
49 
50 _LOGGER = logging.getLogger(__name__)
51 
52 PLATFORM_SCHEMA = vol.All(
53  SENSOR_PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **SENSOR_SCHEMA}),
54  cv.has_at_least_one_key(CONF_RESOURCE, CONF_RESOURCE_TEMPLATE),
55 )
56 
57 TRIGGER_ENTITY_OPTIONS = (
58  CONF_AVAILABILITY,
59  CONF_DEVICE_CLASS,
60  CONF_ICON,
61  CONF_PICTURE,
62  CONF_UNIQUE_ID,
63  CONF_STATE_CLASS,
64  CONF_UNIT_OF_MEASUREMENT,
65 )
66 
67 
69  hass: HomeAssistant,
70  config: ConfigType,
71  async_add_entities: AddEntitiesCallback,
72  discovery_info: DiscoveryInfoType | None = None,
73 ) -> None:
74  """Set up the RESTful sensor."""
75  # Must update the sensor now (including fetching the rest resource) to
76  # ensure it's updating its state.
77  if discovery_info is not None:
78  conf, coordinator, rest = await async_get_config_and_coordinator(
79  hass, SENSOR_DOMAIN, discovery_info
80  )
81  else:
82  conf = config
83  coordinator = None
84  rest = create_rest_data_from_config(hass, conf)
85  await rest.async_update(log_errors=False)
86 
87  if rest.data is None:
88  if rest.last_exception:
89  if isinstance(rest.last_exception, ssl.SSLError):
90  _LOGGER.error(
91  "Error connecting %s failed with %s",
92  rest.url,
93  rest.last_exception,
94  )
95  return
96  raise PlatformNotReady from rest.last_exception
97  raise PlatformNotReady
98 
99  name = conf.get(CONF_NAME) or Template(DEFAULT_SENSOR_NAME, hass)
100 
101  trigger_entity_config = {CONF_NAME: name}
102 
103  for key in TRIGGER_ENTITY_OPTIONS:
104  if key not in conf:
105  continue
106  trigger_entity_config[key] = conf[key]
107 
109  [
110  RestSensor(
111  hass,
112  coordinator,
113  rest,
114  conf,
115  trigger_entity_config,
116  )
117  ],
118  )
119 
120 
122  """Implementation of a REST sensor."""
123 
124  def __init__(
125  self,
126  hass: HomeAssistant,
127  coordinator: DataUpdateCoordinator[None] | None,
128  rest: RestData,
129  config: ConfigType,
130  trigger_entity_config: ConfigType,
131  ) -> None:
132  """Initialize the REST sensor."""
133  ManualTriggerSensorEntity.__init__(self, hass, trigger_entity_config)
134  RestEntity.__init__(
135  self,
136  coordinator,
137  rest,
138  config.get(CONF_RESOURCE_TEMPLATE),
139  config[CONF_FORCE_UPDATE],
140  )
141  self._value_template_value_template = config.get(CONF_VALUE_TEMPLATE)
142  self._json_attrs_json_attrs = config.get(CONF_JSON_ATTRS)
143  self._json_attrs_path_json_attrs_path = config.get(CONF_JSON_ATTRS_PATH)
144  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
145 
146  @property
147  def available(self) -> bool:
148  """Return if entity is available."""
149  available1 = RestEntity.available.fget(self) # type: ignore[attr-defined]
150  available2 = ManualTriggerSensorEntity.available.fget(self) # type: ignore[attr-defined]
151  return bool(available1 and available2)
152 
153  @property
154  def extra_state_attributes(self) -> dict[str, Any]:
155  """Return extra attributes."""
156  return dict(self._attr_extra_state_attributes_attr_extra_state_attributes)
157 
158  def _update_from_rest_data(self) -> None:
159  """Update state from the rest data."""
160  try:
161  value = self.restrest.data_without_xml()
162  except ExpatError as err:
163  _LOGGER.warning(
164  "REST xml result could not be parsed and converted to JSON: %s", err
165  )
166  value = self.restrest.data
167 
168  if self._json_attrs_json_attrs:
169  self._attr_extra_state_attributes_attr_extra_state_attributes = parse_json_attributes(
170  value, self._json_attrs_json_attrs, self._json_attrs_path_json_attrs_path
171  )
172 
173  raw_value = value
174 
175  if value is not None and self._value_template_value_template is not None:
176  value = self._value_template_value_template.async_render_with_possible_json_value(
177  value, None
178  )
179 
180  if value is None or self.device_classdevice_classdevice_class not in (
181  SensorDeviceClass.DATE,
182  SensorDeviceClass.TIMESTAMP,
183  ):
184  self._attr_native_value_attr_native_value = value
185  self._process_manual_data_process_manual_data(raw_value)
186  self.async_write_ha_stateasync_write_ha_state()
187  return
188 
189  self._attr_native_value_attr_native_value = async_parse_date_datetime(
190  value, self.entity_identity_id, self.device_classdevice_classdevice_class
191  )
192 
193  self._process_manual_data_process_manual_data(raw_value)
194  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)
Definition: sensor.py:131
SensorDeviceClass|None device_class(self)
Definition: __init__.py:313
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:73
dict[str, Any] parse_json_attributes(str|None value, list[str] json_attrs, str|None json_attrs_path)
Definition: util.py:15
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
datetime|date|None async_parse_date_datetime(str value, str entity_id, SensorDeviceClass|str|None device_class)
Definition: helpers.py:19