mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
Introduce recorder.get_statistics service (#142602)
Co-authored-by: abmantis <amfcalt@gmail.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
parent
161b62d8fa
commit
c023f610dd
@ -11,6 +11,9 @@
|
|||||||
},
|
},
|
||||||
"enable": {
|
"enable": {
|
||||||
"service": "mdi:database"
|
"service": "mdi:database"
|
||||||
|
},
|
||||||
|
"get_statistics": {
|
||||||
|
"service": "mdi:chart-bar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,13 @@ from typing import cast
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import ATTR_ENTITY_ID
|
from homeassistant.const import ATTR_ENTITY_ID
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import (
|
||||||
|
HomeAssistant,
|
||||||
|
ServiceCall,
|
||||||
|
ServiceResponse,
|
||||||
|
SupportsResponse,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entityfilter import generate_filter
|
from homeassistant.helpers.entityfilter import generate_filter
|
||||||
from homeassistant.helpers.service import (
|
from homeassistant.helpers.service import (
|
||||||
@ -16,15 +22,18 @@ from homeassistant.helpers.service import (
|
|||||||
async_register_admin_service,
|
async_register_admin_service,
|
||||||
)
|
)
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
from homeassistant.util.json import JsonArrayType, JsonObjectType
|
||||||
|
|
||||||
from .const import ATTR_APPLY_FILTER, ATTR_KEEP_DAYS, ATTR_REPACK, DOMAIN
|
from .const import ATTR_APPLY_FILTER, ATTR_KEEP_DAYS, ATTR_REPACK, DOMAIN
|
||||||
from .core import Recorder
|
from .core import Recorder
|
||||||
|
from .statistics import statistics_during_period
|
||||||
from .tasks import PurgeEntitiesTask, PurgeTask
|
from .tasks import PurgeEntitiesTask, PurgeTask
|
||||||
|
|
||||||
SERVICE_PURGE = "purge"
|
SERVICE_PURGE = "purge"
|
||||||
SERVICE_PURGE_ENTITIES = "purge_entities"
|
SERVICE_PURGE_ENTITIES = "purge_entities"
|
||||||
SERVICE_ENABLE = "enable"
|
SERVICE_ENABLE = "enable"
|
||||||
SERVICE_DISABLE = "disable"
|
SERVICE_DISABLE = "disable"
|
||||||
|
SERVICE_GET_STATISTICS = "get_statistics"
|
||||||
|
|
||||||
SERVICE_PURGE_SCHEMA = vol.Schema(
|
SERVICE_PURGE_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -63,6 +72,20 @@ SERVICE_PURGE_ENTITIES_SCHEMA = vol.All(
|
|||||||
SERVICE_ENABLE_SCHEMA = vol.Schema({})
|
SERVICE_ENABLE_SCHEMA = vol.Schema({})
|
||||||
SERVICE_DISABLE_SCHEMA = vol.Schema({})
|
SERVICE_DISABLE_SCHEMA = vol.Schema({})
|
||||||
|
|
||||||
|
SERVICE_GET_STATISTICS_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("start_time"): cv.datetime,
|
||||||
|
vol.Optional("end_time"): cv.datetime,
|
||||||
|
vol.Required("statistic_ids"): vol.All(cv.ensure_list, [cv.string]),
|
||||||
|
vol.Required("period"): vol.In(["5minute", "hour", "day", "week", "month"]),
|
||||||
|
vol.Required("types"): vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[vol.In(["change", "last_reset", "max", "mean", "min", "state", "sum"])],
|
||||||
|
),
|
||||||
|
vol.Optional("units"): vol.Schema({cv.string: cv.string}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_register_purge_service(hass: HomeAssistant, instance: Recorder) -> None:
|
def _async_register_purge_service(hass: HomeAssistant, instance: Recorder) -> None:
|
||||||
@ -135,6 +158,79 @@ def _async_register_disable_service(hass: HomeAssistant, instance: Recorder) ->
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_register_get_statistics_service(
|
||||||
|
hass: HomeAssistant, instance: Recorder
|
||||||
|
) -> None:
|
||||||
|
async def async_handle_get_statistics_service(
|
||||||
|
service: ServiceCall,
|
||||||
|
) -> ServiceResponse:
|
||||||
|
"""Handle calls to the get_statistics service."""
|
||||||
|
start_time = dt_util.as_utc(service.data["start_time"])
|
||||||
|
end_time = (
|
||||||
|
dt_util.as_utc(service.data["end_time"])
|
||||||
|
if "end_time" in service.data
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
statistic_ids = service.data["statistic_ids"]
|
||||||
|
types = service.data["types"]
|
||||||
|
period = service.data["period"]
|
||||||
|
units = service.data.get("units")
|
||||||
|
|
||||||
|
result = await instance.async_add_executor_job(
|
||||||
|
statistics_during_period,
|
||||||
|
hass,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
statistic_ids,
|
||||||
|
period,
|
||||||
|
units,
|
||||||
|
types,
|
||||||
|
)
|
||||||
|
|
||||||
|
formatted_result: JsonObjectType = {}
|
||||||
|
for statistic_id, statistic_rows in result.items():
|
||||||
|
formatted_statistic_rows: JsonArrayType = []
|
||||||
|
|
||||||
|
for row in statistic_rows:
|
||||||
|
formatted_row: JsonObjectType = {
|
||||||
|
"start": dt_util.utc_from_timestamp(row["start"]).isoformat(),
|
||||||
|
"end": dt_util.utc_from_timestamp(row["end"]).isoformat(),
|
||||||
|
}
|
||||||
|
if (last_reset := row.get("last_reset")) is not None:
|
||||||
|
formatted_row["last_reset"] = dt_util.utc_from_timestamp(
|
||||||
|
last_reset
|
||||||
|
).isoformat()
|
||||||
|
if (state := row.get("state")) is not None:
|
||||||
|
formatted_row["state"] = state
|
||||||
|
if (sum_value := row.get("sum")) is not None:
|
||||||
|
formatted_row["sum"] = sum_value
|
||||||
|
if (min_value := row.get("min")) is not None:
|
||||||
|
formatted_row["min"] = min_value
|
||||||
|
if (max_value := row.get("max")) is not None:
|
||||||
|
formatted_row["max"] = max_value
|
||||||
|
if (mean := row.get("mean")) is not None:
|
||||||
|
formatted_row["mean"] = mean
|
||||||
|
if (change := row.get("change")) is not None:
|
||||||
|
formatted_row["change"] = change
|
||||||
|
|
||||||
|
formatted_statistic_rows.append(formatted_row)
|
||||||
|
|
||||||
|
formatted_result[statistic_id] = formatted_statistic_rows
|
||||||
|
|
||||||
|
return {"statistics": formatted_result}
|
||||||
|
|
||||||
|
async_register_admin_service(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_STATISTICS,
|
||||||
|
async_handle_get_statistics_service,
|
||||||
|
schema=SERVICE_GET_STATISTICS_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_register_services(hass: HomeAssistant, instance: Recorder) -> None:
|
def async_register_services(hass: HomeAssistant, instance: Recorder) -> None:
|
||||||
"""Register recorder services."""
|
"""Register recorder services."""
|
||||||
@ -142,3 +238,4 @@ def async_register_services(hass: HomeAssistant, instance: Recorder) -> None:
|
|||||||
_async_register_purge_entities_service(hass, instance)
|
_async_register_purge_entities_service(hass, instance)
|
||||||
_async_register_enable_service(hass, instance)
|
_async_register_enable_service(hass, instance)
|
||||||
_async_register_disable_service(hass, instance)
|
_async_register_disable_service(hass, instance)
|
||||||
|
_async_register_get_statistics_service(hass, instance)
|
||||||
|
@ -48,3 +48,63 @@ purge_entities:
|
|||||||
|
|
||||||
disable:
|
disable:
|
||||||
enable:
|
enable:
|
||||||
|
|
||||||
|
get_statistics:
|
||||||
|
fields:
|
||||||
|
start_time:
|
||||||
|
required: true
|
||||||
|
example: "2025-01-01 00:00:00"
|
||||||
|
selector:
|
||||||
|
datetime:
|
||||||
|
|
||||||
|
end_time:
|
||||||
|
required: false
|
||||||
|
example: "2025-01-02 00:00:00"
|
||||||
|
selector:
|
||||||
|
datetime:
|
||||||
|
|
||||||
|
statistic_ids:
|
||||||
|
required: true
|
||||||
|
example:
|
||||||
|
- sensor.energy_consumption
|
||||||
|
- sensor.temperature
|
||||||
|
selector:
|
||||||
|
entity:
|
||||||
|
multiple: true
|
||||||
|
|
||||||
|
period:
|
||||||
|
required: true
|
||||||
|
example: "hour"
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- "5minute"
|
||||||
|
- "hour"
|
||||||
|
- "day"
|
||||||
|
- "week"
|
||||||
|
- "month"
|
||||||
|
|
||||||
|
types:
|
||||||
|
required: true
|
||||||
|
example:
|
||||||
|
- "mean"
|
||||||
|
- "sum"
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- "change"
|
||||||
|
- "last_reset"
|
||||||
|
- "max"
|
||||||
|
- "mean"
|
||||||
|
- "min"
|
||||||
|
- "state"
|
||||||
|
- "sum"
|
||||||
|
multiple: true
|
||||||
|
|
||||||
|
units:
|
||||||
|
required: false
|
||||||
|
example:
|
||||||
|
energy: "kWh"
|
||||||
|
temperature: "°C"
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
@ -66,6 +66,36 @@
|
|||||||
"enable": {
|
"enable": {
|
||||||
"name": "[%key:common::action::enable%]",
|
"name": "[%key:common::action::enable%]",
|
||||||
"description": "Starts the recording of events and state changes."
|
"description": "Starts the recording of events and state changes."
|
||||||
|
},
|
||||||
|
"get_statistics": {
|
||||||
|
"name": "Get statistics",
|
||||||
|
"description": "Retrieves statistics data for entities within a specific time period.",
|
||||||
|
"fields": {
|
||||||
|
"end_time": {
|
||||||
|
"name": "End time",
|
||||||
|
"description": "The end time for the statistics query. If omitted, returns all statistics from start time onward."
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"name": "Period",
|
||||||
|
"description": "The time period to group statistics by."
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "Start time",
|
||||||
|
"description": "The start time for the statistics query."
|
||||||
|
},
|
||||||
|
"statistic_ids": {
|
||||||
|
"name": "Statistic IDs",
|
||||||
|
"description": "The entity IDs or statistic IDs to return statistics for."
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"name": "Types",
|
||||||
|
"description": "The types of statistics values to return."
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"name": "Units",
|
||||||
|
"description": "Optional unit conversion mapping."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import re
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import ANY, Mock, patch
|
from unittest.mock import ANY, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import exceptions
|
||||||
from homeassistant.components import recorder
|
from homeassistant.components import recorder
|
||||||
from homeassistant.components.recorder import Recorder, history, statistics
|
from homeassistant.components.recorder import Recorder, history, statistics
|
||||||
from homeassistant.components.recorder.db_schema import StatisticsShortTerm
|
from homeassistant.components.recorder.db_schema import StatisticsShortTerm
|
||||||
@ -40,7 +43,7 @@ from homeassistant.components.recorder.table_managers.statistics_meta import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.recorder.util import session_scope
|
from homeassistant.components.recorder.util import session_scope
|
||||||
from homeassistant.components.sensor import UNIT_CONVERTERS
|
from homeassistant.components.sensor import UNIT_CONVERTERS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import Context, HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -56,7 +59,7 @@ from .common import (
|
|||||||
statistics_during_period,
|
statistics_during_period,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import MockPlatform, mock_platform
|
from tests.common import MockPlatform, MockUser, mock_platform
|
||||||
from tests.typing import RecorderInstanceContextManager, WebSocketGenerator
|
from tests.typing import RecorderInstanceContextManager, WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
@ -3421,3 +3424,319 @@ async def test_recorder_platform_with_partial_statistics_support(
|
|||||||
|
|
||||||
for meth in supported_methods:
|
for meth in supported_methods:
|
||||||
getattr(recorder_platform, meth).assert_called_once()
|
getattr(recorder_platform, meth).assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_args", "expected_result"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"period": "hour",
|
||||||
|
"statistic_ids": ["sensor.i_dont_exist"],
|
||||||
|
"types": ["change", "last_reset", "max", "mean", "min", "state", "sum"],
|
||||||
|
},
|
||||||
|
{"statistics": {}},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"period": "hour",
|
||||||
|
"statistic_ids": [
|
||||||
|
"sensor.total_energy_import1",
|
||||||
|
"sensor.total_energy_import2",
|
||||||
|
],
|
||||||
|
"types": ["change", "last_reset", "max", "mean", "min", "state", "sum"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"statistics": {
|
||||||
|
"sensor.total_energy_import1": [
|
||||||
|
{
|
||||||
|
"last_reset": "2021-12-31T22:00:00+00:00",
|
||||||
|
"change": 2.0,
|
||||||
|
"end": "2023-05-08T08:00:00+00:00",
|
||||||
|
"start": "2023-05-08T07:00:00+00:00",
|
||||||
|
"state": 0.0,
|
||||||
|
"sum": 2.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 10.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 1.0,
|
||||||
|
"end": "2023-05-08T09:00:00+00:00",
|
||||||
|
"start": "2023-05-08T08:00:00+00:00",
|
||||||
|
"state": 1.0,
|
||||||
|
"sum": 3.0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 11.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 2.0,
|
||||||
|
"end": "2023-05-08T10:00:00+00:00",
|
||||||
|
"start": "2023-05-08T09:00:00+00:00",
|
||||||
|
"state": 2.0,
|
||||||
|
"sum": 5.0,
|
||||||
|
"min": 2.0,
|
||||||
|
"max": 12.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 3.0,
|
||||||
|
"end": "2023-05-08T11:00:00+00:00",
|
||||||
|
"start": "2023-05-08T10:00:00+00:00",
|
||||||
|
"state": 3.0,
|
||||||
|
"sum": 8.0,
|
||||||
|
"min": 3.0,
|
||||||
|
"max": 13.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sensor.total_energy_import2": [
|
||||||
|
{
|
||||||
|
"last_reset": "2021-12-31T22:00:00+00:00",
|
||||||
|
"change": 2.0,
|
||||||
|
"end": "2023-05-08T08:00:00+00:00",
|
||||||
|
"start": "2023-05-08T07:00:00+00:00",
|
||||||
|
"state": 0.0,
|
||||||
|
"sum": 2.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 10.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 1.0,
|
||||||
|
"end": "2023-05-08T09:00:00+00:00",
|
||||||
|
"start": "2023-05-08T08:00:00+00:00",
|
||||||
|
"state": 1.0,
|
||||||
|
"sum": 3.0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 11.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 2.0,
|
||||||
|
"end": "2023-05-08T10:00:00+00:00",
|
||||||
|
"start": "2023-05-08T09:00:00+00:00",
|
||||||
|
"state": 2.0,
|
||||||
|
"sum": 5.0,
|
||||||
|
"min": 2.0,
|
||||||
|
"max": 12.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"change": 3.0,
|
||||||
|
"end": "2023-05-08T11:00:00+00:00",
|
||||||
|
"start": "2023-05-08T10:00:00+00:00",
|
||||||
|
"state": 3.0,
|
||||||
|
"sum": 8.0,
|
||||||
|
"min": 3.0,
|
||||||
|
"max": 13.0,
|
||||||
|
"mean": 1.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"period": "day",
|
||||||
|
"statistic_ids": [
|
||||||
|
"sensor.total_energy_import1",
|
||||||
|
"sensor.total_energy_import2",
|
||||||
|
],
|
||||||
|
"types": ["sum"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"statistics": {
|
||||||
|
"sensor.total_energy_import1": [
|
||||||
|
{
|
||||||
|
"start": "2023-05-08T07:00:00+00:00",
|
||||||
|
"end": "2023-05-09T07:00:00+00:00",
|
||||||
|
"sum": 8.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sensor.total_energy_import2": [
|
||||||
|
{
|
||||||
|
"start": "2023-05-08T07:00:00+00:00",
|
||||||
|
"end": "2023-05-09T07:00:00+00:00",
|
||||||
|
"sum": 8.0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"end_time": "2023-05-08 08:00:00Z",
|
||||||
|
"period": "hour",
|
||||||
|
"types": ["change", "sum"],
|
||||||
|
"statistic_ids": ["sensor.total_energy_import1"],
|
||||||
|
"units": {"energy": "Wh"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"statistics": {
|
||||||
|
"sensor.total_energy_import1": [
|
||||||
|
{
|
||||||
|
"start": "2023-05-08T07:00:00+00:00",
|
||||||
|
"end": "2023-05-08T08:00:00+00:00",
|
||||||
|
"change": 2000.0,
|
||||||
|
"sum": 2000.0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("recorder_mock")
|
||||||
|
async def test_get_statistics_service(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_read_only_user: MockUser,
|
||||||
|
service_args: dict[str, Any],
|
||||||
|
expected_result: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test the get_statistics service."""
|
||||||
|
period1 = dt_util.as_utc(dt_util.parse_datetime("2023-05-08 00:00:00"))
|
||||||
|
period2 = dt_util.as_utc(dt_util.parse_datetime("2023-05-08 01:00:00"))
|
||||||
|
period3 = dt_util.as_utc(dt_util.parse_datetime("2023-05-08 02:00:00"))
|
||||||
|
period4 = dt_util.as_utc(dt_util.parse_datetime("2023-05-08 03:00:00"))
|
||||||
|
|
||||||
|
last_reset = dt_util.parse_datetime("2022-01-01T00:00:00+02:00")
|
||||||
|
external_statistics = (
|
||||||
|
{
|
||||||
|
"start": period1,
|
||||||
|
"state": 0,
|
||||||
|
"sum": 2,
|
||||||
|
"min": 0,
|
||||||
|
"max": 10,
|
||||||
|
"mean": 1,
|
||||||
|
"last_reset": last_reset,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": period2,
|
||||||
|
"state": 1,
|
||||||
|
"sum": 3,
|
||||||
|
"min": 1,
|
||||||
|
"max": 11,
|
||||||
|
"mean": 1,
|
||||||
|
"last_reset": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": period3,
|
||||||
|
"state": 2,
|
||||||
|
"sum": 5,
|
||||||
|
"min": 2,
|
||||||
|
"max": 12,
|
||||||
|
"mean": 1,
|
||||||
|
"last_reset": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"start": period4,
|
||||||
|
"state": 3,
|
||||||
|
"sum": 8,
|
||||||
|
"min": 3,
|
||||||
|
"max": 13,
|
||||||
|
"mean": 1,
|
||||||
|
"last_reset": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
external_metadata1 = {
|
||||||
|
"has_mean": True,
|
||||||
|
"has_sum": True,
|
||||||
|
"name": "Total imported energy",
|
||||||
|
"source": "recorder",
|
||||||
|
"statistic_id": "sensor.total_energy_import1",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
}
|
||||||
|
external_metadata2 = {
|
||||||
|
"has_mean": True,
|
||||||
|
"has_sum": True,
|
||||||
|
"name": "Total imported energy",
|
||||||
|
"source": "recorder",
|
||||||
|
"statistic_id": "sensor.total_energy_import2",
|
||||||
|
"unit_of_measurement": "kWh",
|
||||||
|
}
|
||||||
|
async_import_statistics(hass, external_metadata1, external_statistics)
|
||||||
|
async_import_statistics(hass, external_metadata2, external_statistics)
|
||||||
|
|
||||||
|
await async_setup_component(hass, "sensor", {})
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
result = await hass.services.async_call(
|
||||||
|
"recorder", "get_statistics", service_args, return_response=True, blocking=True
|
||||||
|
)
|
||||||
|
assert result == expected_result
|
||||||
|
|
||||||
|
with pytest.raises(exceptions.Unauthorized):
|
||||||
|
result = await hass.services.async_call(
|
||||||
|
"recorder",
|
||||||
|
"get_statistics",
|
||||||
|
service_args,
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
context=Context(user_id=hass_read_only_user.id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_args", "missing_key"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"period": "hour",
|
||||||
|
"statistic_ids": ["sensor.sensor"],
|
||||||
|
"types": ["change", "last_reset", "max", "mean", "min", "state", "sum"],
|
||||||
|
},
|
||||||
|
"start_time",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"period": "hour",
|
||||||
|
"types": ["change", "last_reset", "max", "mean", "min", "state", "sum"],
|
||||||
|
},
|
||||||
|
"statistic_ids",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"statistic_ids": ["sensor.sensor"],
|
||||||
|
"types": ["change", "last_reset", "max", "mean", "min", "state", "sum"],
|
||||||
|
},
|
||||||
|
"period",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"start_time": "2023-05-08 07:00:00Z",
|
||||||
|
"period": "hour",
|
||||||
|
"statistic_ids": ["sensor.sensor"],
|
||||||
|
},
|
||||||
|
"types",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("recorder_mock")
|
||||||
|
async def test_get_statistics_service_missing_mandatory_keys(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
service_args: dict[str, Any],
|
||||||
|
missing_key: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test the get_statistics service with missing mandatory keys."""
|
||||||
|
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
vol.error.MultipleInvalid,
|
||||||
|
match=re.escape(f"required key not provided @ data['{missing_key}']"),
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
"recorder",
|
||||||
|
"get_statistics",
|
||||||
|
service_args,
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user