Add type option "first_available" to sensor group in group component (#155525)

This commit is contained in:
Michael Potthoff
2026-02-05 20:26:45 +01:00
committed by GitHub
parent 1cef223a06
commit 9f32e0da14
4 changed files with 81 additions and 0 deletions

View File

@@ -39,6 +39,7 @@ from .valve import async_create_preview_valve
_STATISTIC_MEASURES = [
"last",
"first_available",
"max",
"mean",
"median",

View File

@@ -68,6 +68,8 @@ ATTR_MEAN = "mean"
ATTR_MEDIAN = "median"
ATTR_LAST = "last"
ATTR_LAST_ENTITY_ID = "last_entity_id"
ATTR_FIRST_AVAILABLE = "first_available"
ATTR_FIRST_AVAILABLE_ENTITY_ID = "first_available_entity_id"
ATTR_RANGE = "range"
ATTR_STDEV = "stdev"
ATTR_SUM = "sum"
@@ -78,6 +80,7 @@ SENSOR_TYPES = {
ATTR_MEAN: "mean",
ATTR_MEDIAN: "median",
ATTR_LAST: "last",
ATTR_FIRST_AVAILABLE: "first_available",
ATTR_RANGE: "range",
ATTR_STDEV: "stdev",
ATTR_SUM: "sum",
@@ -255,6 +258,19 @@ def calc_last(
return attributes, last
def calc_first_available(
sensor_values: list[tuple[str, float, State]],
) -> tuple[dict[str, str | None], float | None]:
"""Calculate first available value."""
first_available_entity_id: str | None = None
first_available: float | None = None
if sensor_values:
first_available_entity_id, first_available, _ = sensor_values[0]
attributes = {ATTR_FIRST_AVAILABLE_ENTITY_ID: first_available_entity_id}
return attributes, first_available
def calc_range(
sensor_values: list[tuple[str, float, State]],
) -> tuple[dict[str, str | None], float]:
@@ -309,6 +325,7 @@ CALC_TYPES: dict[
"mean": calc_mean,
"median": calc_median,
"last": calc_last,
"first_available": calc_first_available,
"range": calc_range,
"stdev": calc_stdev,
"sum": calc_sum,

View File

@@ -280,6 +280,7 @@
"selector": {
"type": {
"options": {
"first_available": "First available",
"last": "Most recently updated",
"max": "Maximum",
"mean": "Arithmetic mean",

View File

@@ -12,6 +12,7 @@ import pytest
from homeassistant import config as hass_config
from homeassistant.components.group import DOMAIN
from homeassistant.components.group.sensor import (
ATTR_FIRST_AVAILABLE_ENTITY_ID,
ATTR_LAST_ENTITY_ID,
ATTR_MAX_ENTITY_ID,
ATTR_MIN_ENTITY_ID,
@@ -86,6 +87,11 @@ def set_or_remove_state(
("mean", MEAN, {}),
("median", MEDIAN, {}),
("last", VALUES[2], {ATTR_LAST_ENTITY_ID: "sensor.test_3"}),
(
"first_available",
VALUES[0],
{ATTR_FIRST_AVAILABLE_ENTITY_ID: "sensor.test_1"},
),
("range", RANGE, {}),
("stdev", STDEV, {}),
("sum", SUM_VALUE, {}),
@@ -861,6 +867,62 @@ async def test_last_sensor(hass: HomeAssistant) -> None:
assert state.attributes.get("last_entity_id") == entity_id
async def test_first_available_sensor(hass: HomeAssistant) -> None:
"""Test the first available sensor."""
config = {
SENSOR_DOMAIN: {
"platform": DOMAIN,
"name": "test_first_available",
"type": "first_available",
"entities": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
"unique_id": "very_unique_id_first_available_sensor",
"ignore_non_numeric": True,
}
}
assert await async_setup_component(hass, "sensor", config)
await hass.async_block_till_done()
entity_ids = config["sensor"]["entities"]
# Ensure that while sensor states are being set
# the group will always point to the first available sensor.
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_first_available")
assert str(float(VALUES[0])) == state.state
assert entity_ids[0] == state.attributes.get("first_available_entity_id")
# If the second sensor of the group becomes unavailable
# then the first one should still be taken.
hass.states.async_set(entity_ids[1], STATE_UNAVAILABLE)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_first_available")
assert str(float(VALUES[0])) == state.state
assert entity_ids[0] == state.attributes.get("first_available_entity_id")
# If the first sensor of the group becomes now unavailable
# then the third one should be taken.
hass.states.async_set(entity_ids[0], STATE_UNAVAILABLE)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_first_available")
assert str(float(VALUES[2])) == state.state
assert entity_ids[2] == state.attributes.get("first_available_entity_id")
# If all sensors of the group become unavailable
# then the group should also be unavailable.
hass.states.async_set(entity_ids[2], STATE_UNAVAILABLE)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_first_available")
assert state.state == STATE_UNAVAILABLE
assert state.attributes.get("first_available_entity_id") is None
async def test_sensors_attributes_added_when_entity_info_available(
hass: HomeAssistant,
) -> None: