1 """Support for monitoring if a sensor value is below/above a threshold."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Mapping
7 from typing
import Any, Final
9 import voluptuous
as vol
12 DEVICE_CLASSES_SCHEMA,
13 PLATFORM_SCHEMA
as BINARY_SENSOR_PLATFORM_SCHEMA,
14 BinarySensorDeviceClass,
29 EventStateChangedData,
60 _LOGGER = logging.getLogger(__name__)
62 DEFAULT_NAME: Final =
"Threshold"
66 """Validate data point list is greater than polynomial degrees."""
67 if value.get(CONF_LOWER)
is None and value.get(CONF_UPPER)
is None:
68 raise vol.Invalid(
"Lower or Upper thresholds are not provided")
73 PLATFORM_SCHEMA = vol.All(
74 BINARY_SENSOR_PLATFORM_SCHEMA.extend(
76 vol.Required(CONF_ENTITY_ID): cv.entity_id,
77 vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
78 vol.Optional(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): vol.Coerce(
81 vol.Optional(CONF_LOWER): vol.Coerce(float),
82 vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
83 vol.Optional(CONF_UPPER): vol.Coerce(float),
92 config_entry: ConfigEntry,
93 async_add_entities: AddEntitiesCallback,
95 """Initialize threshold config entry."""
96 registry = er.async_get(hass)
98 entity_id = er.async_validate_entity_id(
99 registry, config_entry.options[CONF_ENTITY_ID]
107 hysteresis = config_entry.options[CONF_HYSTERESIS]
108 lower = config_entry.options[CONF_LOWER]
109 name = config_entry.title
110 unique_id = config_entry.entry_id
111 upper = config_entry.options[CONF_UPPER]
123 device_info=device_info,
132 async_add_entities: AddEntitiesCallback,
133 discovery_info: DiscoveryInfoType |
None =
None,
135 """Set up the Threshold sensor."""
136 entity_id: str = config[CONF_ENTITY_ID]
137 name: str = config[CONF_NAME]
138 lower: float |
None = config.get(CONF_LOWER)
139 upper: float |
None = config.get(CONF_UPPER)
140 hysteresis: float = config[CONF_HYSTERESIS]
141 device_class: BinarySensorDeviceClass |
None = config.get(CONF_DEVICE_CLASS)
146 entity_id, name, lower, upper, hysteresis, device_class,
None
153 """Return the type of threshold this sensor represents."""
154 if lower
is not None and upper
is not None:
156 if lower
is not None:
162 """Representation of a Threshold sensor."""
164 _attr_should_poll =
False
165 _unrecorded_attributes = frozenset(
166 {ATTR_ENTITY_ID, ATTR_HYSTERESIS, ATTR_LOWER, ATTR_TYPE, ATTR_UPPER}
176 device_class: BinarySensorDeviceClass |
None,
177 unique_id: str |
None,
178 device_info: DeviceInfo |
None =
None,
180 """Initialize the Threshold sensor."""
181 self.
_preview_callback_preview_callback: Callable[[str, Mapping[str, Any]],
None] |
None =
None
186 if lower
is not None:
188 if upper
is not None:
191 self._hysteresis: float = hysteresis
197 """Run when entity about to be added to hass."""
202 """Set up the sensor and start tracking state changes."""
204 def _update_sensor_state() -> None:
205 """Handle sensor state changes."""
206 if (new_state := self.
hasshass.states.get(self.
_entity_id_entity_id))
is None:
212 if new_state.state
in [STATE_UNKNOWN, STATE_UNAVAILABLE]
213 else float(new_state.state)
215 except (ValueError, TypeError):
217 _LOGGER.warning(
"State is not numerical")
224 calculated_state.state, calculated_state.attributes
228 def async_threshold_sensor_state_listener(
229 event: Event[EventStateChangedData],
231 """Handle sensor state changes."""
232 _update_sensor_state()
240 self.
hasshass, [self.
_entity_id_entity_id], async_threshold_sensor_state_listener
243 _update_sensor_state()
247 """Return the state attributes of the sensor."""
250 ATTR_HYSTERESIS: self._hysteresis,
251 ATTR_LOWER: getattr(self,
"_threshold_lower",
None),
255 ATTR_UPPER: getattr(self,
"_threshold_upper",
None),
260 """Update the state."""
262 def below(sensor_value: float, threshold: float) -> bool:
263 """Determine if the sensor value is below a threshold."""
264 return sensor_value < (threshold - self._hysteresis)
266 def above(sensor_value: float, threshold: float) -> bool:
267 """Determine if the sensor value is above a threshold."""
268 return sensor_value > (threshold + self._hysteresis)
324 preview_callback: Callable[[str, Mapping[str, Any]],
None],
326 """Render a preview."""
331 not hasattr(self,
"_threshold_lower")
332 and not hasattr(self,
"_threshold_upper")
336 preview_callback(calculated_state.state, calculated_state.attributes)
None __init__(self, str entity_id, str name, float|None lower, float|None upper, float hysteresis, BinarySensorDeviceClass|None device_class, str|None unique_id, DeviceInfo|None device_info=None)
CALLBACK_TYPE async_start_preview(self, Callable[[str, Mapping[str, Any]], None] preview_callback)
None _async_setup_sensor(self)
None async_added_to_hass(self)
dict[str, Any] extra_state_attributes(self)
None async_write_ha_state(self)
CalculatedState _async_calculate_state(self)
None _call_on_remove_callbacks(self)
None async_on_remove(self, CALLBACK_TYPE func)
dict no_missing_threshold(dict value)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
str _threshold_type(float|None lower, float|None upper)
dr.DeviceInfo|None async_device_info_to_link_from_entity(HomeAssistant hass, str entity_id_or_uuid)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)