diff --git a/homeassistant/components/recorder/websocket_api.py b/homeassistant/components/recorder/websocket_api.py index 58c362df62e..b0874d9ea2a 100644 --- a/homeassistant/components/recorder/websocket_api.py +++ b/homeassistant/components/recorder/websocket_api.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime as dt -from typing import Any, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast import voluptuous as vol @@ -44,7 +44,11 @@ from .statistics import ( statistics_during_period, validate_statistics, ) -from .util import PERIOD_SCHEMA, get_instance, resolve_period +from .util import PERIOD_SCHEMA, get_instance, resolve_period, session_scope + +if TYPE_CHECKING: + from .core import Recorder + UNIT_SCHEMA = vol.Schema( { @@ -81,6 +85,7 @@ def async_setup(hass: HomeAssistant) -> None: websocket_api.async_register_command(hass, ws_info) websocket_api.async_register_command(hass, ws_update_statistics_metadata) websocket_api.async_register_command(hass, ws_validate_statistics) + websocket_api.async_register_command(hass, ws_get_recorded_entities) def _ws_get_statistic_during_period( @@ -513,3 +518,40 @@ def ws_info( "thread_running": is_running, } connection.send_result(msg["id"], recorder_info) + + +def _get_recorded_entities( + hass: HomeAssistant, msg_id: int, instance: Recorder +) -> bytes: + """Get the list of entities being recorded.""" + with session_scope(hass=hass, read_only=True) as session: + return json_bytes( + messages.result_message( + msg_id, + { + "entity_ids": list( + instance.states_meta_manager.get_metadata_id_to_entity_id( + session + ).values() + ) + }, + ) + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "recorder/recorded_entities", + } +) +@websocket_api.async_response +async def ws_get_recorded_entities( + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any] +) -> None: + """Get the list of entities being recorded.""" + instance = get_instance(hass) + return connection.send_message( + await instance.async_add_executor_job( + _get_recorded_entities, hass, msg["id"], instance + ) + ) diff --git a/tests/components/recorder/test_websocket_api.py b/tests/components/recorder/test_websocket_api.py index 4a1410d45a4..f97c5b835b5 100644 --- a/tests/components/recorder/test_websocket_api.py +++ b/tests/components/recorder/test_websocket_api.py @@ -23,6 +23,7 @@ from homeassistant.components.recorder.statistics import ( from homeassistant.components.recorder.util import session_scope from homeassistant.components.recorder.websocket_api import UNIT_SCHEMA from homeassistant.components.sensor import UNIT_CONVERTERS +from homeassistant.const import CONF_DOMAINS, CONF_EXCLUDE from homeassistant.core import HomeAssistant from homeassistant.helpers import recorder as recorder_helper from homeassistant.setup import async_setup_component @@ -38,7 +39,7 @@ from .common import ( ) from tests.common import async_fire_time_changed -from tests.typing import WebSocketGenerator +from tests.typing import RecorderInstanceGenerator, WebSocketGenerator DISTANCE_SENSOR_FT_ATTRIBUTES = { "device_class": "distance", @@ -132,6 +133,13 @@ VOLUME_SENSOR_M3_ATTRIBUTES_TOTAL = { } +@pytest.fixture +async def mock_recorder_before_hass( + async_setup_recorder_instance: RecorderInstanceGenerator, +) -> None: + """Set up recorder.""" + + def test_converters_align_with_sensor() -> None: """Ensure UNIT_SCHEMA is aligned with sensor UNIT_CONVERTERS.""" for converter in UNIT_CONVERTERS.values(): @@ -3177,3 +3185,64 @@ async def test_adjust_sum_statistics_errors( stats = statistics_during_period(hass, zero, period="hour") assert stats != previous_stats previous_stats = stats + + +async def test_recorder_recorded_entities_no_filter( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + async_setup_recorder_instance: RecorderInstanceGenerator, +) -> None: + """Test getting the list of recorded entities without a filter.""" + await async_setup_recorder_instance(hass, {recorder.CONF_COMMIT_INTERVAL: 0}) + client = await hass_ws_client() + + await client.send_json({"id": 1, "type": "recorder/recorded_entities"}) + response = await client.receive_json() + assert response["result"] == {"entity_ids": []} + assert response["id"] == 1 + assert response["success"] + assert response["type"] == "result" + + hass.states.async_set("sensor.test", 10) + await async_wait_recording_done(hass) + + await client.send_json({"id": 2, "type": "recorder/recorded_entities"}) + response = await client.receive_json() + assert response["result"] == {"entity_ids": ["sensor.test"]} + assert response["id"] == 2 + assert response["success"] + assert response["type"] == "result" + + +async def test_recorder_recorded_entities_with_filter( + hass: HomeAssistant, + hass_ws_client: WebSocketGenerator, + async_setup_recorder_instance: RecorderInstanceGenerator, +) -> None: + """Test getting the list of recorded entities with a filter.""" + await async_setup_recorder_instance( + hass, + { + recorder.CONF_COMMIT_INTERVAL: 0, + CONF_EXCLUDE: {CONF_DOMAINS: ["sensor"]}, + }, + ) + client = await hass_ws_client() + + await client.send_json({"id": 1, "type": "recorder/recorded_entities"}) + response = await client.receive_json() + assert response["result"] == {"entity_ids": []} + assert response["id"] == 1 + assert response["success"] + assert response["type"] == "result" + + hass.states.async_set("switch.test", 10) + hass.states.async_set("sensor.test", 10) + await async_wait_recording_done(hass) + + await client.send_json({"id": 2, "type": "recorder/recorded_entities"}) + response = await client.receive_json() + assert response["result"] == {"entity_ids": ["switch.test"]} + assert response["id"] == 2 + assert response["success"] + assert response["type"] == "result"