1 """Support for displaying minimal, maximal, mean or median values."""
3 from __future__
import annotations
5 from datetime
import datetime
10 import voluptuous
as vol
13 PLATFORM_SCHEMA
as SENSOR_PLATFORM_SCHEMA,
19 ATTR_UNIT_OF_MEASUREMENT,
33 from .
import PLATFORMS
34 from .const
import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
36 _LOGGER = logging.getLogger(__name__)
38 ATTR_MIN_VALUE =
"min_value"
39 ATTR_MIN_ENTITY_ID =
"min_entity_id"
40 ATTR_MAX_VALUE =
"max_value"
41 ATTR_MAX_ENTITY_ID =
"max_entity_id"
43 ATTR_MEDIAN =
"median"
45 ATTR_LAST_ENTITY_ID =
"last_entity_id"
49 ICON =
"mdi:calculator"
52 ATTR_MIN_VALUE:
"min",
53 ATTR_MAX_VALUE:
"max",
55 ATTR_MEDIAN:
"median",
60 SENSOR_TYPE_TO_ATTR = {v: k
for k, v
in SENSOR_TYPES.items()}
62 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
64 vol.Optional(CONF_TYPE, default=SENSOR_TYPES[ATTR_MAX_VALUE]): vol.All(
65 cv.string, vol.In(SENSOR_TYPES.values())
67 vol.Optional(CONF_NAME): cv.string,
68 vol.Required(CONF_ENTITY_IDS): cv.entity_ids,
69 vol.Optional(CONF_ROUND_DIGITS, default=2): vol.Coerce(int),
70 vol.Optional(CONF_UNIQUE_ID): cv.string,
77 config_entry: ConfigEntry,
78 async_add_entities: AddEntitiesCallback,
80 """Initialize min/max/mean config entry."""
81 registry = er.async_get(hass)
82 entity_ids = er.async_validate_entity_ids(
83 registry, config_entry.options[CONF_ENTITY_IDS]
85 sensor_type = config_entry.options[CONF_TYPE]
86 round_digits =
int(config_entry.options[CONF_ROUND_DIGITS])
95 config_entry.entry_id,
104 async_add_entities: AddEntitiesCallback,
105 discovery_info: DiscoveryInfoType |
None =
None,
107 """Set up the min/max/mean sensor."""
108 entity_ids: list[str] = config[CONF_ENTITY_IDS]
109 name: str |
None = config.get(CONF_NAME)
110 sensor_type: str = config[CONF_TYPE]
111 round_digits: int = config[CONF_ROUND_DIGITS]
112 unique_id = config.get(CONF_UNIQUE_ID)
117 [
MinMaxSensor(entity_ids, name, sensor_type, round_digits, unique_id)]
121 def calc_min(sensor_values: list[tuple[str, Any]]) -> tuple[str |
None, float |
None]:
122 """Calculate min value, honoring unknown states."""
123 val: float |
None =
None
124 entity_id: str |
None =
None
125 for sensor_id, sensor_value
in sensor_values:
126 if sensor_value
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
and (
127 val
is None or val > sensor_value
129 entity_id, val = sensor_id, sensor_value
130 return entity_id, val
133 def calc_max(sensor_values: list[tuple[str, Any]]) -> tuple[str |
None, float |
None]:
134 """Calculate max value, honoring unknown states."""
135 val: float |
None =
None
136 entity_id: str |
None =
None
137 for sensor_id, sensor_value
in sensor_values:
138 if sensor_value
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
and (
139 val
is None or val < sensor_value
141 entity_id, val = sensor_id, sensor_value
142 return entity_id, val
145 def calc_mean(sensor_values: list[tuple[str, Any]], round_digits: int) -> float |
None:
146 """Calculate mean value, honoring unknown states."""
149 for _, sensor_value
in sensor_values
150 if sensor_value
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
155 value: float = round(statistics.mean(result), round_digits)
160 sensor_values: list[tuple[str, Any]], round_digits: int
162 """Calculate median value, honoring unknown states."""
165 for _, sensor_value
in sensor_values
166 if sensor_value
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
171 value: float = round(statistics.median(result), round_digits)
175 def calc_range(sensor_values: list[tuple[str, Any]], round_digits: int) -> float |
None:
176 """Calculate range value, honoring unknown states."""
179 for _, sensor_value
in sensor_values
180 if sensor_value
not in [STATE_UNKNOWN, STATE_UNAVAILABLE]
185 value: float = round(
max(result) -
min(result), round_digits)
189 def calc_sum(sensor_values: list[tuple[str, Any]], round_digits: int) -> float |
None:
190 """Calculate a sum of values, not honoring unknown states."""
192 for _, sensor_value
in sensor_values:
193 if sensor_value
in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
195 result += sensor_value
197 value: float = round(result, round_digits)
202 """Representation of a min/max sensor."""
205 _attr_should_poll =
False
206 _attr_state_class = SensorStateClass.MEASUREMENT
210 entity_ids: list[str],
214 unique_id: str |
None,
216 """Initialize the min/max sensor."""
225 self.
_attr_name_attr_name = f
"{sensor_type} sensor".capitalize()
229 self.
min_valuemin_value: float |
None =
None
230 self.
max_valuemax_value: float |
None =
None
231 self.
meanmean: float |
None =
None
232 self.
lastlast: float |
None =
None
233 self.
medianmedian: float |
None =
None
234 self.
rangerange: float |
None =
None
235 self.
sumsum: float |
None =
None
236 self.min_entity_id: str |
None =
None
237 self.max_entity_id: str |
None =
None
240 self.states: dict[str, Any] = {}
243 """Handle added to Hass."""
252 state = self.
hasshass.states.get(entity_id)
253 state_event: Event[EventStateChangedData] =
Event(
254 "", {
"entity_id": entity_id,
"new_state": state,
"old_state":
None}
262 """Return the state of the sensor."""
265 value: StateType | datetime = getattr(self, self.
_sensor_attr_sensor_attr)
270 """Return the unit the value is expressed in."""
277 """Return the state attributes of the sensor."""
279 return {ATTR_MIN_ENTITY_ID: self.min_entity_id}
281 return {ATTR_MAX_ENTITY_ID: self.max_entity_id}
288 self, event: Event[EventStateChangedData], update_state: bool =
True
290 """Handle the sensor state changes."""
291 new_state = event.data[
"new_state"]
292 entity = event.data[
"entity_id"]
296 or new_state.state
is None
303 self.states[entity] = STATE_UNKNOWN
313 ATTR_UNIT_OF_MEASUREMENT
317 ATTR_UNIT_OF_MEASUREMENT
320 "Units of measurement do not match for entity %s", self.
entity_identity_id
325 self.states[entity] =
float(new_state.state)
330 "Unable to store state. Only numerical states are supported"
341 """Calculate the values."""
343 (entity_id, self.states[entity_id])
345 if entity_id
in self.states
None async_added_to_hass(self)
StateType|datetime native_value(self)
str|None native_unit_of_measurement(self)
dict[str, Any]|None extra_state_attributes(self)
_unit_of_measurement_mismatch
None _async_min_max_sensor_state_listener(self, Event[EventStateChangedData] event, bool update_state=True)
None __init__(self, list[str] entity_ids, str|None name, str sensor_type, int round_digits, str|None unique_id)
None async_write_ha_state(self)
None async_on_remove(self, CALLBACK_TYPE func)
float|None calc_median(list[tuple[str, Any]] sensor_values, int round_digits)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
float|None calc_range(list[tuple[str, Any]] sensor_values, int round_digits)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
float|None calc_sum(list[tuple[str, Any]] sensor_values, int round_digits)
tuple[str|None, float|None] calc_max(list[tuple[str, Any]] sensor_values)
tuple[str|None, float|None] calc_min(list[tuple[str, Any]] sensor_values)
float|None calc_mean(list[tuple[str, Any]] sensor_values, int round_digits)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
None async_setup_reload_service(HomeAssistant hass, str domain, Iterable[str] platforms)