1
0
mirror of https://github.com/home-assistant/core.git synced 2026-08-06 13:26:29 +01:00
Files
core/homeassistant/components/http/websocket_api.py
T

146 lines
5.1 KiB
Python

"""WebSocket API for the HTTP integration user config."""
from typing import Any, Final
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.homeassistant import (
DOMAIN as HASS_DOMAIN,
SERVICE_HOMEASSISTANT_RESTART,
)
from homeassistant.core import CoreState, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from .config import HTTP_STORAGE_SCHEMA, ConfData, async_get_and_load_store
from .const import ATTR_CONFIG, CONF_SERVER_PORT
from .server import async_verify_can_bind
ERR_BIND_FAILED: Final = "bind_failed"
ERR_NOT_RUNNING: Final = "not_running"
@callback
def async_register_websocket_commands(hass: HomeAssistant) -> None:
"""Register the HTTP config websocket commands."""
websocket_api.async_register_command(hass, websocket_get_config)
websocket_api.async_register_command(hass, websocket_set_config)
websocket_api.async_register_command(hass, websocket_promote_config)
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required("type"): "http/config"})
@websocket_api.async_response
async def websocket_get_config(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Return the HTTP configuration.
``stable`` is the confirmed-working config
``pending`` is an unconfirmed config awaiting promotion, or ``None``.
A pending config that failed its trial is kept with its ``error``
(and ``error_message``) recorded, but is never applied again.
``revert_at`` is when an unconfirmed pending config auto-reverts to
stable, or ``None`` when no revert is scheduled.
``active_config_type`` is the slot the running server was started with.
``default`` is the built-in default config.
"""
store = await async_get_and_load_store(hass)
connection.send_result(
msg["id"],
{
"stable": store.stable,
"pending": store.pending,
"revert_at": store.revert_deadline,
"active_config_type": store.active_config_type,
"default": store.default,
},
)
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "http/config/configure",
vol.Required(ATTR_CONFIG): vol.Any(None, HTTP_STORAGE_SCHEMA),
}
)
@websocket_api.async_response
async def websocket_set_config(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Store a new pending HTTP configuration and restart to apply it.
Only allowed while Home Assistant is running: applying a config means
restarting, and restarting a start that has not finished yet leaves
integrations that are still setting up in an undefined state.
A new config is first verified to be applicable by binding its
configured address, so an unusable config is rejected here instead of
being discovered after the restart. The check is skipped when the port
matches the currently bound one: the running server holds that port
until the restart releases it, so a probe would always fail against
ourselves.
Restart whenever the pending slot changes, so the runtime config is
refreshed. The result reports whether a restart was triggered via
``{"restart": bool}``.
"""
if hass.state is not CoreState.running:
connection.send_error(
msg["id"],
ERR_NOT_RUNNING,
"The HTTP configuration can only be changed while Home Assistant "
f"is running, current state: {hass.state.value}",
)
return
config: ConfData | None = msg[ATTR_CONFIG]
if config is not None and config[CONF_SERVER_PORT] != hass.http.server_port:
try:
await async_verify_can_bind(hass, config)
except HomeAssistantError as err:
connection.send_error(msg["id"], ERR_BIND_FAILED, str(err))
return
store = await async_get_and_load_store(hass)
previous_pending = store.pending
await store.async_set_pending(config)
restart = store.pending != previous_pending
connection.send_result(msg["id"], {"restart": restart})
if restart:
await hass.services.async_call(HASS_DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
@websocket_api.require_admin
@websocket_api.websocket_command({vol.Required("type"): "http/config/promote"})
@websocket_api.async_response
async def websocket_promote_config(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Promote the pending HTTP config to stable.
Called by the user after they have verified Home Assistant is
working correctly with the pending config. The stable config is
the one used by recovery mode, so promotion must be explicit.
"""
store = await async_get_and_load_store(hass)
try:
await store.async_promote_pending()
except HomeAssistantError as err:
connection.send_error(
msg["id"],
websocket_api.const.ERR_NOT_ALLOWED,
str(err),
)
return
connection.send_result(msg["id"])