2022-05-22 19:57:54 +00:00
|
|
|
"""Event parser and human readable log generator."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
2023-05-27 23:52:42 +00:00
|
|
|
from collections.abc import Callable
|
2022-05-22 19:57:54 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
from http import HTTPStatus
|
|
|
|
from typing import Any, cast
|
|
|
|
|
|
|
|
from aiohttp import web
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2024-03-07 17:03:44 +00:00
|
|
|
from homeassistant.components.http import KEY_HASS, HomeAssistantView
|
2022-05-22 19:57:54 +00:00
|
|
|
from homeassistant.components.recorder import get_instance
|
|
|
|
from homeassistant.components.recorder.filters import Filters
|
|
|
|
from homeassistant.core import HomeAssistant, callback
|
|
|
|
from homeassistant.exceptions import InvalidEntityFormatError
|
|
|
|
from homeassistant.helpers import config_validation as cv
|
|
|
|
from homeassistant.helpers.typing import ConfigType
|
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
|
|
|
from .helpers import async_determine_event_types
|
|
|
|
from .processor import EventProcessor
|
|
|
|
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def async_setup(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
conf: ConfigType,
|
|
|
|
filters: Filters | None,
|
2023-05-27 23:52:42 +00:00
|
|
|
entities_filter: Callable[[str], bool] | None,
|
2022-05-22 19:57:54 +00:00
|
|
|
) -> None:
|
|
|
|
"""Set up the logbook rest API."""
|
|
|
|
hass.http.register_view(LogbookView(conf, filters, entities_filter))
|
|
|
|
|
|
|
|
|
|
|
|
class LogbookView(HomeAssistantView):
|
|
|
|
"""Handle logbook view requests."""
|
|
|
|
|
|
|
|
url = "/api/logbook"
|
|
|
|
name = "api:logbook"
|
|
|
|
extra_urls = ["/api/logbook/{datetime}"]
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
config: dict[str, Any],
|
|
|
|
filters: Filters | None,
|
2023-05-27 23:52:42 +00:00
|
|
|
entities_filter: Callable[[str], bool] | None,
|
2022-05-22 19:57:54 +00:00
|
|
|
) -> None:
|
|
|
|
"""Initialize the logbook view."""
|
|
|
|
self.config = config
|
|
|
|
self.filters = filters
|
|
|
|
self.entities_filter = entities_filter
|
|
|
|
|
|
|
|
async def get(
|
|
|
|
self, request: web.Request, datetime: str | None = None
|
|
|
|
) -> web.Response:
|
|
|
|
"""Retrieve logbook entries."""
|
|
|
|
if datetime:
|
|
|
|
if (datetime_dt := dt_util.parse_datetime(datetime)) is None:
|
|
|
|
return self.json_message("Invalid datetime", HTTPStatus.BAD_REQUEST)
|
|
|
|
else:
|
|
|
|
datetime_dt = dt_util.start_of_local_day()
|
|
|
|
|
|
|
|
if (period_str := request.query.get("period")) is None:
|
|
|
|
period: int = 1
|
|
|
|
else:
|
|
|
|
period = int(period_str)
|
|
|
|
|
|
|
|
if entity_ids_str := request.query.get("entity"):
|
|
|
|
try:
|
|
|
|
entity_ids = cv.entity_ids(entity_ids_str)
|
|
|
|
except vol.Invalid:
|
|
|
|
raise InvalidEntityFormatError(
|
|
|
|
f"Invalid entity id(s) encountered: {entity_ids_str}. "
|
|
|
|
"Format should be <domain>.<object_id>"
|
|
|
|
) from vol.Invalid
|
|
|
|
else:
|
|
|
|
entity_ids = None
|
|
|
|
|
|
|
|
if (end_time_str := request.query.get("end_time")) is None:
|
|
|
|
start_day = dt_util.as_utc(datetime_dt) - timedelta(days=period - 1)
|
|
|
|
end_day = start_day + timedelta(days=period)
|
|
|
|
else:
|
|
|
|
start_day = datetime_dt
|
|
|
|
if (end_day_dt := dt_util.parse_datetime(end_time_str)) is None:
|
|
|
|
return self.json_message("Invalid end_time", HTTPStatus.BAD_REQUEST)
|
|
|
|
end_day = end_day_dt
|
|
|
|
|
2024-03-07 17:03:44 +00:00
|
|
|
hass = request.app[KEY_HASS]
|
2022-05-22 19:57:54 +00:00
|
|
|
|
|
|
|
context_id = request.query.get("context_id")
|
|
|
|
|
|
|
|
if entity_ids and context_id:
|
|
|
|
return self.json_message(
|
|
|
|
"Can't combine entity with context_id", HTTPStatus.BAD_REQUEST
|
|
|
|
)
|
|
|
|
|
|
|
|
event_types = async_determine_event_types(hass, entity_ids, None)
|
|
|
|
event_processor = EventProcessor(
|
|
|
|
hass,
|
|
|
|
event_types,
|
|
|
|
entity_ids,
|
|
|
|
None,
|
|
|
|
context_id,
|
|
|
|
timestamp=False,
|
|
|
|
include_entity_name=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
def json_events() -> web.Response:
|
|
|
|
"""Fetch events and generate JSON."""
|
|
|
|
return self.json(
|
|
|
|
event_processor.get_events(
|
|
|
|
start_day,
|
|
|
|
end_day,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return cast(
|
|
|
|
web.Response, await get_instance(hass).async_add_executor_job(json_events)
|
|
|
|
)
|