Skip to content
Merged
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
11 changes: 7 additions & 4 deletions homeassistant/components/group/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,6 @@ def __init__(
self._attr_name = name
if name == DEFAULT_NAME:
self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize()
self._attr_extra_state_attributes = {ATTR_ENTITY_ID: entity_ids}
self._attr_unique_id = unique_id
self._ignore_non_numeric = ignore_non_numeric
self.mode = all if ignore_non_numeric is False else any
Expand Down Expand Up @@ -374,7 +373,7 @@ def calculate_state_attributes(self, valid_state_entities: list[str]) -> None:
def async_update_group_state(self) -> None:
"""Query all members and determine the sensor group state."""
self.calculate_state_attributes(self._get_valid_entities())
states: list[str] = []
states: list[str | None] = []
valid_units = self._valid_units
valid_states: list[bool] = []
sensor_values: list[tuple[str, float, State]] = []
Expand Down Expand Up @@ -435,9 +434,12 @@ def async_update_group_state(self) -> None:
state.attributes.get("unit_of_measurement"),
self.entity_id,
)
else:
states.append(None)
valid_states.append(False)

# Set group as unavailable if all members do not have numeric values
self._attr_available = any(numeric_state for numeric_state in valid_states)
# Set group as unavailable if all members are unavailable or missing
self._attr_available = not all(s in (STATE_UNAVAILABLE, None) for s in states)

valid_state = self.mode(
state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in states
Expand All @@ -446,6 +448,7 @@ def async_update_group_state(self) -> None:

if not valid_state or not valid_state_numeric:
self._attr_native_value = None
self._extra_state_attribute = {}
return

# Calculate values
Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/matter/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,10 @@ def _update_from_device(self) -> None:
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
required_attributes=(
clusters.Thermostat.Attributes.RemoteSensing,
clusters.Thermostat.Attributes.OutdoorTemperature,
),
allow_multi=True,
),
MatterDiscoverySchema(
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/portainer/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["pyportainer==1.0.22"]
"requirements": ["pyportainer==1.0.23"]
}
2 changes: 1 addition & 1 deletion requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 30 additions & 24 deletions tests/components/group/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
await hass.async_block_till_done()

state = hass.states.get("sensor.test_max")
assert state.state == STATE_UNAVAILABLE
assert state.state == STATE_UNKNOWN
assert state.attributes.get("min_entity_id") is None
assert state.attributes.get("max_entity_id") is None

Expand All @@ -215,20 +215,20 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:

state = hass.states.get("sensor.test_max")
assert state.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN]
assert entity_ids[1] == state.attributes.get("max_entity_id")
assert state.attributes.get("max_entity_id") == entity_ids[1]

hass.states.async_set(entity_ids[2], STATE_UNKNOWN)
await hass.async_block_till_done()

state = hass.states.get("sensor.test_max")
assert state.state not in [STATE_UNAVAILABLE, STATE_UNKNOWN]
assert entity_ids[1] == state.attributes.get("max_entity_id")
assert state.attributes.get("max_entity_id") == entity_ids[1]

hass.states.async_set(entity_ids[1], STATE_UNAVAILABLE)
await hass.async_block_till_done()

state = hass.states.get("sensor.test_max")
assert state.state == STATE_UNAVAILABLE
assert state.state == STATE_UNKNOWN
assert state.attributes.get("min_entity_id") is None
assert state.attributes.get("max_entity_id") is None

Expand Down Expand Up @@ -281,14 +281,14 @@ async def test_reload(hass: HomeAssistant) -> None:
(STATES_ONE_MISSING, "17.0"),
(STATES_ONE_UNKNOWN, "17.0"),
(STATES_ONE_UNAVAILABLE, "17.0"),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_ERROR, STATE_UNKNOWN),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNKNOWN),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNKNOWN),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN),
],
)
async def test_sensor_incorrect_state_with_ignore_non_numeric(
Expand Down Expand Up @@ -339,17 +339,17 @@ async def test_sensor_incorrect_state_with_ignore_non_numeric(
("states_list", "expected_group_state", "error_count"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN, 1),
(STATES_ONE_MISSING, "17.0", 0),
(STATES_ONE_MISSING, STATE_UNKNOWN, 0),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN, 1),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN, 1),
(STATES_ALL_ERROR, STATE_UNAVAILABLE, 3),
(STATES_ALL_ERROR, STATE_UNKNOWN, 3),
(STATES_ALL_MISSING, STATE_UNAVAILABLE, 0),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE, 3),
(STATES_ALL_UNKNOWN, STATE_UNKNOWN, 3),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE, 3),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE, 2),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE, 3),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE, 2),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE, 2),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNKNOWN, 2),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN, 3),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN, 2),
],
)
async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
Expand Down Expand Up @@ -402,17 +402,17 @@ async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
("states_list", "expected_group_state"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN),
(STATES_ONE_MISSING, "32.3"),
(STATES_ONE_MISSING, STATE_UNKNOWN),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_ERROR, STATE_UNKNOWN),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNKNOWN),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNKNOWN),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNKNOWN),
],
)
async def test_sensor_require_all_states(
Expand Down Expand Up @@ -740,7 +740,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
await hass.async_block_till_done()

state = hass.states.get("sensor.test_sum")
assert state.state == STATE_UNAVAILABLE
assert state.state == STATE_UNKNOWN
assert state.attributes.get("device_class") == "energy"
assert state.attributes.get("state_class") == "total"
assert state.attributes.get("unit_of_measurement") is None
Expand Down Expand Up @@ -847,12 +847,18 @@ async def test_last_sensor(hass: HomeAssistant) -> None:

entity_ids = config["sensor"]["entities"]

for entity_id in entity_ids[1:]:
hass.states.async_set(entity_id, "0.0")
await hass.async_block_till_done()
state = hass.states.get("sensor.test_last")
assert state.state == STATE_UNKNOWN

for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_last")
assert str(float(value)) == state.state
assert entity_id == state.attributes.get("last_entity_id")
assert state.state == str(float(value))
assert state.attributes.get("last_entity_id") == entity_id


async def test_sensors_attributes_added_when_entity_info_available(
Expand Down
96 changes: 0 additions & 96 deletions tests/components/matter/snapshots/test_binary_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -439,54 +439,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20EBP1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
Expand Down Expand Up @@ -535,54 +487,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[heiman_motion_sensor_m1][binary_sensor.smart_motion_sensor_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
Expand Down
Loading