1 """Support for System health ."""
3 from __future__
import annotations
6 from collections.abc
import Awaitable, Callable
8 from datetime
import datetime
10 from typing
import Any, Protocol
13 import voluptuous
as vol
19 config_validation
as cv,
25 _LOGGER = logging.getLogger(__name__)
27 DOMAIN =
"system_health"
29 INFO_CALLBACK_TIMEOUT = 5
31 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
35 """Define the format of system_health platforms."""
38 self, hass: HomeAssistant, register: SystemHealthRegistration
40 """Register system health callbacks."""
48 info_callback: Callable[[HomeAssistant], Awaitable[dict]],
50 """Register an info callback.
55 "Calling system_health.async_register_info is deprecated; Add a system_health"
58 hass.data.setdefault(DOMAIN, {})
62 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
63 """Set up the System Health component."""
64 websocket_api.async_register_command(hass, handle_info)
65 hass.data.setdefault(DOMAIN, {})
67 await integration_platform.async_process_integration_platforms(
68 hass, DOMAIN, _register_system_health_platform
76 hass: HomeAssistant, integration_domain: str, platform: SystemHealthProtocol
78 """Register a system health platform."""
83 hass: HomeAssistant, registration: SystemHealthRegistration
85 """Get integration system health."""
87 assert registration.info_callback
88 async
with asyncio.timeout(INFO_CALLBACK_TIMEOUT):
89 data = await registration.info_callback(hass)
91 data = {
"error": {
"type":
"failed",
"error":
"timeout"}}
93 _LOGGER.exception(
"Error fetching info")
94 data = {
"error": {
"type":
"failed",
"error":
"unknown"}}
96 result: dict[str, Any] = {
"info": data}
98 if registration.manage_url:
99 result[
"manage_url"] = registration.manage_url
106 """Format a system health value."""
107 if isinstance(val, datetime):
108 return {
"value": val.isoformat(),
"type":
"date"}
112 @websocket_api.websocket_command({vol.Required("type"):
"system_health/info"})
113 @websocket_api.async_response
117 """Handle an info request via a subscription."""
118 registrations: dict[str, SystemHealthRegistration] = hass.data[DOMAIN]
120 pending_info: dict[tuple[str, str], asyncio.Task] = {}
122 for domain, domain_data
in zip(
124 await asyncio.gather(
127 for registration
in registrations.values()
132 for key, value
in domain_data[
"info"].items():
133 if asyncio.iscoroutine(value):
134 value = asyncio.create_task(value)
135 if isinstance(value, asyncio.Task):
136 pending_info[(domain, key)] = value
137 domain_data[
"info"][key] = {
"type":
"pending"}
141 data[domain] = domain_data
144 connection.send_result(msg[
"id"])
146 stop_event = asyncio.Event()
147 connection.subscriptions[msg[
"id"]] = stop_event.set
150 connection.send_message(
151 websocket_api.messages.event_message(
152 msg[
"id"], {
"type":
"initial",
"data": data}
158 connection.send_message(
159 websocket_api.messages.event_message(msg[
"id"], {
"type":
"finish"})
163 tasks: set[asyncio.Task] = {
164 asyncio.create_task(stop_event.wait()),
165 *pending_info.values(),
167 pending_lookup = {val: key
for key, val
in pending_info.items()}
170 while len(tasks) > 1
and not stop_event.is_set():
172 done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
174 if stop_event.is_set():
181 domain, key = pending_lookup[result]
188 if exception := result.exception():
190 "Error fetching system info for %s - %s",
193 exc_info=(type(exception), exception, exception.__traceback__),
195 event_msg[
"success"] =
False
196 event_msg[
"error"] = {
"type":
"failed",
"error":
"unknown"}
198 event_msg[
"success"] =
True
201 connection.send_message(
202 websocket_api.messages.event_message(msg[
"id"], event_msg)
205 connection.send_message(
206 websocket_api.messages.event_message(msg[
"id"], {
"type":
"finish"})
210 @dataclasses.dataclass(slots=True)
212 """Helper class to track platform registration."""
216 info_callback: Callable[[HomeAssistant], Awaitable[dict]] |
None =
None
217 manage_url: str |
None =
None
222 info_callback: Callable[[HomeAssistant], Awaitable[dict]],
223 manage_url: str |
None =
None,
225 """Register an info callback."""
228 self.hass.data[DOMAIN][self.domain] = self
232 hass: HomeAssistant, url: str, more_info: str |
None =
None
233 ) -> str | dict[str, str]:
234 """Test if the url can be reached."""
235 session = aiohttp_client.async_get_clientsession(hass)
238 await session.get(url, timeout=aiohttp.ClientTimeout(total=5))
239 except aiohttp.ClientError:
240 data = {
"type":
"failed",
"error":
"unreachable"}
242 data = {
"type":
"failed",
"error":
"timeout"}
245 if more_info
is not None:
246 data[
"more_info"] = more_info
None async_register(self, HomeAssistant hass, SystemHealthRegistration register)
None async_register_info(self, Callable[[HomeAssistant], Awaitable[dict]] info_callback, str|None manage_url=None)
None handle_info(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
dict[str, Any] get_integration_info(HomeAssistant hass, SystemHealthRegistration registration)
Any _format_value(Any val)
str|dict[str, str] async_check_can_reach_url(HomeAssistant hass, str url, str|None more_info=None)
None async_register_info(HomeAssistant hass, str domain, Callable[[HomeAssistant], Awaitable[dict]] info_callback)
bool async_setup(HomeAssistant hass, ConfigType config)
None _register_system_health_platform(HomeAssistant hass, str integration_domain, SystemHealthProtocol platform)