1 """Helpers to help find if an entity has changed significantly.
3 Does this with help of the integration. Looks at significant_change.py
4 platform for a function `async_check_significant_change`:
7 from typing import Optional
8 from homeassistant.core import HomeAssistant
10 async def async_check_significant_change(
20 Return boolean to indicate if significantly changed. If don't know, return None.
22 **kwargs will allow us to expand this feature in the future, like passing in a
23 level of significance.
25 The following cases will never be passed to your function:
26 - if either state is unknown/unavailable
27 - state adding/removing
30 from __future__
import annotations
32 from collections.abc
import Callable, Mapping
33 from types
import MappingProxyType
34 from typing
import Any, Protocol
40 from .integration_platform
import async_process_integration_platforms
42 PLATFORM =
"significant_change"
43 DATA_FUNCTIONS: HassKey[dict[str, CheckTypeFunc]] =
HassKey(
"significant_change")
44 type CheckTypeFunc = Callable[
48 dict | MappingProxyType,
50 dict | MappingProxyType,
55 type ExtraCheckTypeFunc = Callable[
59 dict | MappingProxyType,
62 dict | MappingProxyType,
70 """Define the format of significant_change platforms."""
76 old_attrs: Mapping[str, Any],
78 new_attrs: Mapping[str, Any],
80 """Test if state significantly changed."""
86 extra_significant_check: ExtraCheckTypeFunc |
None =
None,
87 ) -> SignificantlyChangedChecker:
88 """Create a significantly changed checker for a domain."""
95 """Initialize the functions."""
96 if DATA_FUNCTIONS
in hass.data:
99 functions = hass.data[DATA_FUNCTIONS] = {}
102 def process_platform(
105 platform: SignificantChangeProtocol,
107 """Process a significant change platform."""
108 functions[component_name] = platform.async_check_significant_change
114 """Test if exactly one value is None."""
115 return (val1
is None and val2
is not None)
or (val1
is not None and val2
is None)
119 old_state: float |
None,
120 new_state: float |
None,
122 metric: Callable[[int | float, int | float], int | float],
124 """Check if two numeric values have changed."""
125 if old_state
is None and new_state
is None:
131 assert old_state
is not None
132 assert new_state
is not None
134 if metric(old_state, new_state) >= change:
145 """Check if two numeric values have changed."""
147 val1, val2, change,
lambda val1, val2: abs(val1 - val2)
152 old_state: float |
None,
153 new_state: float |
None,
156 """Check if two numeric values have changed."""
158 def percentage_change(old_state: float, new_state: float) -> float:
159 if old_state == new_state:
162 return (abs(new_state - old_state) / old_state) * 100.0
163 except ZeroDivisionError:
170 """Check if given value is a valid float."""
179 """Class to keep track of entities to see if they have significantly changed.
181 Will always compare the entity to the last entity that was considered significant.
187 extra_significant_check: ExtraCheckTypeFunc |
None =
None,
189 """Test if an entity has significantly changed."""
191 self.last_approved_entities: dict[str, tuple[State, Any]] = {}
196 self, new_state: State, *, extra_arg: Any |
None =
None
198 """Return if this was a significant change.
200 Extra kwargs are passed to the extra significant checker.
202 old_data: tuple[State, Any] |
None = self.last_approved_entities.
get(
208 self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg)
211 old_state, old_extra_arg = old_data
214 if new_state.state
in (STATE_UNKNOWN, STATE_UNAVAILABLE):
215 if new_state.state == old_state.state:
218 self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg)
222 if old_state.state
in (STATE_UNKNOWN, STATE_UNAVAILABLE):
223 self.last_approved_entities[new_state.entity_id] = (new_state, extra_arg)
226 functions = self.
hasshass.data.get(DATA_FUNCTIONS)
228 if functions
is None:
229 raise RuntimeError(
"Significant Change not initialized")
231 check_significantly_changed = functions.get(new_state.domain)
233 if check_significantly_changed
is not None:
234 result = check_significantly_changed(
237 old_state.attributes,
239 new_state.attributes,
249 old_state.attributes,
252 new_state.attributes,
261 self.last_approved_entities[new_state.entity_id] = (
bool|None async_check_significant_change(self, HomeAssistant hass, str old_state, Mapping[str, Any] old_attrs, str new_state, Mapping[str, Any] new_attrs)
None __init__(self, HomeAssistant hass, ExtraCheckTypeFunc|None extra_significant_check=None)
bool async_is_significant_change(self, State new_state, *Any|None extra_arg=None)
web.Response get(self, web.Request request, str config_key)
None _initialize(HomeAssistant hass)
SignificantlyChangedChecker create_checker(HomeAssistant hass, str _domain, ExtraCheckTypeFunc|None extra_significant_check=None)
bool check_absolute_change(float|None val1, float|None val2, float change)
bool check_percentage_change(float|None old_state, float|None new_state, float change)
bool check_valid_float(str|float value)
bool _check_numeric_change(float|None old_state, float|None new_state, float change, Callable[[int|float, int|float], int|float] metric)
bool either_one_none(Any|None val1, Any|None val2)