Deprecate history integration's statistics API (#78056)
This commit is contained in:
parent
c528a2d2cd
commit
7937bfeedb
4 changed files with 535 additions and 440 deletions
|
@ -6,22 +6,22 @@ from datetime import datetime as dt, timedelta
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import Literal, cast
|
from typing import cast
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import frontend, websocket_api
|
from homeassistant.components import frontend, websocket_api
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.components.recorder import get_instance, history
|
from homeassistant.components.recorder import (
|
||||||
|
get_instance,
|
||||||
|
history,
|
||||||
|
websocket_api as recorder_ws,
|
||||||
|
)
|
||||||
from homeassistant.components.recorder.filters import (
|
from homeassistant.components.recorder.filters import (
|
||||||
Filters,
|
Filters,
|
||||||
sqlalchemy_filter_from_include_exclude_conf,
|
sqlalchemy_filter_from_include_exclude_conf,
|
||||||
)
|
)
|
||||||
from homeassistant.components.recorder.statistics import (
|
|
||||||
list_statistic_ids,
|
|
||||||
statistics_during_period,
|
|
||||||
)
|
|
||||||
from homeassistant.components.recorder.util import session_scope
|
from homeassistant.components.recorder.util import session_scope
|
||||||
from homeassistant.components.websocket_api import messages
|
from homeassistant.components.websocket_api import messages
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -68,23 +68,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _ws_get_statistics_during_period(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
msg_id: int,
|
|
||||||
start_time: dt,
|
|
||||||
end_time: dt | None = None,
|
|
||||||
statistic_ids: list[str] | None = None,
|
|
||||||
period: Literal["5minute", "day", "hour", "month"] = "hour",
|
|
||||||
) -> str:
|
|
||||||
"""Fetch statistics and convert them to json in the executor."""
|
|
||||||
return JSON_DUMP(
|
|
||||||
messages.result_message(
|
|
||||||
msg_id,
|
|
||||||
statistics_during_period(hass, start_time, end_time, statistic_ids, period),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required("type"): "history/statistics_during_period",
|
vol.Required("type"): "history/statistics_during_period",
|
||||||
|
@ -99,46 +82,11 @@ async def ws_get_statistics_during_period(
|
||||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle statistics websocket command."""
|
"""Handle statistics websocket command."""
|
||||||
start_time_str = msg["start_time"]
|
_LOGGER.warning(
|
||||||
end_time_str = msg.get("end_time")
|
"WS API 'history/statistics_during_period' is deprecated and will be removed in "
|
||||||
|
"Home Assistant Core 2022.12. Use 'recorder/statistics_during_period' instead"
|
||||||
if start_time := dt_util.parse_datetime(start_time_str):
|
|
||||||
start_time = dt_util.as_utc(start_time)
|
|
||||||
else:
|
|
||||||
connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time")
|
|
||||||
return
|
|
||||||
|
|
||||||
if end_time_str:
|
|
||||||
if end_time := dt_util.parse_datetime(end_time_str):
|
|
||||||
end_time = dt_util.as_utc(end_time)
|
|
||||||
else:
|
|
||||||
connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
end_time = None
|
|
||||||
|
|
||||||
connection.send_message(
|
|
||||||
await get_instance(hass).async_add_executor_job(
|
|
||||||
_ws_get_statistics_during_period,
|
|
||||||
hass,
|
|
||||||
msg["id"],
|
|
||||||
start_time,
|
|
||||||
end_time,
|
|
||||||
msg.get("statistic_ids"),
|
|
||||||
msg.get("period"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _ws_get_list_statistic_ids(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
msg_id: int,
|
|
||||||
statistic_type: Literal["mean"] | Literal["sum"] | None = None,
|
|
||||||
) -> str:
|
|
||||||
"""Fetch a list of available statistic_id and convert them to json in the executor."""
|
|
||||||
return JSON_DUMP(
|
|
||||||
messages.result_message(msg_id, list_statistic_ids(hass, None, statistic_type))
|
|
||||||
)
|
)
|
||||||
|
await recorder_ws.ws_handle_get_statistics_during_period(hass, connection, msg)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
|
@ -152,14 +100,11 @@ async def ws_get_list_statistic_ids(
|
||||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Fetch a list of available statistic_id."""
|
"""Fetch a list of available statistic_id."""
|
||||||
connection.send_message(
|
_LOGGER.warning(
|
||||||
await get_instance(hass).async_add_executor_job(
|
"WS API 'history/list_statistic_ids' is deprecated and will be removed in "
|
||||||
_ws_get_list_statistic_ids,
|
"Home Assistant Core 2022.12. Use 'recorder/list_statistic_ids' instead"
|
||||||
hass,
|
|
||||||
msg["id"],
|
|
||||||
msg.get("statistic_type"),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
await recorder_ws.ws_handle_list_statistic_ids(hass, connection, msg)
|
||||||
|
|
||||||
|
|
||||||
def _ws_get_significant_states(
|
def _ws_get_significant_states(
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
"""The Recorder websocket API."""
|
"""The Recorder websocket API."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime as dt
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.components.websocket_api import messages
|
||||||
from homeassistant.core import HomeAssistant, callback, valid_entity_id
|
from homeassistant.core import HomeAssistant, callback, valid_entity_id
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.json import JSON_DUMP
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import MAX_QUEUE_BACKLOG
|
from .const import MAX_QUEUE_BACKLOG
|
||||||
|
@ -15,6 +19,7 @@ from .statistics import (
|
||||||
async_add_external_statistics,
|
async_add_external_statistics,
|
||||||
async_import_statistics,
|
async_import_statistics,
|
||||||
list_statistic_ids,
|
list_statistic_ids,
|
||||||
|
statistics_during_period,
|
||||||
validate_statistics,
|
validate_statistics,
|
||||||
)
|
)
|
||||||
from .util import async_migration_in_progress, async_migration_is_live, get_instance
|
from .util import async_migration_in_progress, async_migration_is_live, get_instance
|
||||||
|
@ -25,15 +30,125 @@ _LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||||
@callback
|
@callback
|
||||||
def async_setup(hass: HomeAssistant) -> None:
|
def async_setup(hass: HomeAssistant) -> None:
|
||||||
"""Set up the recorder websocket API."""
|
"""Set up the recorder websocket API."""
|
||||||
websocket_api.async_register_command(hass, ws_validate_statistics)
|
|
||||||
websocket_api.async_register_command(hass, ws_clear_statistics)
|
|
||||||
websocket_api.async_register_command(hass, ws_get_statistics_metadata)
|
|
||||||
websocket_api.async_register_command(hass, ws_update_statistics_metadata)
|
|
||||||
websocket_api.async_register_command(hass, ws_info)
|
|
||||||
websocket_api.async_register_command(hass, ws_backup_start)
|
|
||||||
websocket_api.async_register_command(hass, ws_backup_end)
|
|
||||||
websocket_api.async_register_command(hass, ws_adjust_sum_statistics)
|
websocket_api.async_register_command(hass, ws_adjust_sum_statistics)
|
||||||
|
websocket_api.async_register_command(hass, ws_backup_end)
|
||||||
|
websocket_api.async_register_command(hass, ws_backup_start)
|
||||||
|
websocket_api.async_register_command(hass, ws_clear_statistics)
|
||||||
|
websocket_api.async_register_command(hass, ws_get_statistics_during_period)
|
||||||
|
websocket_api.async_register_command(hass, ws_get_statistics_metadata)
|
||||||
|
websocket_api.async_register_command(hass, ws_list_statistic_ids)
|
||||||
websocket_api.async_register_command(hass, ws_import_statistics)
|
websocket_api.async_register_command(hass, ws_import_statistics)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def _ws_get_statistics_during_period(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
msg_id: int,
|
||||||
|
start_time: dt,
|
||||||
|
end_time: dt | None = None,
|
||||||
|
statistic_ids: list[str] | None = None,
|
||||||
|
period: Literal["5minute", "day", "hour", "month"] = "hour",
|
||||||
|
) -> str:
|
||||||
|
"""Fetch statistics and convert them to json in the executor."""
|
||||||
|
return JSON_DUMP(
|
||||||
|
messages.result_message(
|
||||||
|
msg_id,
|
||||||
|
statistics_during_period(hass, start_time, end_time, statistic_ids, period),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def ws_handle_get_statistics_during_period(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
|
"""Handle statistics websocket command."""
|
||||||
|
start_time_str = msg["start_time"]
|
||||||
|
end_time_str = msg.get("end_time")
|
||||||
|
|
||||||
|
if start_time := dt_util.parse_datetime(start_time_str):
|
||||||
|
start_time = dt_util.as_utc(start_time)
|
||||||
|
else:
|
||||||
|
connection.send_error(msg["id"], "invalid_start_time", "Invalid start_time")
|
||||||
|
return
|
||||||
|
|
||||||
|
if end_time_str:
|
||||||
|
if end_time := dt_util.parse_datetime(end_time_str):
|
||||||
|
end_time = dt_util.as_utc(end_time)
|
||||||
|
else:
|
||||||
|
connection.send_error(msg["id"], "invalid_end_time", "Invalid end_time")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
end_time = None
|
||||||
|
|
||||||
|
connection.send_message(
|
||||||
|
await get_instance(hass).async_add_executor_job(
|
||||||
|
_ws_get_statistics_during_period,
|
||||||
|
hass,
|
||||||
|
msg["id"],
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
msg.get("statistic_ids"),
|
||||||
|
msg.get("period"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): "recorder/statistics_during_period",
|
||||||
|
vol.Required("start_time"): str,
|
||||||
|
vol.Optional("end_time"): str,
|
||||||
|
vol.Optional("statistic_ids"): [str],
|
||||||
|
vol.Required("period"): vol.Any("5minute", "hour", "day", "month"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def ws_get_statistics_during_period(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
|
"""Handle statistics websocket command."""
|
||||||
|
await ws_handle_get_statistics_during_period(hass, connection, msg)
|
||||||
|
|
||||||
|
|
||||||
|
def _ws_get_list_statistic_ids(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
msg_id: int,
|
||||||
|
statistic_type: Literal["mean"] | Literal["sum"] | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Fetch a list of available statistic_id and convert them to json in the executor."""
|
||||||
|
return JSON_DUMP(
|
||||||
|
messages.result_message(msg_id, list_statistic_ids(hass, None, statistic_type))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def ws_handle_list_statistic_ids(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
|
"""Fetch a list of available statistic_id."""
|
||||||
|
connection.send_message(
|
||||||
|
await get_instance(hass).async_add_executor_job(
|
||||||
|
_ws_get_list_statistic_ids,
|
||||||
|
hass,
|
||||||
|
msg["id"],
|
||||||
|
msg.get("statistic_type"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required("type"): "recorder/list_statistic_ids",
|
||||||
|
vol.Optional("statistic_type"): vol.Any("sum", "mean"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def ws_list_statistic_ids(
|
||||||
|
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
|
"""Fetch a list of available statistic_id."""
|
||||||
|
await ws_handle_list_statistic_ids(hass, connection, msg)
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
|
|
|
@ -5,24 +5,24 @@ from http import HTTPStatus
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch, sentinel
|
from unittest.mock import patch, sentinel
|
||||||
|
|
||||||
from freezegun import freeze_time
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import approx
|
|
||||||
|
|
||||||
from homeassistant.components import history
|
from homeassistant.components import history
|
||||||
from homeassistant.components.recorder.history import get_significant_states
|
from homeassistant.components.recorder.history import get_significant_states
|
||||||
from homeassistant.components.recorder.models import process_timestamp
|
from homeassistant.components.recorder.models import process_timestamp
|
||||||
|
from homeassistant.components.recorder.websocket_api import (
|
||||||
|
ws_handle_get_statistics_during_period,
|
||||||
|
ws_handle_list_statistic_ids,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE
|
from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.helpers.json import JSONEncoder
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
|
||||||
|
|
||||||
from tests.components.recorder.common import (
|
from tests.components.recorder.common import (
|
||||||
async_recorder_block_till_done,
|
async_recorder_block_till_done,
|
||||||
async_wait_recording_done,
|
async_wait_recording_done,
|
||||||
do_adhoc_statistics,
|
|
||||||
wait_recording_done,
|
wait_recording_done,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -844,51 +844,13 @@ async def test_entity_ids_limit_via_api_with_skip_initial_state(
|
||||||
assert response_json[1][0]["entity_id"] == "light.cow"
|
assert response_json[1][0]["entity_id"] == "light.cow"
|
||||||
|
|
||||||
|
|
||||||
POWER_SENSOR_ATTRIBUTES = {
|
async def test_statistics_during_period(hass, hass_ws_client, recorder_mock, caplog):
|
||||||
"device_class": "power",
|
"""Test history/statistics_during_period forwards to recorder."""
|
||||||
"state_class": "measurement",
|
|
||||||
"unit_of_measurement": "kW",
|
|
||||||
}
|
|
||||||
PRESSURE_SENSOR_ATTRIBUTES = {
|
|
||||||
"device_class": "pressure",
|
|
||||||
"state_class": "measurement",
|
|
||||||
"unit_of_measurement": "hPa",
|
|
||||||
}
|
|
||||||
TEMPERATURE_SENSOR_ATTRIBUTES = {
|
|
||||||
"device_class": "temperature",
|
|
||||||
"state_class": "measurement",
|
|
||||||
"unit_of_measurement": "°C",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"units, attributes, state, value",
|
|
||||||
[
|
|
||||||
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
|
||||||
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
|
||||||
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
|
|
||||||
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
|
|
||||||
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
|
|
||||||
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_statistics_during_period(
|
|
||||||
hass, hass_ws_client, recorder_mock, units, attributes, state, value
|
|
||||||
):
|
|
||||||
"""Test statistics_during_period."""
|
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
hass.config.units = units
|
|
||||||
await async_setup_component(hass, "history", {})
|
await async_setup_component(hass, "history", {})
|
||||||
await async_setup_component(hass, "sensor", {})
|
|
||||||
await async_recorder_block_till_done(hass)
|
|
||||||
hass.states.async_set("sensor.test", state, attributes=attributes)
|
|
||||||
await async_wait_recording_done(hass)
|
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_wait_recording_done(hass)
|
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
|
|
||||||
|
# Test the WS API works and issues a warning
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
@ -903,322 +865,53 @@ async def test_statistics_during_period(
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"] == {}
|
assert response["result"] == {}
|
||||||
|
|
||||||
await client.send_json(
|
assert (
|
||||||
{
|
"WS API 'history/statistics_during_period' is deprecated and will be removed in "
|
||||||
"id": 2,
|
"Home Assistant Core 2022.12. Use 'recorder/statistics_during_period' instead"
|
||||||
"type": "history/statistics_during_period",
|
) in caplog.text
|
||||||
"start_time": now.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
# Test the WS API forwards to recorder
|
||||||
"period": "5minute",
|
with patch(
|
||||||
}
|
"homeassistant.components.history.recorder_ws.ws_handle_get_statistics_during_period",
|
||||||
)
|
wraps=ws_handle_get_statistics_during_period,
|
||||||
response = await client.receive_json()
|
) as ws_mock:
|
||||||
assert response["success"]
|
await client.send_json(
|
||||||
assert response["result"] == {
|
|
||||||
"sensor.test": [
|
|
||||||
{
|
{
|
||||||
"statistic_id": "sensor.test",
|
"id": 2,
|
||||||
"start": now.isoformat(),
|
"type": "history/statistics_during_period",
|
||||||
"end": (now + timedelta(minutes=5)).isoformat(),
|
"start_time": now.isoformat(),
|
||||||
"mean": approx(value),
|
"end_time": now.isoformat(),
|
||||||
"min": approx(value),
|
"statistic_ids": ["sensor.test"],
|
||||||
"max": approx(value),
|
"period": "hour",
|
||||||
"last_reset": None,
|
|
||||||
"state": None,
|
|
||||||
"sum": None,
|
|
||||||
}
|
}
|
||||||
]
|
)
|
||||||
}
|
await client.receive_json()
|
||||||
|
ws_mock.assert_awaited_once()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
async def test_list_statistic_ids(hass, hass_ws_client, recorder_mock, caplog):
|
||||||
"units, attributes, state, value",
|
"""Test history/list_statistic_ids forwards to recorder."""
|
||||||
[
|
|
||||||
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
|
||||||
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
|
||||||
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
|
|
||||||
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
|
|
||||||
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
|
|
||||||
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_statistics_during_period_in_the_past(
|
|
||||||
hass, hass_ws_client, recorder_mock, units, attributes, state, value
|
|
||||||
):
|
|
||||||
"""Test statistics_during_period in the past."""
|
|
||||||
hass.config.set_time_zone("UTC")
|
|
||||||
now = dt_util.utcnow().replace()
|
|
||||||
|
|
||||||
hass.config.units = units
|
|
||||||
await async_setup_component(hass, "history", {})
|
await async_setup_component(hass, "history", {})
|
||||||
await async_setup_component(hass, "sensor", {})
|
|
||||||
await async_recorder_block_till_done(hass)
|
|
||||||
|
|
||||||
past = now - timedelta(days=3)
|
|
||||||
|
|
||||||
with freeze_time(past):
|
|
||||||
hass.states.async_set("sensor.test", state, attributes=attributes)
|
|
||||||
await async_wait_recording_done(hass)
|
|
||||||
|
|
||||||
sensor_state = hass.states.get("sensor.test")
|
|
||||||
assert sensor_state.last_updated == past
|
|
||||||
|
|
||||||
stats_top_of_hour = past.replace(minute=0, second=0, microsecond=0)
|
|
||||||
stats_start = past.replace(minute=55)
|
|
||||||
do_adhoc_statistics(hass, start=stats_start)
|
|
||||||
await async_wait_recording_done(hass)
|
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": now.isoformat(),
|
|
||||||
"end_time": now.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
|
||||||
"period": "hour",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {}
|
|
||||||
|
|
||||||
await client.send_json(
|
# Test the WS API works and issues a warning
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": now.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
|
||||||
"period": "5minute",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {}
|
|
||||||
|
|
||||||
past = now - timedelta(days=3, hours=1)
|
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": past.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
|
||||||
"period": "5minute",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {
|
|
||||||
"sensor.test": [
|
|
||||||
{
|
|
||||||
"statistic_id": "sensor.test",
|
|
||||||
"start": stats_start.isoformat(),
|
|
||||||
"end": (stats_start + timedelta(minutes=5)).isoformat(),
|
|
||||||
"mean": approx(value),
|
|
||||||
"min": approx(value),
|
|
||||||
"max": approx(value),
|
|
||||||
"last_reset": None,
|
|
||||||
"state": None,
|
|
||||||
"sum": None,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
start_of_day = stats_top_of_hour.replace(hour=0, minute=0)
|
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": stats_top_of_hour.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
|
||||||
"period": "day",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {
|
|
||||||
"sensor.test": [
|
|
||||||
{
|
|
||||||
"statistic_id": "sensor.test",
|
|
||||||
"start": start_of_day.isoformat(),
|
|
||||||
"end": (start_of_day + timedelta(days=1)).isoformat(),
|
|
||||||
"mean": approx(value),
|
|
||||||
"min": approx(value),
|
|
||||||
"max": approx(value),
|
|
||||||
"last_reset": None,
|
|
||||||
"state": None,
|
|
||||||
"sum": None,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": now.isoformat(),
|
|
||||||
"statistic_ids": ["sensor.test"],
|
|
||||||
"period": "5minute",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == {}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_statistics_during_period_bad_start_time(
|
|
||||||
hass, hass_ws_client, recorder_mock
|
|
||||||
):
|
|
||||||
"""Test statistics_during_period."""
|
|
||||||
await async_setup_component(
|
|
||||||
hass,
|
|
||||||
"history",
|
|
||||||
{"history": {}},
|
|
||||||
)
|
|
||||||
|
|
||||||
client = await hass_ws_client()
|
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": "cats",
|
|
||||||
"period": "5minute",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert not response["success"]
|
|
||||||
assert response["error"]["code"] == "invalid_start_time"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_statistics_during_period_bad_end_time(
|
|
||||||
hass, hass_ws_client, recorder_mock
|
|
||||||
):
|
|
||||||
"""Test statistics_during_period."""
|
|
||||||
now = dt_util.utcnow()
|
|
||||||
|
|
||||||
await async_setup_component(
|
|
||||||
hass,
|
|
||||||
"history",
|
|
||||||
{"history": {}},
|
|
||||||
)
|
|
||||||
|
|
||||||
client = await hass_ws_client()
|
|
||||||
await client.send_json(
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"type": "history/statistics_during_period",
|
|
||||||
"start_time": now.isoformat(),
|
|
||||||
"end_time": "dogs",
|
|
||||||
"period": "5minute",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert not response["success"]
|
|
||||||
assert response["error"]["code"] == "invalid_end_time"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"units, attributes, display_unit, statistics_unit",
|
|
||||||
[
|
|
||||||
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
|
|
||||||
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
|
|
||||||
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°F", "°C"),
|
|
||||||
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°C", "°C"),
|
|
||||||
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "psi", "Pa"),
|
|
||||||
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "Pa", "Pa"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_list_statistic_ids(
|
|
||||||
hass,
|
|
||||||
hass_ws_client,
|
|
||||||
recorder_mock,
|
|
||||||
units,
|
|
||||||
attributes,
|
|
||||||
display_unit,
|
|
||||||
statistics_unit,
|
|
||||||
):
|
|
||||||
"""Test list_statistic_ids."""
|
|
||||||
now = dt_util.utcnow()
|
|
||||||
|
|
||||||
hass.config.units = units
|
|
||||||
await async_setup_component(hass, "history", {"history": {}})
|
|
||||||
await async_setup_component(hass, "sensor", {})
|
|
||||||
await async_recorder_block_till_done(hass)
|
|
||||||
|
|
||||||
client = await hass_ws_client()
|
|
||||||
await client.send_json({"id": 1, "type": "history/list_statistic_ids"})
|
await client.send_json({"id": 1, "type": "history/list_statistic_ids"})
|
||||||
response = await client.receive_json()
|
response = await client.receive_json()
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"] == []
|
assert response["result"] == []
|
||||||
|
|
||||||
hass.states.async_set("sensor.test", 10, attributes=attributes)
|
assert (
|
||||||
await async_wait_recording_done(hass)
|
"WS API 'history/list_statistic_ids' is deprecated and will be removed in "
|
||||||
|
"Home Assistant Core 2022.12. Use 'recorder/list_statistic_ids' instead"
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
await client.send_json({"id": 2, "type": "history/list_statistic_ids"})
|
with patch(
|
||||||
response = await client.receive_json()
|
"homeassistant.components.history.recorder_ws.ws_handle_list_statistic_ids",
|
||||||
assert response["success"]
|
wraps=ws_handle_list_statistic_ids,
|
||||||
assert response["result"] == [
|
) as ws_mock:
|
||||||
{
|
await client.send_json({"id": 2, "type": "history/list_statistic_ids"})
|
||||||
"statistic_id": "sensor.test",
|
await client.receive_json()
|
||||||
"has_mean": True,
|
ws_mock.assert_called_once()
|
||||||
"has_sum": False,
|
|
||||||
"name": None,
|
|
||||||
"source": "recorder",
|
|
||||||
"display_unit_of_measurement": display_unit,
|
|
||||||
"statistics_unit_of_measurement": statistics_unit,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_recorder_block_till_done(hass)
|
|
||||||
# Remove the state, statistics will now be fetched from the database
|
|
||||||
hass.states.async_remove("sensor.test")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
await client.send_json({"id": 3, "type": "history/list_statistic_ids"})
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == [
|
|
||||||
{
|
|
||||||
"statistic_id": "sensor.test",
|
|
||||||
"has_mean": True,
|
|
||||||
"has_sum": False,
|
|
||||||
"name": None,
|
|
||||||
"source": "recorder",
|
|
||||||
"display_unit_of_measurement": display_unit,
|
|
||||||
"statistics_unit_of_measurement": statistics_unit,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
await client.send_json(
|
|
||||||
{"id": 4, "type": "history/list_statistic_ids", "statistic_type": "dogs"}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert not response["success"]
|
|
||||||
|
|
||||||
await client.send_json(
|
|
||||||
{"id": 5, "type": "history/list_statistic_ids", "statistic_type": "mean"}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == [
|
|
||||||
{
|
|
||||||
"statistic_id": "sensor.test",
|
|
||||||
"has_mean": True,
|
|
||||||
"has_sum": False,
|
|
||||||
"name": None,
|
|
||||||
"source": "recorder",
|
|
||||||
"display_unit_of_measurement": display_unit,
|
|
||||||
"statistics_unit_of_measurement": statistics_unit,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
await client.send_json(
|
|
||||||
{"id": 6, "type": "history/list_statistic_ids", "statistic_type": "sum"}
|
|
||||||
)
|
|
||||||
response = await client.receive_json()
|
|
||||||
assert response["success"]
|
|
||||||
assert response["result"] == []
|
|
||||||
|
|
||||||
|
|
||||||
async def test_history_during_period(hass, hass_ws_client, recorder_mock):
|
async def test_history_during_period(hass, hass_ws_client, recorder_mock):
|
||||||
|
@ -1239,7 +932,6 @@ async def test_history_during_period(hass, hass_ws_client, recorder_mock):
|
||||||
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
|
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
|
@ -1358,8 +1050,6 @@ async def test_history_during_period_impossible_conditions(
|
||||||
hass, hass_ws_client, recorder_mock
|
hass, hass_ws_client, recorder_mock
|
||||||
):
|
):
|
||||||
"""Test history_during_period returns when condition cannot be true."""
|
"""Test history_during_period returns when condition cannot be true."""
|
||||||
now = dt_util.utcnow()
|
|
||||||
|
|
||||||
await async_setup_component(hass, "history", {})
|
await async_setup_component(hass, "history", {})
|
||||||
await async_setup_component(hass, "sensor", {})
|
await async_setup_component(hass, "sensor", {})
|
||||||
await async_recorder_block_till_done(hass)
|
await async_recorder_block_till_done(hass)
|
||||||
|
@ -1374,7 +1064,6 @@ async def test_history_during_period_impossible_conditions(
|
||||||
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
|
hass.states.async_set("sensor.test", "on", attributes={"any": "attr"})
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
after = dt_util.utcnow()
|
after = dt_util.utcnow()
|
||||||
|
@ -1440,7 +1129,6 @@ async def test_history_during_period_significant_domain(
|
||||||
hass.states.async_set("climate.test", "on", attributes={"temperature": "5"})
|
hass.states.async_set("climate.test", "on", attributes={"temperature": "5"})
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
|
@ -1664,7 +1352,6 @@ async def test_history_during_period_with_use_include_order(
|
||||||
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
|
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
do_adhoc_statistics(hass, start=now)
|
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
|
|
|
@ -4,6 +4,7 @@ from datetime import timedelta
|
||||||
import threading
|
import threading
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
import pytest
|
import pytest
|
||||||
from pytest import approx
|
from pytest import approx
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ from homeassistant.components.recorder.statistics import (
|
||||||
from homeassistant.helpers import recorder as recorder_helper
|
from homeassistant.helpers import recorder as recorder_helper
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
async_recorder_block_till_done,
|
async_recorder_block_till_done,
|
||||||
|
@ -34,6 +35,11 @@ POWER_SENSOR_ATTRIBUTES = {
|
||||||
"state_class": "measurement",
|
"state_class": "measurement",
|
||||||
"unit_of_measurement": "kW",
|
"unit_of_measurement": "kW",
|
||||||
}
|
}
|
||||||
|
PRESSURE_SENSOR_ATTRIBUTES = {
|
||||||
|
"device_class": "pressure",
|
||||||
|
"state_class": "measurement",
|
||||||
|
"unit_of_measurement": "hPa",
|
||||||
|
}
|
||||||
TEMPERATURE_SENSOR_ATTRIBUTES = {
|
TEMPERATURE_SENSOR_ATTRIBUTES = {
|
||||||
"device_class": "temperature",
|
"device_class": "temperature",
|
||||||
"state_class": "measurement",
|
"state_class": "measurement",
|
||||||
|
@ -51,6 +57,351 @@ GAS_SENSOR_ATTRIBUTES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"units, attributes, state, value",
|
||||||
|
[
|
||||||
|
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
||||||
|
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
||||||
|
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
|
||||||
|
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
|
||||||
|
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
|
||||||
|
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_statistics_during_period(
|
||||||
|
hass, hass_ws_client, recorder_mock, units, attributes, state, value
|
||||||
|
):
|
||||||
|
"""Test statistics_during_period."""
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
hass.config.units = units
|
||||||
|
await async_setup_component(hass, "sensor", {})
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
hass.states.async_set("sensor.test", state, attributes=attributes)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
do_adhoc_statistics(hass, start=now)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"end_time": now.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "hour",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {}
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"sensor.test": [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"start": now.isoformat(),
|
||||||
|
"end": (now + timedelta(minutes=5)).isoformat(),
|
||||||
|
"mean": approx(value),
|
||||||
|
"min": approx(value),
|
||||||
|
"max": approx(value),
|
||||||
|
"last_reset": None,
|
||||||
|
"state": None,
|
||||||
|
"sum": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"units, attributes, state, value",
|
||||||
|
[
|
||||||
|
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
||||||
|
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, 10, 10000),
|
||||||
|
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 50),
|
||||||
|
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, 10, 10),
|
||||||
|
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 14.503774389728312),
|
||||||
|
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, 1000, 100000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_statistics_during_period_in_the_past(
|
||||||
|
hass, hass_ws_client, recorder_mock, units, attributes, state, value
|
||||||
|
):
|
||||||
|
"""Test statistics_during_period in the past."""
|
||||||
|
hass.config.set_time_zone("UTC")
|
||||||
|
now = dt_util.utcnow().replace()
|
||||||
|
|
||||||
|
hass.config.units = units
|
||||||
|
await async_setup_component(hass, "sensor", {})
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
past = now - timedelta(days=3)
|
||||||
|
|
||||||
|
with freeze_time(past):
|
||||||
|
hass.states.async_set("sensor.test", state, attributes=attributes)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
sensor_state = hass.states.get("sensor.test")
|
||||||
|
assert sensor_state.last_updated == past
|
||||||
|
|
||||||
|
stats_top_of_hour = past.replace(minute=0, second=0, microsecond=0)
|
||||||
|
stats_start = past.replace(minute=55)
|
||||||
|
do_adhoc_statistics(hass, start=stats_start)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"end_time": now.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "hour",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {}
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {}
|
||||||
|
|
||||||
|
past = now - timedelta(days=3, hours=1)
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": past.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"sensor.test": [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"start": stats_start.isoformat(),
|
||||||
|
"end": (stats_start + timedelta(minutes=5)).isoformat(),
|
||||||
|
"mean": approx(value),
|
||||||
|
"min": approx(value),
|
||||||
|
"max": approx(value),
|
||||||
|
"last_reset": None,
|
||||||
|
"state": None,
|
||||||
|
"sum": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
start_of_day = stats_top_of_hour.replace(hour=0, minute=0)
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": stats_top_of_hour.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "day",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {
|
||||||
|
"sensor.test": [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"start": start_of_day.isoformat(),
|
||||||
|
"end": (start_of_day + timedelta(days=1)).isoformat(),
|
||||||
|
"mean": approx(value),
|
||||||
|
"min": approx(value),
|
||||||
|
"max": approx(value),
|
||||||
|
"last_reset": None,
|
||||||
|
"state": None,
|
||||||
|
"sum": None,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"statistic_ids": ["sensor.test"],
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == {}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_statistics_during_period_bad_start_time(
|
||||||
|
hass, hass_ws_client, recorder_mock
|
||||||
|
):
|
||||||
|
"""Test statistics_during_period."""
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": "cats",
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert not response["success"]
|
||||||
|
assert response["error"]["code"] == "invalid_start_time"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_statistics_during_period_bad_end_time(
|
||||||
|
hass, hass_ws_client, recorder_mock
|
||||||
|
):
|
||||||
|
"""Test statistics_during_period."""
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json(
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"type": "recorder/statistics_during_period",
|
||||||
|
"start_time": now.isoformat(),
|
||||||
|
"end_time": "dogs",
|
||||||
|
"period": "5minute",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert not response["success"]
|
||||||
|
assert response["error"]["code"] == "invalid_end_time"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"units, attributes, display_unit, statistics_unit",
|
||||||
|
[
|
||||||
|
(IMPERIAL_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
|
||||||
|
(METRIC_SYSTEM, POWER_SENSOR_ATTRIBUTES, "W", "W"),
|
||||||
|
(IMPERIAL_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°F", "°C"),
|
||||||
|
(METRIC_SYSTEM, TEMPERATURE_SENSOR_ATTRIBUTES, "°C", "°C"),
|
||||||
|
(IMPERIAL_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "psi", "Pa"),
|
||||||
|
(METRIC_SYSTEM, PRESSURE_SENSOR_ATTRIBUTES, "Pa", "Pa"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_list_statistic_ids(
|
||||||
|
hass,
|
||||||
|
hass_ws_client,
|
||||||
|
recorder_mock,
|
||||||
|
units,
|
||||||
|
attributes,
|
||||||
|
display_unit,
|
||||||
|
statistics_unit,
|
||||||
|
):
|
||||||
|
"""Test list_statistic_ids."""
|
||||||
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
|
hass.config.units = units
|
||||||
|
await async_setup_component(hass, "sensor", {})
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
client = await hass_ws_client()
|
||||||
|
await client.send_json({"id": 1, "type": "recorder/list_statistic_ids"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == []
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 10, attributes=attributes)
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
await client.send_json({"id": 2, "type": "recorder/list_statistic_ids"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"has_mean": True,
|
||||||
|
"has_sum": False,
|
||||||
|
"name": None,
|
||||||
|
"source": "recorder",
|
||||||
|
"display_unit_of_measurement": display_unit,
|
||||||
|
"statistics_unit_of_measurement": statistics_unit,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
do_adhoc_statistics(hass, start=now)
|
||||||
|
await async_recorder_block_till_done(hass)
|
||||||
|
# Remove the state, statistics will now be fetched from the database
|
||||||
|
hass.states.async_remove("sensor.test")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await client.send_json({"id": 3, "type": "recorder/list_statistic_ids"})
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"has_mean": True,
|
||||||
|
"has_sum": False,
|
||||||
|
"name": None,
|
||||||
|
"source": "recorder",
|
||||||
|
"display_unit_of_measurement": display_unit,
|
||||||
|
"statistics_unit_of_measurement": statistics_unit,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{"id": 4, "type": "recorder/list_statistic_ids", "statistic_type": "dogs"}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert not response["success"]
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{"id": 5, "type": "recorder/list_statistic_ids", "statistic_type": "mean"}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == [
|
||||||
|
{
|
||||||
|
"statistic_id": "sensor.test",
|
||||||
|
"has_mean": True,
|
||||||
|
"has_sum": False,
|
||||||
|
"name": None,
|
||||||
|
"source": "recorder",
|
||||||
|
"display_unit_of_measurement": display_unit,
|
||||||
|
"statistics_unit_of_measurement": statistics_unit,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await client.send_json(
|
||||||
|
{"id": 6, "type": "recorder/list_statistic_ids", "statistic_type": "sum"}
|
||||||
|
)
|
||||||
|
response = await client.receive_json()
|
||||||
|
assert response["success"]
|
||||||
|
assert response["result"] == []
|
||||||
|
|
||||||
|
|
||||||
async def test_validate_statistics(hass, hass_ws_client, recorder_mock):
|
async def test_validate_statistics(hass, hass_ws_client, recorder_mock):
|
||||||
"""Test validate_statistics can be called."""
|
"""Test validate_statistics can be called."""
|
||||||
id = 1
|
id = 1
|
||||||
|
@ -83,7 +434,6 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
|
||||||
value = 10000
|
value = 10000
|
||||||
|
|
||||||
hass.config.units = units
|
hass.config.units = units
|
||||||
await async_setup_component(hass, "history", {})
|
|
||||||
await async_setup_component(hass, "sensor", {})
|
await async_setup_component(hass, "sensor", {})
|
||||||
await async_recorder_block_till_done(hass)
|
await async_recorder_block_till_done(hass)
|
||||||
hass.states.async_set("sensor.test1", state, attributes=attributes)
|
hass.states.async_set("sensor.test1", state, attributes=attributes)
|
||||||
|
@ -98,7 +448,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"type": "history/statistics_during_period",
|
"type": "recorder/statistics_during_period",
|
||||||
"start_time": now.isoformat(),
|
"start_time": now.isoformat(),
|
||||||
"period": "5minute",
|
"period": "5minute",
|
||||||
}
|
}
|
||||||
|
@ -163,7 +513,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"type": "history/statistics_during_period",
|
"type": "recorder/statistics_during_period",
|
||||||
"start_time": now.isoformat(),
|
"start_time": now.isoformat(),
|
||||||
"period": "5minute",
|
"period": "5minute",
|
||||||
}
|
}
|
||||||
|
@ -187,7 +537,7 @@ async def test_clear_statistics(hass, hass_ws_client, recorder_mock):
|
||||||
await client.send_json(
|
await client.send_json(
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "history/statistics_during_period",
|
"type": "recorder/statistics_during_period",
|
||||||
"start_time": now.isoformat(),
|
"start_time": now.isoformat(),
|
||||||
"period": "5minute",
|
"period": "5minute",
|
||||||
}
|
}
|
||||||
|
@ -209,7 +559,6 @@ async def test_update_statistics_metadata(
|
||||||
state = 10
|
state = 10
|
||||||
|
|
||||||
hass.config.units = units
|
hass.config.units = units
|
||||||
await async_setup_component(hass, "history", {})
|
|
||||||
await async_setup_component(hass, "sensor", {})
|
await async_setup_component(hass, "sensor", {})
|
||||||
await async_recorder_block_till_done(hass)
|
await async_recorder_block_till_done(hass)
|
||||||
hass.states.async_set("sensor.test", state, attributes=attributes)
|
hass.states.async_set("sensor.test", state, attributes=attributes)
|
||||||
|
@ -220,7 +569,7 @@ async def test_update_statistics_metadata(
|
||||||
|
|
||||||
client = await hass_ws_client()
|
client = await hass_ws_client()
|
||||||
|
|
||||||
await client.send_json({"id": 1, "type": "history/list_statistic_ids"})
|
await client.send_json({"id": 1, "type": "recorder/list_statistic_ids"})
|
||||||
response = await client.receive_json()
|
response = await client.receive_json()
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"] == [
|
assert response["result"] == [
|
||||||
|
@ -247,7 +596,7 @@ async def test_update_statistics_metadata(
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
await async_recorder_block_till_done(hass)
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
await client.send_json({"id": 3, "type": "history/list_statistic_ids"})
|
await client.send_json({"id": 3, "type": "recorder/list_statistic_ids"})
|
||||||
response = await client.receive_json()
|
response = await client.receive_json()
|
||||||
assert response["success"]
|
assert response["success"]
|
||||||
assert response["result"] == [
|
assert response["result"] == [
|
||||||
|
@ -457,7 +806,6 @@ async def test_get_statistics_metadata(
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
|
|
||||||
hass.config.units = units
|
hass.config.units = units
|
||||||
await async_setup_component(hass, "history", {"history": {}})
|
|
||||||
await async_setup_component(hass, "sensor", {})
|
await async_setup_component(hass, "sensor", {})
|
||||||
await async_recorder_block_till_done(hass)
|
await async_recorder_block_till_done(hass)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue