2019-02-14 04:35:12 +00:00
|
|
|
"""Event parser and human readable log generator."""
|
2022-03-20 11:28:17 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-05-22 19:57:54 +00:00
|
|
|
from collections.abc import Callable
|
|
|
|
from typing import Any
|
2015-03-29 21:43:16 +00:00
|
|
|
|
2016-04-13 01:01:35 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2022-05-22 19:57:54 +00:00
|
|
|
from homeassistant.components import frontend
|
2022-09-12 16:53:05 +00:00
|
|
|
from homeassistant.components.recorder import DOMAIN as RECORDER_DOMAIN
|
2022-05-15 06:04:23 +00:00
|
|
|
from homeassistant.components.recorder.filters import (
|
2022-05-31 00:34:32 +00:00
|
|
|
extract_include_exclude_filter_conf,
|
|
|
|
merge_include_exclude_filters,
|
2022-05-02 00:33:31 +00:00
|
|
|
sqlalchemy_filter_from_include_exclude_conf,
|
|
|
|
)
|
2017-04-30 05:04:49 +00:00
|
|
|
from homeassistant.const import (
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_DOMAIN,
|
|
|
|
ATTR_ENTITY_ID,
|
|
|
|
ATTR_NAME,
|
|
|
|
EVENT_LOGBOOK_ENTRY,
|
|
|
|
)
|
2022-05-22 19:57:54 +00:00
|
|
|
from homeassistant.core import Context, Event, HomeAssistant, ServiceCall, callback
|
|
|
|
from homeassistant.helpers import config_validation as cv
|
2020-06-24 01:02:29 +00:00
|
|
|
from homeassistant.helpers.entityfilter import (
|
|
|
|
INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA,
|
|
|
|
convert_include_exclude_filter,
|
|
|
|
)
|
2020-06-25 01:14:50 +00:00
|
|
|
from homeassistant.helpers.integration_platform import (
|
|
|
|
async_process_integration_platforms,
|
|
|
|
)
|
2022-01-03 11:08:14 +00:00
|
|
|
from homeassistant.helpers.typing import ConfigType
|
2019-10-21 07:57:31 +00:00
|
|
|
from homeassistant.loader import bind_hass
|
2022-05-11 22:27:02 +00:00
|
|
|
|
2022-05-22 19:57:54 +00:00
|
|
|
from . import rest_api, websocket_api
|
2022-09-10 21:44:03 +00:00
|
|
|
from .const import ( # noqa: F401
|
2022-05-22 19:57:54 +00:00
|
|
|
ATTR_MESSAGE,
|
|
|
|
DOMAIN,
|
|
|
|
LOGBOOK_ENTITIES_FILTER,
|
2022-09-10 21:44:03 +00:00
|
|
|
LOGBOOK_ENTRY_CONTEXT_ID,
|
2022-05-22 19:57:54 +00:00
|
|
|
LOGBOOK_ENTRY_DOMAIN,
|
|
|
|
LOGBOOK_ENTRY_ENTITY_ID,
|
2022-09-10 21:44:03 +00:00
|
|
|
LOGBOOK_ENTRY_ICON,
|
2022-05-22 19:57:54 +00:00
|
|
|
LOGBOOK_ENTRY_MESSAGE,
|
|
|
|
LOGBOOK_ENTRY_NAME,
|
2022-09-10 21:44:03 +00:00
|
|
|
LOGBOOK_ENTRY_SOURCE,
|
2022-05-22 19:57:54 +00:00
|
|
|
LOGBOOK_FILTERS,
|
|
|
|
)
|
|
|
|
from .models import LazyEventPartialState # noqa: F401
|
2018-05-14 11:05:52 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
2020-06-24 01:02:29 +00:00
|
|
|
{DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-09-21 03:07:26 +00:00
|
|
|
|
2020-08-24 17:44:40 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
LOG_MESSAGE_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Required(ATTR_NAME): cv.string,
|
|
|
|
vol.Required(ATTR_MESSAGE): cv.template,
|
|
|
|
vol.Optional(ATTR_DOMAIN): cv.slug,
|
|
|
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
|
|
|
|
}
|
|
|
|
)
|
2016-04-13 01:01:35 +00:00
|
|
|
|
2016-01-27 16:27:55 +00:00
|
|
|
|
2018-10-01 14:12:25 +00:00
|
|
|
@bind_hass
|
2022-05-02 00:33:31 +00:00
|
|
|
def log_entry(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
name: str,
|
|
|
|
message: str,
|
|
|
|
domain: str | None = None,
|
|
|
|
entity_id: str | None = None,
|
|
|
|
context: Context | None = None,
|
|
|
|
) -> None:
|
2016-09-30 19:57:24 +00:00
|
|
|
"""Add an entry to the logbook."""
|
2020-08-24 17:44:40 +00:00
|
|
|
hass.add_job(async_log_entry, hass, name, message, domain, entity_id, context)
|
2016-09-30 19:57:24 +00:00
|
|
|
|
|
|
|
|
2021-12-17 07:19:07 +00:00
|
|
|
@callback
|
2018-10-01 14:12:25 +00:00
|
|
|
@bind_hass
|
2022-05-02 00:33:31 +00:00
|
|
|
def async_log_entry(
|
|
|
|
hass: HomeAssistant,
|
|
|
|
name: str,
|
|
|
|
message: str,
|
|
|
|
domain: str | None = None,
|
|
|
|
entity_id: str | None = None,
|
|
|
|
context: Context | None = None,
|
|
|
|
) -> None:
|
2016-03-08 16:55:57 +00:00
|
|
|
"""Add an entry to the logbook."""
|
2022-05-13 00:21:14 +00:00
|
|
|
data = {LOGBOOK_ENTRY_NAME: name, LOGBOOK_ENTRY_MESSAGE: message}
|
2015-09-14 01:30:44 +00:00
|
|
|
|
|
|
|
if domain is not None:
|
2022-05-13 00:21:14 +00:00
|
|
|
data[LOGBOOK_ENTRY_DOMAIN] = domain
|
2015-09-14 01:30:44 +00:00
|
|
|
if entity_id is not None:
|
2022-05-13 00:21:14 +00:00
|
|
|
data[LOGBOOK_ENTRY_ENTITY_ID] = entity_id
|
2020-08-24 17:44:40 +00:00
|
|
|
hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data, context=context)
|
2015-09-14 01:30:44 +00:00
|
|
|
|
2015-03-29 21:43:16 +00:00
|
|
|
|
2022-01-03 11:08:14 +00:00
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
2020-06-21 19:34:47 +00:00
|
|
|
"""Logbook setup."""
|
2020-06-25 01:14:50 +00:00
|
|
|
hass.data[DOMAIN] = {}
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2016-10-24 06:48:01 +00:00
|
|
|
@callback
|
2021-12-28 13:33:08 +00:00
|
|
|
def log_message(service: ServiceCall) -> None:
|
2016-02-23 20:06:50 +00:00
|
|
|
"""Handle sending notification message service calls."""
|
2016-04-13 01:01:35 +00:00
|
|
|
message = service.data[ATTR_MESSAGE]
|
|
|
|
name = service.data[ATTR_NAME]
|
|
|
|
domain = service.data.get(ATTR_DOMAIN)
|
|
|
|
entity_id = service.data.get(ATTR_ENTITY_ID)
|
2016-01-29 07:13:46 +00:00
|
|
|
|
2020-07-03 00:53:28 +00:00
|
|
|
if entity_id is None and domain is None:
|
|
|
|
# If there is no entity_id or
|
|
|
|
# domain, the event will get filtered
|
|
|
|
# away so we use the "logbook" domain
|
|
|
|
domain = DOMAIN
|
|
|
|
|
2016-09-28 04:29:55 +00:00
|
|
|
message.hass = hass
|
2020-10-26 18:29:10 +00:00
|
|
|
message = message.async_render(parse_result=False)
|
2022-05-13 00:21:14 +00:00
|
|
|
async_log_entry(hass, name, message, domain, entity_id, service.context)
|
2016-01-27 16:27:55 +00:00
|
|
|
|
2022-01-14 09:01:12 +00:00
|
|
|
frontend.async_register_built_in_panel(
|
|
|
|
hass, "logbook", "logbook", "hass:format-list-bulleted-type"
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-07-17 05:32:25 +00:00
|
|
|
|
2022-05-31 00:34:32 +00:00
|
|
|
recorder_conf = config.get(RECORDER_DOMAIN, {})
|
|
|
|
logbook_conf = config.get(DOMAIN, {})
|
|
|
|
recorder_filter = extract_include_exclude_filter_conf(recorder_conf)
|
|
|
|
logbook_filter = extract_include_exclude_filter_conf(logbook_conf)
|
|
|
|
merged_filter = merge_include_exclude_filters(recorder_filter, logbook_filter)
|
|
|
|
|
|
|
|
possible_merged_entities_filter = convert_include_exclude_filter(merged_filter)
|
|
|
|
if not possible_merged_entities_filter.empty_filter:
|
|
|
|
filters = sqlalchemy_filter_from_include_exclude_conf(merged_filter)
|
|
|
|
entities_filter = possible_merged_entities_filter
|
2020-07-02 16:12:27 +00:00
|
|
|
else:
|
|
|
|
filters = None
|
|
|
|
entities_filter = None
|
2022-05-12 03:28:06 +00:00
|
|
|
hass.data[LOGBOOK_FILTERS] = filters
|
|
|
|
hass.data[LOGBOOK_ENTITIES_FILTER] = entities_filter
|
2022-05-22 19:57:54 +00:00
|
|
|
websocket_api.async_setup(hass)
|
|
|
|
rest_api.async_setup(hass, config, filters, entities_filter)
|
2019-07-31 19:25:30 +00:00
|
|
|
hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA)
|
2020-06-25 01:14:50 +00:00
|
|
|
|
|
|
|
await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform)
|
|
|
|
|
2015-03-29 21:43:16 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2022-05-02 00:33:31 +00:00
|
|
|
async def _process_logbook_platform(
|
|
|
|
hass: HomeAssistant, domain: str, platform: Any
|
|
|
|
) -> None:
|
2020-06-25 01:14:50 +00:00
|
|
|
"""Process a logbook platform."""
|
|
|
|
|
|
|
|
@callback
|
2022-05-02 00:33:31 +00:00
|
|
|
def _async_describe_event(
|
|
|
|
domain: str,
|
|
|
|
event_name: str,
|
|
|
|
describe_callback: Callable[[Event], dict[str, Any]],
|
|
|
|
) -> None:
|
2020-06-25 01:14:50 +00:00
|
|
|
"""Teach logbook how to describe a new event."""
|
|
|
|
hass.data[DOMAIN][event_name] = (domain, describe_callback)
|
|
|
|
|
|
|
|
platform.async_describe_events(hass, _async_describe_event)
|