From 2215adf5cf4337f76a55777b429a81b608b909a7 Mon Sep 17 00:00:00 2001 From: Matt Gerega Date: Tue, 27 Dec 2022 05:25:33 -0500 Subject: [PATCH] Add support for covers in Prometheus metrics (#83763) fixes undefined --- .../components/prometheus/__init__.py | 37 +++++ tests/components/prometheus/test_init.py | 136 ++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index f6bd4430623..71d668d93dd 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -16,6 +16,7 @@ from homeassistant.components.climate import ( ATTR_TARGET_TEMP_LOW, HVACAction, ) +from homeassistant.components.cover import ATTR_POSITION, ATTR_TILT_POSITION from homeassistant.components.http import HomeAssistantView from homeassistant.components.humidifier import ATTR_AVAILABLE_MODES, ATTR_HUMIDITY from homeassistant.const import ( @@ -28,7 +29,11 @@ from homeassistant.const import ( CONTENT_TYPE_TEXT_PLAIN, EVENT_STATE_CHANGED, PERCENTAGE, + STATE_CLOSED, + STATE_CLOSING, STATE_ON, + STATE_OPEN, + STATE_OPENING, STATE_UNAVAILABLE, STATE_UNKNOWN, UnitOfTemperature, @@ -371,6 +376,38 @@ class PrometheusMetrics: value = self.state_as_number(state) metric.labels(**self._labels(state)).set(value) + def _handle_cover(self, state): + metric = self._metric( + "cover_state", + self.prometheus_cli.Gauge, + "State of the cover (0/1)", + ["state"], + ) + + cover_states = [STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING] + for cover_state in cover_states: + metric.labels(**dict(self._labels(state), state=cover_state)).set( + float(cover_state == state.state) + ) + + position = state.attributes.get(ATTR_POSITION) + if position is not None: + position_metric = self._metric( + "cover_position", + self.prometheus_cli.Gauge, + "Position of the cover (0-100)", + ) + position_metric.labels(**self._labels(state)).set(float(position)) + + tilt_position = state.attributes.get(ATTR_TILT_POSITION) + if tilt_position is not None: + tilt_position_metric = self._metric( + "cover_tilt_position", + self.prometheus_cli.Gauge, + "Tilt Position of the cover (0-100)", + ) + tilt_position_metric.labels(**self._labels(state)).set(float(tilt_position)) + def _handle_light(self, state): metric = self._metric( "light_brightness_percent", diff --git a/tests/components/prometheus/test_init.py b/tests/components/prometheus/test_init.py index febbc72ec31..e49e0eb779d 100644 --- a/tests/components/prometheus/test_init.py +++ b/tests/components/prometheus/test_init.py @@ -11,6 +11,7 @@ from homeassistant.components import ( binary_sensor, climate, counter, + cover, device_tracker, humidifier, input_boolean, @@ -44,11 +45,15 @@ from homeassistant.const import ( ENERGY_KILO_WATT_HOUR, EVENT_STATE_CHANGED, PERCENTAGE, + STATE_CLOSED, + STATE_CLOSING, STATE_HOME, STATE_LOCKED, STATE_NOT_HOME, STATE_OFF, STATE_ON, + STATE_OPEN, + STATE_OPENING, STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -445,6 +450,65 @@ async def test_lock(client, lock_entities): ) +@pytest.mark.parametrize("namespace", [""]) +async def test_cover(client, cover_entities): + """Test prometheus metrics for cover.""" + data = {**cover_entities} + body = await generate_latest_metrics(client) + + open_covers = ["cover_open", "cover_position", "cover_tilt_position"] + for testcover in data: + open_metric = ( + f'cover_state{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}",' + f'state="open"}} {1.0 if cover_entities[testcover].unique_id in open_covers else 0.0}' + ) + assert open_metric in body + + closed_metric = ( + f'cover_state{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}",' + f'state="closed"}} {1.0 if cover_entities[testcover].unique_id == "cover_closed" else 0.0}' + ) + assert closed_metric in body + + opening_metric = ( + f'cover_state{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}",' + f'state="opening"}} {1.0 if cover_entities[testcover].unique_id == "cover_opening" else 0.0}' + ) + assert opening_metric in body + + closing_metric = ( + f'cover_state{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}",' + f'state="closing"}} {1.0 if cover_entities[testcover].unique_id == "cover_closing" else 0.0}' + ) + assert closing_metric in body + + if testcover == "cover_position": + position_metric = ( + f'cover_position{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}"' + f"}} 50.0" + ) + assert position_metric in body + + if testcover == "cover_tilt_position": + tilt_position_metric = ( + f'cover_tilt_position{{domain="cover",' + f'entity="{cover_entities[testcover].entity_id}",' + f'friendly_name="{cover_entities[testcover].original_name}"' + f"}} 50.0" + ) + assert tilt_position_metric in body + + @pytest.mark.parametrize("namespace", [""]) async def test_counter(client, counter_entities): """Test prometheus metrics for counter.""" @@ -1110,6 +1174,78 @@ async def lock_fixture(hass, registry): return data +@pytest.fixture(name="cover_entities") +async def cover_fixture(hass, registry): + """Simulate cover entities.""" + data = {} + cover_open = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_open", + suggested_object_id="open_shade", + original_name="Open Shade", + ) + set_state_with_entry(hass, cover_open, STATE_OPEN) + data["cover_open"] = cover_open + + cover_closed = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_closed", + suggested_object_id="closed_shade", + original_name="Closed Shade", + ) + set_state_with_entry(hass, cover_closed, STATE_CLOSED) + data["cover_closed"] = cover_closed + + cover_closing = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_closing", + suggested_object_id="closing_shade", + original_name="Closing Shade", + ) + set_state_with_entry(hass, cover_closing, STATE_CLOSING) + data["cover_closing"] = cover_closing + + cover_opening = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_opening", + suggested_object_id="opening_shade", + original_name="Opening Shade", + ) + set_state_with_entry(hass, cover_opening, STATE_OPENING) + data["cover_opening"] = cover_opening + + cover_position = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_position", + suggested_object_id="position_shade", + original_name="Position Shade", + ) + cover_position_attributes = {cover.ATTR_POSITION: 50} + set_state_with_entry(hass, cover_position, STATE_OPEN, cover_position_attributes) + data["cover_position"] = cover_position + + cover_tilt_position = registry.async_get_or_create( + domain=cover.DOMAIN, + platform="test", + unique_id="cover_tilt_position", + suggested_object_id="tilt_position_shade", + original_name="Tilt Position Shade", + ) + cover_tilt_position_attributes = {cover.ATTR_TILT_POSITION: 50} + set_state_with_entry( + hass, cover_tilt_position, STATE_OPEN, cover_tilt_position_attributes + ) + data["cover_tilt_position"] = cover_tilt_position + + await hass.async_block_till_done() + return data + + @pytest.fixture(name="input_number_entities") async def input_number_fixture(hass, registry): """Simulate input_number entities."""