diff --git a/homeassistant/components/group/config_flow.py b/homeassistant/components/group/config_flow.py index 3624ebb7ef2fd..bc81a6ffe7653 100644 --- a/homeassistant/components/group/config_flow.py +++ b/homeassistant/components/group/config_flow.py @@ -39,6 +39,7 @@ _STATISTIC_MEASURES = [ "last", + "first", "max", "mean", "median", diff --git a/homeassistant/components/group/sensor.py b/homeassistant/components/group/sensor.py index cad794fd6b96a..f9cb543864d83 100644 --- a/homeassistant/components/group/sensor.py +++ b/homeassistant/components/group/sensor.py @@ -68,6 +68,8 @@ ATTR_MEDIAN = "median" ATTR_LAST = "last" ATTR_LAST_ENTITY_ID = "last_entity_id" +ATTR_FIRST = "first" +ATTR_FIRST_ENTITY_ID = "first_entity_id" ATTR_RANGE = "range" ATTR_STDEV = "stdev" ATTR_SUM = "sum" @@ -78,6 +80,7 @@ ATTR_MEAN: "mean", ATTR_MEDIAN: "median", ATTR_LAST: "last", + ATTR_FIRST: "first", ATTR_RANGE: "range", ATTR_STDEV: "stdev", ATTR_SUM: "sum", @@ -255,6 +258,19 @@ def calc_last( return attributes, last +def calc_first( + sensor_values: list[tuple[str, float, State]], +) -> tuple[dict[str, str | None], float | None]: + """Calculate first value.""" + first_entity_id: str | None = None + first: float | None = None + if len(sensor_values) > 0: + first_entity_id, first, _ = sensor_values[0] + + attributes = {ATTR_FIRST_ENTITY_ID: first_entity_id} + return attributes, first + + def calc_range( sensor_values: list[tuple[str, float, State]], ) -> tuple[dict[str, str | None], float]: @@ -309,6 +325,7 @@ def calc_product( "mean": calc_mean, "median": calc_median, "last": calc_last, + "first": calc_first, "range": calc_range, "stdev": calc_stdev, "sum": calc_sum, diff --git a/homeassistant/components/group/strings.json b/homeassistant/components/group/strings.json index ba4cb24270095..44a4d49afe753 100644 --- a/homeassistant/components/group/strings.json +++ b/homeassistant/components/group/strings.json @@ -280,6 +280,7 @@ "selector": { "type": { "options": { + "first": "First", "last": "Most recently updated", "max": "Maximum", "mean": "Arithmetic mean", diff --git a/tests/components/group/test_sensor.py b/tests/components/group/test_sensor.py index acbd9c44cbfb6..a942ffbecd9b3 100644 --- a/tests/components/group/test_sensor.py +++ b/tests/components/group/test_sensor.py @@ -12,6 +12,7 @@ from homeassistant import config as hass_config from homeassistant.components.group import DOMAIN from homeassistant.components.group.sensor import ( + ATTR_FIRST_ENTITY_ID, ATTR_LAST_ENTITY_ID, ATTR_MAX_ENTITY_ID, ATTR_MIN_ENTITY_ID, @@ -61,6 +62,7 @@ ("mean", MEAN, {}), ("median", MEDIAN, {}), ("last", VALUES[2], {ATTR_LAST_ENTITY_ID: "sensor.test_3"}), + ("first", VALUES[0], {ATTR_FIRST_ENTITY_ID: "sensor.test_1"}), ("range", RANGE, {}), ("stdev", STDEV, {}), ("sum", SUM_VALUE, {}), @@ -767,6 +769,62 @@ async def test_last_sensor(hass: HomeAssistant) -> None: assert entity_id == state.attributes.get("last_entity_id") +async def test_first_sensor(hass: HomeAssistant) -> None: + """Test the first sensor.""" + config = { + SENSOR_DOMAIN: { + "platform": DOMAIN, + "name": "test_first", + "type": "first", + "entities": ["sensor.test_1", "sensor.test_2", "sensor.test_3"], + "unique_id": "very_unique_id_first_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 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") + assert str(float(VALUES[0])) == state.state + assert entity_ids[0] == state.attributes.get("first_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") + assert str(float(VALUES[0])) == state.state + assert entity_ids[0] == state.attributes.get("first_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") + assert str(float(VALUES[2])) == state.state + assert entity_ids[2] == state.attributes.get("first_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") + assert state.state == STATE_UNAVAILABLE + assert state.attributes.get("first_entity_id") is None + + async def test_sensors_attributes_added_when_entity_info_available( hass: HomeAssistant, ) -> None: