初始化config文件
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
"""Websocket API for Node-RED."""
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp.web import Request, Response
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import device_automation
|
||||
from homeassistant.components.device_automation import DeviceAutomationType
|
||||
from homeassistant.components.device_automation.exceptions import (
|
||||
DeviceNotFound,
|
||||
InvalidDeviceAutomationConfig,
|
||||
)
|
||||
from homeassistant.components.device_automation.trigger import TRIGGER_SCHEMA
|
||||
from homeassistant.components.webhook import (
|
||||
SUPPORTED_METHODS,
|
||||
async_register as webhook_async_register,
|
||||
async_unregister as webhook_async_unregister,
|
||||
)
|
||||
from homeassistant.components.websocket_api import async_register_command
|
||||
from homeassistant.components.websocket_api.connection import ActiveConnection
|
||||
from homeassistant.components.websocket_api.decorators import (
|
||||
async_response,
|
||||
require_admin,
|
||||
websocket_command,
|
||||
)
|
||||
from homeassistant.components.websocket_api.messages import (
|
||||
error_message,
|
||||
event_message,
|
||||
result_message,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DOMAIN,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_STATE,
|
||||
CONF_TYPE,
|
||||
CONF_WEBHOOK_ID,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
trigger,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity_registry import async_entries_for_device, async_get
|
||||
|
||||
from .const import (
|
||||
CONF_ATTRIBUTES,
|
||||
CONF_COMPONENT,
|
||||
CONF_CONFIG,
|
||||
CONF_DEVICE_INFO,
|
||||
CONF_DEVICE_TRIGGER,
|
||||
CONF_NODE_ID,
|
||||
CONF_REMOVE,
|
||||
CONF_SERVER_ID,
|
||||
CONF_SUB_TYPE,
|
||||
DOMAIN,
|
||||
DOMAIN_DATA,
|
||||
NODERED_CONFIG_UPDATE,
|
||||
NODERED_DISCOVERY,
|
||||
NODERED_ENTITY,
|
||||
VERSION,
|
||||
WEBHOOKS,
|
||||
)
|
||||
from .sentence import websocket_sentence, websocket_sentence_response
|
||||
from .utils import NodeRedJSONEncoder
|
||||
|
||||
CONF_ALLOWED_METHODS = "allowed_methods"
|
||||
CONF_LOCAL_ONLY = "local_only"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def unregister_all_webhooks(hass: HomeAssistant) -> None:
|
||||
"""Unregister all tracked webhooks."""
|
||||
if DOMAIN_DATA not in hass.data:
|
||||
return
|
||||
|
||||
domain_data = hass.data[DOMAIN_DATA]
|
||||
webhook_ids = domain_data.get(WEBHOOKS, set()).copy()
|
||||
|
||||
for webhook_id in webhook_ids:
|
||||
with contextlib.suppress(ValueError):
|
||||
webhook_async_unregister(hass, webhook_id)
|
||||
_LOGGER.info("Webhook unregistered during cleanup: %s..", webhook_id[:15])
|
||||
|
||||
# Clear the tracking set
|
||||
domain_data[WEBHOOKS] = set()
|
||||
|
||||
|
||||
def register_websocket_handlers(hass: HomeAssistant) -> None:
|
||||
"""Register the websocket handlers."""
|
||||
async_register_command(hass, websocket_device_action)
|
||||
async_register_command(hass, websocket_device_remove)
|
||||
async_register_command(hass, websocket_device_trigger)
|
||||
async_register_command(hass, websocket_discovery)
|
||||
async_register_command(hass, websocket_entity)
|
||||
async_register_command(hass, websocket_config_update)
|
||||
async_register_command(hass, websocket_version)
|
||||
async_register_command(hass, websocket_webhook)
|
||||
async_register_command(hass, websocket_sentence)
|
||||
async_register_command(hass, websocket_sentence_response)
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/device/action",
|
||||
vol.Required("action"): cv.DEVICE_ACTION_SCHEMA,
|
||||
}
|
||||
)
|
||||
@async_response
|
||||
async def websocket_device_action(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Execute a device action."""
|
||||
context = connection.context(msg)
|
||||
platform = await device_automation.async_get_device_automation_platform(
|
||||
hass, msg["action"][CONF_DOMAIN], DeviceAutomationType.ACTION
|
||||
)
|
||||
|
||||
try:
|
||||
if "entity_id" in msg["action"]:
|
||||
entity_registry = async_get(hass)
|
||||
entity_entry = entity_registry.async_get(msg["action"]["entity_id"])
|
||||
if entity_entry is None:
|
||||
connection.send_message(
|
||||
error_message(
|
||||
msg[CONF_ID],
|
||||
"entity_not_found",
|
||||
f"Entity '{msg['action']['entity_id']}' not found",
|
||||
)
|
||||
)
|
||||
return
|
||||
msg["action"]["entity_id"] = entity_entry.entity_id
|
||||
|
||||
await platform.async_call_action_from_config(hass, msg["action"], {}, context)
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
except InvalidDeviceAutomationConfig as err:
|
||||
connection.send_message(error_message(msg[CONF_ID], "invalid_config", str(err)))
|
||||
except DeviceNotFound as err:
|
||||
connection.send_message(
|
||||
error_message(msg[CONF_ID], "device_not_found", str(err))
|
||||
)
|
||||
except ValueError as err:
|
||||
connection.send_message(error_message(msg[CONF_ID], "value_error", str(err)))
|
||||
except RuntimeError as err:
|
||||
connection.send_message(error_message(msg[CONF_ID], "runtime_error", str(err)))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/device/remove",
|
||||
vol.Required(CONF_NODE_ID): cv.string,
|
||||
vol.Optional(CONF_CONFIG, default={}): dict,
|
||||
}
|
||||
)
|
||||
@async_response
|
||||
async def websocket_device_remove(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Remove a device."""
|
||||
device_registry = dr.async_get(hass)
|
||||
device = device_registry.async_get_device({(DOMAIN, msg[CONF_NODE_ID])})
|
||||
if device is not None:
|
||||
entity_registry = async_get(hass)
|
||||
entries = async_entries_for_device(entity_registry, device.id)
|
||||
# Remove entities from device before removing device so the entities
|
||||
# are not removed from HA
|
||||
if entries:
|
||||
for entry in entries:
|
||||
entity_registry.async_update_entity(entry.entity_id, device_id=None)
|
||||
|
||||
device_registry.async_remove_device(device.id)
|
||||
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/discovery",
|
||||
vol.Required(CONF_COMPONENT): cv.string,
|
||||
vol.Required(CONF_SERVER_ID): cv.string,
|
||||
vol.Required(CONF_NODE_ID): cv.string,
|
||||
vol.Optional(CONF_CONFIG, default={}): dict,
|
||||
vol.Optional(CONF_STATE): vol.Any(bool, str, int, float, None),
|
||||
vol.Optional(CONF_ATTRIBUTES): dict,
|
||||
vol.Optional(CONF_REMOVE): bool,
|
||||
vol.Optional(CONF_DEVICE_INFO): dict,
|
||||
vol.Optional(CONF_DEVICE_TRIGGER): TRIGGER_SCHEMA,
|
||||
vol.Optional(CONF_SUB_TYPE): str,
|
||||
}
|
||||
)
|
||||
def websocket_discovery(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Sensor command."""
|
||||
async_dispatcher_send(
|
||||
hass, NODERED_DISCOVERY.format(msg[CONF_COMPONENT]), msg, connection
|
||||
)
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/entity",
|
||||
vol.Required(CONF_SERVER_ID): cv.string,
|
||||
vol.Required(CONF_NODE_ID): cv.string,
|
||||
vol.Required(CONF_STATE): vol.Any(bool, str, int, float, None),
|
||||
vol.Optional(CONF_ATTRIBUTES, default={}): dict,
|
||||
}
|
||||
)
|
||||
def websocket_entity(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Sensor command."""
|
||||
async_dispatcher_send(
|
||||
hass, NODERED_ENTITY.format(msg[CONF_SERVER_ID], msg[CONF_NODE_ID]), msg
|
||||
)
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/entity/update_config",
|
||||
vol.Required(CONF_SERVER_ID): cv.string,
|
||||
vol.Required(CONF_NODE_ID): cv.string,
|
||||
vol.Optional(CONF_CONFIG, default={}): dict,
|
||||
}
|
||||
)
|
||||
def websocket_config_update(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Sensor command."""
|
||||
async_dispatcher_send(
|
||||
hass, NODERED_CONFIG_UPDATE.format(msg[CONF_SERVER_ID], msg[CONF_NODE_ID]), msg
|
||||
)
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command({vol.Required(CONF_TYPE): "nodered/version"})
|
||||
def websocket_version(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Version command."""
|
||||
connection.send_message(result_message(msg[CONF_ID], VERSION))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/webhook",
|
||||
vol.Required(CONF_SERVER_ID): cv.string,
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_WEBHOOK_ID): cv.string,
|
||||
vol.Optional(CONF_ALLOWED_METHODS): vol.All(
|
||||
cv.ensure_list,
|
||||
[vol.All(vol.Upper, vol.In(SUPPORTED_METHODS))],
|
||||
vol.Unique(),
|
||||
),
|
||||
}
|
||||
)
|
||||
@async_response
|
||||
async def websocket_webhook(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Create webhook command."""
|
||||
webhook_id = msg[CONF_WEBHOOK_ID]
|
||||
allowed_methods = msg.get(CONF_ALLOWED_METHODS)
|
||||
|
||||
@callback
|
||||
async def handle_webhook(
|
||||
hass: HomeAssistant, webhook_id: str, request: Request
|
||||
) -> Response | None:
|
||||
"""Handle webhook callback."""
|
||||
body = await request.text()
|
||||
try:
|
||||
payload = json.loads(body) if body else {}
|
||||
except ValueError:
|
||||
payload = body
|
||||
|
||||
data = {
|
||||
"payload": payload,
|
||||
"headers": dict(request.headers),
|
||||
"params": dict(request.query),
|
||||
}
|
||||
|
||||
_LOGGER.debug("Webhook received %s..: %s", webhook_id[:15], data)
|
||||
connection.send_message(event_message(msg[CONF_ID], {"data": data}))
|
||||
|
||||
def remove_webhook() -> None:
|
||||
"""Remove webhook command."""
|
||||
with contextlib.suppress(ValueError):
|
||||
webhook_async_unregister(hass, webhook_id)
|
||||
|
||||
# Remove from tracking
|
||||
if DOMAIN_DATA in hass.data:
|
||||
hass.data[DOMAIN_DATA].get(WEBHOOKS, set()).discard(webhook_id)
|
||||
|
||||
_LOGGER.info("Webhook removed: %s..", webhook_id[:15])
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
try:
|
||||
webhook_async_register(
|
||||
hass,
|
||||
DOMAIN,
|
||||
msg[CONF_NAME],
|
||||
webhook_id,
|
||||
handle_webhook,
|
||||
allowed_methods=allowed_methods,
|
||||
)
|
||||
except ValueError as err:
|
||||
connection.send_message(error_message(msg[CONF_ID], "value_error", str(err)))
|
||||
return
|
||||
|
||||
# Track webhook for cleanup during unload
|
||||
if DOMAIN_DATA in hass.data:
|
||||
hass.data[DOMAIN_DATA].setdefault(WEBHOOKS, set()).add(webhook_id)
|
||||
|
||||
_LOGGER.info("Webhook created: %s..", webhook_id[:15])
|
||||
connection.subscriptions[msg[CONF_ID]] = remove_webhook
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
|
||||
|
||||
@require_admin
|
||||
@websocket_command(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "nodered/device/trigger",
|
||||
vol.Required(CONF_NODE_ID): cv.string,
|
||||
vol.Required(CONF_DEVICE_TRIGGER, default={}): dict,
|
||||
}
|
||||
)
|
||||
@async_response
|
||||
async def websocket_device_trigger(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Create device trigger."""
|
||||
node_id = msg[CONF_NODE_ID]
|
||||
trigger_data = msg[CONF_DEVICE_TRIGGER]
|
||||
|
||||
def forward_trigger(event: dict[str, Any], _context: Context | None = None) -> None:
|
||||
"""Forward events to websocket."""
|
||||
message = event_message(
|
||||
msg[CONF_ID],
|
||||
{"type": "device_trigger", "data": event["trigger"]},
|
||||
)
|
||||
hass.loop.call_soon_threadsafe(
|
||||
connection.send_message,
|
||||
json.dumps(message, cls=NodeRedJSONEncoder, allow_nan=False),
|
||||
)
|
||||
|
||||
def unsubscribe() -> None:
|
||||
"""Remove device trigger."""
|
||||
if remove_trigger is not None:
|
||||
remove_trigger()
|
||||
_LOGGER.info("Device trigger removed: %s", node_id)
|
||||
|
||||
try:
|
||||
trigger_config = await trigger.async_validate_trigger_config(
|
||||
hass, [trigger_data]
|
||||
)
|
||||
remove_trigger = await trigger.async_initialize_triggers(
|
||||
hass,
|
||||
trigger_config,
|
||||
forward_trigger,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
_LOGGER.log,
|
||||
)
|
||||
except vol.MultipleInvalid as err:
|
||||
_LOGGER.exception("Error initializing device trigger for node_id: %s", node_id)
|
||||
connection.send_message(
|
||||
error_message(
|
||||
msg[CONF_ID],
|
||||
"invalid_trigger",
|
||||
str(err),
|
||||
)
|
||||
)
|
||||
return
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.exception("Error initializing device trigger for node_id: %s", node_id)
|
||||
connection.send_message(
|
||||
error_message(
|
||||
msg[CONF_ID],
|
||||
"invalid_trigger",
|
||||
str(err),
|
||||
)
|
||||
)
|
||||
return
|
||||
except RuntimeError as err:
|
||||
_LOGGER.exception("Error initializing device trigger for node_id: %s", node_id)
|
||||
connection.send_message(
|
||||
error_message(
|
||||
msg[CONF_ID],
|
||||
"runtime_error",
|
||||
str(err),
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
_LOGGER.info("Device trigger created: %s", node_id)
|
||||
_LOGGER.debug("Device trigger config for %s: %s", node_id, trigger_data)
|
||||
connection.subscriptions[msg[CONF_ID]] = unsubscribe
|
||||
connection.send_message(result_message(msg[CONF_ID]))
|
||||
Reference in New Issue
Block a user