Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions homeassistant/components/group/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

_STATISTIC_MEASURES = [
"last",
"first",
"max",
"mean",
"median",
Expand Down
17 changes: 17 additions & 0 deletions homeassistant/components/group/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/group/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
"selector": {
"type": {
"options": {
"first": "First",
"last": "Most recently updated",
"max": "Maximum",
"mean": "Arithmetic mean",
Expand Down
58 changes: 58 additions & 0 deletions tests/components/group/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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, {}),
Expand Down Expand Up @@ -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:
Expand Down