mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 08:06:00 +01:00
Publish area and floor metrics to Prometheus (#159322)
This commit is contained in:
@@ -76,15 +76,33 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, State
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
entityfilter,
|
||||
floor_registry as fr,
|
||||
state as state_helper,
|
||||
)
|
||||
from homeassistant.helpers.area_registry import (
|
||||
EVENT_AREA_REGISTRY_UPDATED,
|
||||
AreaEntry,
|
||||
EventAreaRegistryUpdatedData,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import (
|
||||
EVENT_DEVICE_REGISTRY_UPDATED,
|
||||
EventDeviceRegistryUpdatedData,
|
||||
)
|
||||
from homeassistant.helpers.entity_registry import (
|
||||
EVENT_ENTITY_REGISTRY_UPDATED,
|
||||
EventEntityRegistryUpdatedData,
|
||||
)
|
||||
from homeassistant.helpers.entity_values import EntityValues
|
||||
from homeassistant.helpers.floor_registry import (
|
||||
EVENT_FLOOR_REGISTRY_UPDATED,
|
||||
EventFloorRegistryUpdatedData,
|
||||
FloorEntry,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.dt import as_timestamp
|
||||
from homeassistant.util.unit_conversion import TemperatureConverter
|
||||
@@ -152,6 +170,11 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
conf[CONF_COMPONENT_CONFIG_GLOB],
|
||||
)
|
||||
|
||||
area_registry = ar.async_get(hass)
|
||||
device_registry = dr.async_get(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
floor_registry = fr.async_get(hass)
|
||||
|
||||
metrics = PrometheusMetrics(
|
||||
entity_filter,
|
||||
namespace,
|
||||
@@ -159,6 +182,10 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
component_config,
|
||||
override_metric,
|
||||
default_metric,
|
||||
area_registry,
|
||||
device_registry,
|
||||
entity_registry,
|
||||
floor_registry,
|
||||
)
|
||||
|
||||
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_state_changed_event)
|
||||
@@ -166,6 +193,18 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
EVENT_ENTITY_REGISTRY_UPDATED,
|
||||
metrics.handle_entity_registry_updated,
|
||||
)
|
||||
hass.bus.listen(
|
||||
EVENT_DEVICE_REGISTRY_UPDATED,
|
||||
metrics.handle_device_registry_updated,
|
||||
)
|
||||
hass.bus.listen(EVENT_AREA_REGISTRY_UPDATED, metrics.handle_area_registry_updated)
|
||||
hass.bus.listen(EVENT_FLOOR_REGISTRY_UPDATED, metrics.handle_floor_registry_updated)
|
||||
|
||||
for floor in floor_registry.async_list_floors():
|
||||
metrics.handle_floor(floor)
|
||||
|
||||
for area in area_registry.async_list_areas():
|
||||
metrics.handle_area(area)
|
||||
|
||||
for state in hass.states.all():
|
||||
if entity_filter(state.entity_id):
|
||||
@@ -201,6 +240,10 @@ class PrometheusMetrics:
|
||||
component_config: EntityValues,
|
||||
override_metric: str | None,
|
||||
default_metric: str | None,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
floor_registry: fr.FloorRegistry,
|
||||
) -> None:
|
||||
"""Initialize Prometheus Metrics."""
|
||||
self._component_config = component_config
|
||||
@@ -228,6 +271,14 @@ class PrometheusMetrics:
|
||||
)
|
||||
self._climate_units = climate_units
|
||||
|
||||
self._area_info_metrics: dict[str, MetricNameWithLabelValues] = {}
|
||||
self._floor_info_metrics: dict[str, MetricNameWithLabelValues] = {}
|
||||
|
||||
self.area_registry = area_registry
|
||||
self.device_registry = device_registry
|
||||
self.entity_registry = entity_registry
|
||||
self.floor_registry = floor_registry
|
||||
|
||||
def handle_state_changed_event(self, event: Event[EventStateChangedData]) -> None:
|
||||
"""Handle new messages from the bus."""
|
||||
if (state := event.data.get("new_state")) is None:
|
||||
@@ -251,6 +302,11 @@ class PrometheusMetrics:
|
||||
entity_id = state.entity_id
|
||||
_LOGGER.debug("Handling state update for %s", entity_id)
|
||||
|
||||
if not self._metrics_by_entity_id[state.entity_id]:
|
||||
area_id = self._find_area_id(state.entity_id)
|
||||
if area_id is not None:
|
||||
self._add_entity_info(state.entity_id, area_id)
|
||||
|
||||
labels = self._labels(state)
|
||||
|
||||
self._metric(
|
||||
@@ -277,7 +333,12 @@ class PrometheusMetrics:
|
||||
if state.state in IGNORED_STATES:
|
||||
self._remove_labelsets(
|
||||
entity_id,
|
||||
{"state_change", "entity_available", "last_updated_time_seconds"},
|
||||
{
|
||||
"state_change",
|
||||
"entity_available",
|
||||
"last_updated_time_seconds",
|
||||
"entity_info",
|
||||
},
|
||||
)
|
||||
else:
|
||||
domain, _ = hacore.split_entity_id(entity_id)
|
||||
@@ -306,10 +367,129 @@ class PrometheusMetrics:
|
||||
metrics_entity_id = changes["entity_id"]
|
||||
elif "disabled_by" in changes:
|
||||
metrics_entity_id = entity_id
|
||||
elif "area_id" in changes or "device_id" in changes:
|
||||
if entity_id is not None:
|
||||
self._remove_entity_info(entity_id)
|
||||
area_id = self._find_area_id(entity_id)
|
||||
if area_id is not None:
|
||||
self._add_entity_info(entity_id, area_id)
|
||||
|
||||
if metrics_entity_id:
|
||||
self._remove_labelsets(metrics_entity_id)
|
||||
|
||||
def handle_device_registry_updated(
|
||||
self, event: Event[EventDeviceRegistryUpdatedData]
|
||||
) -> None:
|
||||
"""Listen for changes of devices' area_id."""
|
||||
if event.data["action"] != "update" or "area_id" not in event.data["changes"]:
|
||||
return
|
||||
|
||||
device_id = event.data.get("device_id")
|
||||
|
||||
if device_id is None:
|
||||
return
|
||||
|
||||
_LOGGER.debug("Handling device update for %s", device_id)
|
||||
|
||||
device = self.device_registry.async_get(device_id)
|
||||
if device is None:
|
||||
return
|
||||
|
||||
area_id = device.area_id
|
||||
|
||||
for entity_id in (
|
||||
entity.entity_id
|
||||
for entity in er.async_entries_for_device(self.entity_registry, device_id)
|
||||
if entity.area_id is None and entity.entity_id in self._metrics_by_entity_id
|
||||
):
|
||||
self._remove_entity_info(entity_id)
|
||||
if area_id is not None:
|
||||
self._add_entity_info(entity_id, area_id)
|
||||
|
||||
def handle_area_registry_updated(
|
||||
self, event: Event[EventAreaRegistryUpdatedData]
|
||||
) -> None:
|
||||
"""Listen for changes to areas."""
|
||||
|
||||
area_id = event.data.get("area_id")
|
||||
|
||||
if area_id is None:
|
||||
return
|
||||
|
||||
action = event.data["action"]
|
||||
|
||||
_LOGGER.debug("Handling area update for %s (%s)", area_id, action)
|
||||
|
||||
if action in {"update", "remove"}:
|
||||
metric = self._area_info_metrics.pop(area_id, None)
|
||||
if metric is not None:
|
||||
metric_name, label_values = astuple(metric)
|
||||
self._metrics[metric_name].remove(*label_values)
|
||||
if action in {"update", "create"}:
|
||||
area = self.area_registry.async_get_area(area_id)
|
||||
if area is not None:
|
||||
self.handle_area(area)
|
||||
|
||||
def handle_area(self, area: AreaEntry) -> None:
|
||||
"""Add/update an area in Prometheus."""
|
||||
metric_name = "area_info"
|
||||
labels = {
|
||||
"area": area.id,
|
||||
"area_name": area.name,
|
||||
"floor": area.floor_id if area.floor_id is not None else "",
|
||||
}
|
||||
self._area_info_metrics[labels["area"]] = MetricNameWithLabelValues(
|
||||
metric_name, tuple(labels.values())
|
||||
)
|
||||
self._metric(
|
||||
metric_name,
|
||||
prometheus_client.Gauge,
|
||||
"Area information",
|
||||
labels,
|
||||
).set(1.0)
|
||||
|
||||
def handle_floor_registry_updated(
|
||||
self, event: Event[EventFloorRegistryUpdatedData]
|
||||
) -> None:
|
||||
"""Listen for changes to floors."""
|
||||
|
||||
floor_id = event.data.get("floor_id")
|
||||
|
||||
if floor_id is None:
|
||||
return
|
||||
|
||||
action = event.data["action"]
|
||||
|
||||
_LOGGER.debug("Handling floor update for %s (%s)", floor_id, action)
|
||||
|
||||
if action in {"update", "remove"}:
|
||||
metric = self._floor_info_metrics.pop(str(floor_id), None)
|
||||
if metric is not None:
|
||||
metric_name, label_values = astuple(metric)
|
||||
self._metrics[metric_name].remove(*label_values)
|
||||
if action in {"update", "create"}:
|
||||
floor = self.floor_registry.async_get_floor(str(floor_id))
|
||||
if floor is not None:
|
||||
self.handle_floor(floor)
|
||||
|
||||
def handle_floor(self, floor: FloorEntry) -> None:
|
||||
"""Add/update a floor in Prometheus."""
|
||||
metric_name = "floor_info"
|
||||
labels = {
|
||||
"floor": floor.floor_id,
|
||||
"floor_name": floor.name,
|
||||
"floor_level": str(floor.level) if floor.level is not None else "",
|
||||
}
|
||||
self._floor_info_metrics[labels["floor"]] = MetricNameWithLabelValues(
|
||||
metric_name, tuple(labels.values())
|
||||
)
|
||||
self._metric(
|
||||
metric_name,
|
||||
prometheus_client.Gauge,
|
||||
"Floor information",
|
||||
labels,
|
||||
).set(1.0)
|
||||
|
||||
def _remove_labelsets(
|
||||
self,
|
||||
entity_id: str,
|
||||
@@ -371,9 +551,10 @@ class PrometheusMetrics:
|
||||
registry=prometheus_client.REGISTRY,
|
||||
)
|
||||
metric = cast(_MetricBaseT, self._metrics[metric_name])
|
||||
self._metrics_by_entity_id[labels["entity"]].add(
|
||||
MetricNameWithLabelValues(metric_name, tuple(labels.values()))
|
||||
)
|
||||
if "entity" in labels:
|
||||
self._metrics_by_entity_id[labels["entity"]].add(
|
||||
MetricNameWithLabelValues(metric_name, tuple(labels.values()))
|
||||
)
|
||||
return metric.labels(**labels)
|
||||
|
||||
@staticmethod
|
||||
@@ -415,6 +596,45 @@ class PrometheusMetrics:
|
||||
)
|
||||
return labels | extra_labels
|
||||
|
||||
def _remove_entity_info(self, entity_id: str) -> None:
|
||||
"""Remove an entity-area-relation in Prometheus."""
|
||||
self._remove_labelsets(
|
||||
entity_id,
|
||||
{
|
||||
metric_set.metric_name
|
||||
for metric_set in self._metrics_by_entity_id[entity_id]
|
||||
if metric_set.metric_name != "entity_info"
|
||||
},
|
||||
)
|
||||
|
||||
def _add_entity_info(self, entity_id: str, area_id: str) -> None:
|
||||
"""Add/update an entity-area-relation in Prometheus."""
|
||||
self._metric(
|
||||
"entity_info",
|
||||
prometheus_client.Gauge,
|
||||
"The area of an entity",
|
||||
{
|
||||
"entity": entity_id,
|
||||
"area": area_id,
|
||||
},
|
||||
).set(1.0)
|
||||
|
||||
def _find_area_id(self, entity_id: str) -> str | None:
|
||||
"""Find area of entity or parent device."""
|
||||
entity = self.entity_registry.async_get(entity_id)
|
||||
|
||||
if entity is None:
|
||||
return None
|
||||
|
||||
area_id = entity.area_id
|
||||
|
||||
if area_id is None and entity.device_id is not None:
|
||||
device = self.device_registry.async_get(entity.device_id)
|
||||
if device is not None:
|
||||
area_id = device.area_id
|
||||
|
||||
return area_id
|
||||
|
||||
def _battery_metric(self, state: State) -> None:
|
||||
if (battery_level := state.attributes.get(ATTR_BATTERY_LEVEL)) is None:
|
||||
return
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""The tests for the Prometheus exporter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
from http import HTTPStatus
|
||||
from typing import Any, Self
|
||||
from typing import Any
|
||||
from unittest import mock
|
||||
|
||||
from freezegun import freeze_time
|
||||
@@ -92,10 +94,16 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
floor_registry as fr,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
PROMETHEUS_PATH = "homeassistant.components.prometheus"
|
||||
@@ -126,7 +134,7 @@ class EntityMetric:
|
||||
assert labelname in self.labels
|
||||
assert self.labels[labelname] != ""
|
||||
|
||||
def withValue(self, value: float) -> Self:
|
||||
def withValue(self, value: float) -> EntityMetricWithValue:
|
||||
"""Return a metric with value."""
|
||||
return EntityMetricWithValue(self, value)
|
||||
|
||||
@@ -847,7 +855,7 @@ async def test_climate_mode(
|
||||
climate_entities: dict[str, er.RegistryEntry | dict[str, Any]],
|
||||
) -> None:
|
||||
"""Test prometheus metrics for climate mode enum."""
|
||||
data = {**climate_entities}
|
||||
data: dict[str, Any] = {**climate_entities}
|
||||
|
||||
# Set climate_2 to a specific HVAC mode from its available modes
|
||||
set_state_with_entry(
|
||||
@@ -2961,3 +2969,295 @@ async def test_filtered_denylist(
|
||||
was_called = mock_client.labels.call_count == 1
|
||||
assert test.should_pass == was_called
|
||||
mock_client.labels.reset_mock()
|
||||
|
||||
|
||||
class InfoMetric(EntityMetric):
|
||||
"""Represents a Prometheus info metric."""
|
||||
|
||||
@classmethod
|
||||
def required_labels(cls) -> list[str]:
|
||||
"""No required labels for info metrics."""
|
||||
return []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_floor_metric(
|
||||
hass: HomeAssistant,
|
||||
floor_registry: fr.FloorRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test floor metric."""
|
||||
|
||||
# create a floor
|
||||
floor = floor_registry.async_create("Floor", level=1)
|
||||
floor_metric = InfoMetric(
|
||||
metric_name="floor_info",
|
||||
floor="floor",
|
||||
floor_level="1",
|
||||
floor_name="Floor",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
floor_metric.assert_in_metrics(body)
|
||||
|
||||
# update floor
|
||||
floor_registry.async_update(floor.floor_id, level=99, name="Updated")
|
||||
updated_metric = InfoMetric(
|
||||
metric_name="floor_info",
|
||||
floor="floor",
|
||||
floor_level="99",
|
||||
floor_name="Updated",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
floor_metric.assert_not_in_metrics(body)
|
||||
updated_metric.assert_in_metrics(body)
|
||||
|
||||
# delete floor
|
||||
floor_registry.async_delete(floor.floor_id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
floor_metric.assert_not_in_metrics(body)
|
||||
updated_metric.assert_not_in_metrics(body)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_area_metric(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test area metric."""
|
||||
# create an area
|
||||
area = area_registry.async_create("Area")
|
||||
area_metric = InfoMetric(
|
||||
metric_name="area_info",
|
||||
area="area",
|
||||
area_name="Area",
|
||||
floor="",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
area_metric.assert_in_metrics(body)
|
||||
|
||||
# update area
|
||||
area_registry.async_update(area.id, name="Updated")
|
||||
updated_metric = InfoMetric(
|
||||
metric_name="area_info",
|
||||
area="area",
|
||||
area_name="Updated",
|
||||
floor="",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
area_metric.assert_not_in_metrics(body)
|
||||
updated_metric.assert_in_metrics(body)
|
||||
|
||||
# delete area
|
||||
area_registry.async_delete(area.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
area_metric.assert_not_in_metrics(body)
|
||||
updated_metric.assert_not_in_metrics(body)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_delete_floor_of_area(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
floor_registry: fr.FloorRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test entity/area correlation."""
|
||||
|
||||
# create floor and area
|
||||
floor = floor_registry.async_create("Floor", level=1)
|
||||
area = area_registry.async_create("Area", floor_id=floor.floor_id)
|
||||
metric = InfoMetric(
|
||||
metric_name="area_info",
|
||||
area="area",
|
||||
area_name="Area",
|
||||
floor="floor",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
metric.assert_in_metrics(body)
|
||||
|
||||
# delete floor
|
||||
floor_registry.async_delete(floor.floor_id)
|
||||
updated = area_registry.async_get_area(area.id)
|
||||
assert updated is not None
|
||||
updated_metric = InfoMetric(
|
||||
metric_name="area_info",
|
||||
area="area",
|
||||
area_name="Area",
|
||||
floor="",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
metric.assert_not_in_metrics(body)
|
||||
updated_metric.assert_in_metrics(body)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_area_in_entity(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
sensor_entities: dict[str, er.RegistryEntry],
|
||||
) -> None:
|
||||
"""Test entity/area correlation."""
|
||||
|
||||
# link an entity to an area
|
||||
sensor = sensor_entities["sensor_1"]
|
||||
area_1 = area_registry.async_create("Area 1")
|
||||
metric_1 = InfoMetric(
|
||||
metric_name="entity_info", entity="sensor.outside_temperature", area="area_1"
|
||||
)
|
||||
entity_registry.async_update_entity(sensor.entity_id, area_id=area_1.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
metric_1.assert_in_metrics(body)
|
||||
|
||||
# link entity to another area
|
||||
area_2 = area_registry.async_create("Area 2")
|
||||
metric_2 = InfoMetric(
|
||||
metric_name="entity_info", entity="sensor.outside_temperature", area="area_2"
|
||||
)
|
||||
entity_registry.async_update_entity(sensor.entity_id, area_id=area_2.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
metric_1.assert_not_in_metrics(body)
|
||||
metric_2.assert_in_metrics(body)
|
||||
|
||||
# delete current area
|
||||
area_registry.async_delete(area_2.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
metric_1.assert_not_in_metrics(body)
|
||||
metric_2.assert_not_in_metrics(body)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_area_in_device(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
sensor_entities: dict[str, er.RegistryEntry],
|
||||
) -> None:
|
||||
"""Test entity/device/area correlation."""
|
||||
|
||||
# create a device
|
||||
config_entry = MockConfigEntry()
|
||||
config_entry.add_to_hass(hass)
|
||||
device = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={("prometheus", "test-device")},
|
||||
)
|
||||
|
||||
# link entity to device
|
||||
sensor = sensor_entities["sensor_1"]
|
||||
entity_registry.async_update_entity(sensor.entity_id, device_id=device.id)
|
||||
|
||||
# create areas
|
||||
entity_area = area_registry.async_create("Entity Area")
|
||||
entity_area_metric = InfoMetric(
|
||||
metric_name="entity_info",
|
||||
entity="sensor.outside_temperature",
|
||||
area="entity_area",
|
||||
)
|
||||
device_area = area_registry.async_create("Device Area")
|
||||
device_area_metric = InfoMetric(
|
||||
metric_name="entity_info",
|
||||
entity="sensor.outside_temperature",
|
||||
area="device_area",
|
||||
)
|
||||
|
||||
# no area yet
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
entity_area_metric.assert_not_in_metrics(body)
|
||||
device_area_metric.assert_not_in_metrics(body)
|
||||
|
||||
# set device area
|
||||
device_registry.async_update_device(device.id, area_id=device_area.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
entity_area_metric.assert_not_in_metrics(body)
|
||||
device_area_metric.assert_in_metrics(body)
|
||||
|
||||
# set entity area
|
||||
entity_registry.async_update_entity(sensor.entity_id, area_id=entity_area.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
entity_area_metric.assert_in_metrics(body)
|
||||
device_area_metric.assert_not_in_metrics(body)
|
||||
|
||||
# unset entity area
|
||||
entity_registry.async_update_entity(sensor.entity_id, area_id=None)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
entity_area_metric.assert_not_in_metrics(body)
|
||||
device_area_metric.assert_in_metrics(body)
|
||||
|
||||
# remove device
|
||||
device_registry.async_remove_device(device.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
entity_area_metric.assert_not_in_metrics(body)
|
||||
device_area_metric.assert_not_in_metrics(body)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("namespace", [""])
|
||||
async def test_area_in_entity_on_entity_id_update(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
client: ClientSessionGenerator,
|
||||
sensor_entities: dict[str, er.RegistryEntry],
|
||||
) -> None:
|
||||
"""Test simultaneous update of entity_id and area_id."""
|
||||
|
||||
# link an entity to an area
|
||||
sensor = sensor_entities["sensor_1"]
|
||||
area_1 = area_registry.async_create("Area 1")
|
||||
original_metric = InfoMetric(
|
||||
metric_name="entity_info", entity="sensor.outside_temperature", area="area_1"
|
||||
)
|
||||
entity_registry.async_update_entity(sensor.entity_id, area_id=area_1.id)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
original_metric.assert_in_metrics(body)
|
||||
|
||||
# link entity to another area and update entity_id
|
||||
area_2 = area_registry.async_create("Area 2")
|
||||
updated_metric_with_old_entity_id = InfoMetric(
|
||||
metric_name="entity_info",
|
||||
entity="sensor.outside_temperature",
|
||||
area="area_2",
|
||||
)
|
||||
updated_metric_with_new_entity_id = InfoMetric(
|
||||
metric_name="entity_info",
|
||||
entity="sensor.outside_temperature_updated",
|
||||
area="area_2",
|
||||
)
|
||||
updated_sensor = entity_registry.async_update_entity(
|
||||
sensor.entity_id,
|
||||
area_id=area_2.id,
|
||||
new_entity_id="sensor.outside_temperature_updated",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
original_metric.assert_not_in_metrics(body)
|
||||
updated_metric_with_old_entity_id.assert_not_in_metrics(body)
|
||||
updated_metric_with_new_entity_id.assert_not_in_metrics(body)
|
||||
|
||||
set_state_with_entry(hass, updated_sensor, 10)
|
||||
await hass.async_block_till_done()
|
||||
body = await generate_latest_metrics(client)
|
||||
original_metric.assert_not_in_metrics(body)
|
||||
updated_metric_with_old_entity_id.assert_not_in_metrics(body)
|
||||
updated_metric_with_new_entity_id.assert_in_metrics(body)
|
||||
|
||||
Reference in New Issue
Block a user