165 lines
5.2 KiB
Python
165 lines
5.2 KiB
Python
"""Event parser and human readable log generator."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components import frontend
|
|
from homeassistant.components.recorder import DOMAIN as RECORDER_DOMAIN
|
|
from homeassistant.components.recorder.filters import (
|
|
extract_include_exclude_filter_conf,
|
|
merge_include_exclude_filters,
|
|
sqlalchemy_filter_from_include_exclude_conf,
|
|
)
|
|
from homeassistant.const import (
|
|
ATTR_DOMAIN,
|
|
ATTR_ENTITY_ID,
|
|
ATTR_NAME,
|
|
EVENT_LOGBOOK_ENTRY,
|
|
)
|
|
from homeassistant.core import Context, HomeAssistant, ServiceCall, callback
|
|
from homeassistant.helpers import config_validation as cv
|
|
from homeassistant.helpers.entityfilter import (
|
|
INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA,
|
|
convert_include_exclude_filter,
|
|
)
|
|
from homeassistant.helpers.integration_platform import (
|
|
async_process_integration_platforms,
|
|
)
|
|
from homeassistant.helpers.typing import ConfigType
|
|
from homeassistant.loader import bind_hass
|
|
from homeassistant.util.event_type import EventType
|
|
|
|
from . import rest_api, websocket_api
|
|
from .const import ( # noqa: F401
|
|
ATTR_MESSAGE,
|
|
DOMAIN,
|
|
LOGBOOK_ENTRY_CONTEXT_ID,
|
|
LOGBOOK_ENTRY_DOMAIN,
|
|
LOGBOOK_ENTRY_ENTITY_ID,
|
|
LOGBOOK_ENTRY_ICON,
|
|
LOGBOOK_ENTRY_MESSAGE,
|
|
LOGBOOK_ENTRY_NAME,
|
|
LOGBOOK_ENTRY_SOURCE,
|
|
)
|
|
from .models import LazyEventPartialState, LogbookConfig
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
{DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA
|
|
)
|
|
|
|
|
|
LOG_MESSAGE_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Required(ATTR_NAME): cv.string,
|
|
vol.Required(ATTR_MESSAGE): cv.string,
|
|
vol.Optional(ATTR_DOMAIN): cv.slug,
|
|
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
|
|
}
|
|
)
|
|
|
|
|
|
@bind_hass
|
|
def log_entry(
|
|
hass: HomeAssistant,
|
|
name: str,
|
|
message: str,
|
|
domain: str | None = None,
|
|
entity_id: str | None = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Add an entry to the logbook."""
|
|
hass.add_job(async_log_entry, hass, name, message, domain, entity_id, context)
|
|
|
|
|
|
@callback
|
|
@bind_hass
|
|
def async_log_entry(
|
|
hass: HomeAssistant,
|
|
name: str,
|
|
message: str,
|
|
domain: str | None = None,
|
|
entity_id: str | None = None,
|
|
context: Context | None = None,
|
|
) -> None:
|
|
"""Add an entry to the logbook."""
|
|
data = {LOGBOOK_ENTRY_NAME: name, LOGBOOK_ENTRY_MESSAGE: message}
|
|
|
|
if domain is not None:
|
|
data[LOGBOOK_ENTRY_DOMAIN] = domain
|
|
if entity_id is not None:
|
|
data[LOGBOOK_ENTRY_ENTITY_ID] = entity_id
|
|
hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data, context=context)
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
"""Logbook setup."""
|
|
|
|
@callback
|
|
def log_message(service: ServiceCall) -> None:
|
|
"""Handle sending notification message service calls."""
|
|
message = service.data[ATTR_MESSAGE]
|
|
name = service.data[ATTR_NAME]
|
|
domain = service.data.get(ATTR_DOMAIN)
|
|
entity_id = service.data.get(ATTR_ENTITY_ID)
|
|
|
|
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
|
|
|
|
async_log_entry(hass, name, message, domain, entity_id, service.context)
|
|
|
|
frontend.async_register_built_in_panel(
|
|
hass, "logbook", "logbook", "hass:format-list-bulleted-type"
|
|
)
|
|
|
|
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.get_filter()
|
|
else:
|
|
filters = None
|
|
entities_filter = None
|
|
|
|
external_events: dict[
|
|
EventType[Any] | str,
|
|
tuple[str, Callable[[LazyEventPartialState], dict[str, Any]]],
|
|
] = {}
|
|
hass.data[DOMAIN] = LogbookConfig(external_events, filters, entities_filter)
|
|
websocket_api.async_setup(hass)
|
|
rest_api.async_setup(hass, config, filters, entities_filter)
|
|
hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA)
|
|
|
|
await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform)
|
|
|
|
return True
|
|
|
|
|
|
@callback
|
|
def _process_logbook_platform(hass: HomeAssistant, domain: str, platform: Any) -> None:
|
|
"""Process a logbook platform."""
|
|
logbook_config: LogbookConfig = hass.data[DOMAIN]
|
|
external_events = logbook_config.external_events
|
|
|
|
@callback
|
|
def _async_describe_event(
|
|
domain: str,
|
|
event_name: str,
|
|
describe_callback: Callable[[LazyEventPartialState], dict[str, Any]],
|
|
) -> None:
|
|
"""Teach logbook how to describe a new event."""
|
|
external_events[event_name] = (domain, describe_callback)
|
|
|
|
platform.async_describe_events(hass, _async_describe_event)
|