Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Event parser and human readable log generator."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from typing import Any
7 
8 import voluptuous as vol
9 
10 from homeassistant.components import frontend
11 from homeassistant.components.recorder import DOMAIN as RECORDER_DOMAIN
13  extract_include_exclude_filter_conf,
14  merge_include_exclude_filters,
15  sqlalchemy_filter_from_include_exclude_conf,
16 )
17 from homeassistant.const import (
18  ATTR_DOMAIN,
19  ATTR_ENTITY_ID,
20  ATTR_NAME,
21  EVENT_LOGBOOK_ENTRY,
22 )
23 from homeassistant.core import Context, HomeAssistant, ServiceCall, callback
24 from homeassistant.helpers import config_validation as cv
26  INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA,
27  convert_include_exclude_filter,
28 )
30  async_process_integration_platforms,
31 )
32 from homeassistant.helpers.typing import ConfigType
33 from homeassistant.loader import bind_hass
34 from homeassistant.util.event_type import EventType
35 
36 from . import rest_api, websocket_api
37 from .const import ( # noqa: F401
38  ATTR_MESSAGE,
39  DOMAIN,
40  LOGBOOK_ENTRY_CONTEXT_ID,
41  LOGBOOK_ENTRY_DOMAIN,
42  LOGBOOK_ENTRY_ENTITY_ID,
43  LOGBOOK_ENTRY_ICON,
44  LOGBOOK_ENTRY_MESSAGE,
45  LOGBOOK_ENTRY_NAME,
46  LOGBOOK_ENTRY_SOURCE,
47 )
48 from .models import LazyEventPartialState, LogbookConfig
49 
50 CONFIG_SCHEMA = vol.Schema(
51  {DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA
52 )
53 
54 
55 LOG_MESSAGE_SCHEMA = vol.Schema(
56  {
57  vol.Required(ATTR_NAME): cv.string,
58  vol.Required(ATTR_MESSAGE): cv.string,
59  vol.Optional(ATTR_DOMAIN): cv.slug,
60  vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
61  }
62 )
63 
64 
65 @bind_hass
67  hass: HomeAssistant,
68  name: str,
69  message: str,
70  domain: str | None = None,
71  entity_id: str | None = None,
72  context: Context | None = None,
73 ) -> None:
74  """Add an entry to the logbook."""
75  hass.add_job(async_log_entry, hass, name, message, domain, entity_id, context)
76 
77 
78 @callback
79 @bind_hass
81  hass: HomeAssistant,
82  name: str,
83  message: str,
84  domain: str | None = None,
85  entity_id: str | None = None,
86  context: Context | None = None,
87 ) -> None:
88  """Add an entry to the logbook."""
89  data = {LOGBOOK_ENTRY_NAME: name, LOGBOOK_ENTRY_MESSAGE: message}
90 
91  if domain is not None:
92  data[LOGBOOK_ENTRY_DOMAIN] = domain
93  if entity_id is not None:
94  data[LOGBOOK_ENTRY_ENTITY_ID] = entity_id
95  hass.bus.async_fire(EVENT_LOGBOOK_ENTRY, data, context=context)
96 
97 
98 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
99  """Logbook setup."""
100 
101  @callback
102  def log_message(service: ServiceCall) -> None:
103  """Handle sending notification message service calls."""
104  message = service.data[ATTR_MESSAGE]
105  name = service.data[ATTR_NAME]
106  domain = service.data.get(ATTR_DOMAIN)
107  entity_id = service.data.get(ATTR_ENTITY_ID)
108 
109  if entity_id is None and domain is None:
110  # If there is no entity_id or
111  # domain, the event will get filtered
112  # away so we use the "logbook" domain
113  domain = DOMAIN
114 
115  async_log_entry(hass, name, message, domain, entity_id, service.context)
116 
117  frontend.async_register_built_in_panel(
118  hass, "logbook", "logbook", "hass:format-list-bulleted-type"
119  )
120 
121  recorder_conf = config.get(RECORDER_DOMAIN, {})
122  logbook_conf = config.get(DOMAIN, {})
123  recorder_filter = extract_include_exclude_filter_conf(recorder_conf)
124  logbook_filter = extract_include_exclude_filter_conf(logbook_conf)
125  merged_filter = merge_include_exclude_filters(recorder_filter, logbook_filter)
126 
127  possible_merged_entities_filter = convert_include_exclude_filter(merged_filter)
128  if not possible_merged_entities_filter.empty_filter:
129  filters = sqlalchemy_filter_from_include_exclude_conf(merged_filter)
130  entities_filter = possible_merged_entities_filter.get_filter()
131  else:
132  filters = None
133  entities_filter = None
134 
135  external_events: dict[
136  EventType[Any] | str,
137  tuple[str, Callable[[LazyEventPartialState], dict[str, Any]]],
138  ] = {}
139  hass.data[DOMAIN] = LogbookConfig(external_events, filters, entities_filter)
140  websocket_api.async_setup(hass)
141  rest_api.async_setup(hass, config, filters, entities_filter)
142  hass.services.async_register(DOMAIN, "log", log_message, schema=LOG_MESSAGE_SCHEMA)
143 
144  await async_process_integration_platforms(hass, DOMAIN, _process_logbook_platform)
145 
146  return True
147 
148 
149 @callback
150 def _process_logbook_platform(hass: HomeAssistant, domain: str, platform: Any) -> None:
151  """Process a logbook platform."""
152  logbook_config: LogbookConfig = hass.data[DOMAIN]
153  external_events = logbook_config.external_events
154 
155  @callback
156  def _async_describe_event(
157  domain: str,
158  event_name: str,
159  describe_callback: Callable[[LazyEventPartialState], dict[str, Any]],
160  ) -> None:
161  """Teach logbook how to describe a new event."""
162  external_events[event_name] = (domain, describe_callback)
163 
164  platform.async_describe_events(hass, _async_describe_event)
None log_entry(HomeAssistant hass, str name, str message, str|None domain=None, str|None entity_id=None, Context|None context=None)
Definition: __init__.py:73
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:98
None async_log_entry(HomeAssistant hass, str name, str message, str|None domain=None, str|None entity_id=None, Context|None context=None)
Definition: __init__.py:87
None _process_logbook_platform(HomeAssistant hass, str domain, Any platform)
Definition: __init__.py:150
None log_message(HomeAssistant hass, str entity_id, str topic, PublishPayloadType payload, int qos, bool retain)
Definition: debug_info.py:40
dict[str, Any] merge_include_exclude_filters(dict[str, Any] base_filter, dict[str, Any] add_filter)
Definition: filters.py:50
dict[str, Any] extract_include_exclude_filter_conf(ConfigType conf)
Definition: filters.py:34
Filters|None sqlalchemy_filter_from_include_exclude_conf(ConfigType conf)
Definition: filters.py:65
EntityFilter convert_include_exclude_filter(dict[str, dict[str, list[str]]] config)
None async_process_integration_platforms(HomeAssistant hass, str platform_name, Callable[[HomeAssistant, str, Any], Awaitable[None]|None] process_platform, bool wait_for_platforms=False)