初始化config文件

This commit is contained in:
2026-08-24 21:55:33 +08:00
commit 80a05d172e
2983 changed files with 1221334 additions and 0 deletions
@@ -0,0 +1,247 @@
"""Sentence platform for nodered."""
import asyncio
from enum import Enum
import logging
from typing import Any
from hassil.expression import Sentence
from hassil.recognize import RecognizeResult
import voluptuous as vol
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_ID, CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from .const import CONF_SERVER_ID
_LOGGER = logging.getLogger(__name__)
response_futures: dict[str, asyncio.Future] = {}
response_lock = asyncio.Lock()
class ResponseType(Enum):
"""Enumeration for sentence response types used in Node-RED triggers."""
FIXED = "fixed"
DYNAMIC = "dynamic"
@require_admin
@websocket_command(
{
vol.Required(CONF_TYPE): "nodered/sentence",
vol.Required(CONF_SERVER_ID): cv.string,
vol.Required("sentences", default=[]): [cv.string],
vol.Optional("response", default="Done"): cv.string,
vol.Optional("response_type", default=ResponseType.FIXED): vol.Coerce(
ResponseType
),
vol.Optional("response_timeout", default=1): cv.positive_float,
}
)
@async_response
async def websocket_sentence(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Create sentence trigger."""
message_id = msg[CONF_ID]
sentences = msg["sentences"]
response = msg["response"]
response_timeout = msg["response_timeout"]
response_type = msg["response_type"]
@callback
async def handle_trigger(
sentence: str,
result: RecognizeResult | None = None,
device_id: str | None = None,
) -> Any:
"""Handle Sentence trigger.
RecognizeResult was added in 2023.8.0
device_id was added in 2024.4.0.
"""
# RecognizeResult in 2024.12 is not serializable,
# so we need to convert it to a serializable format
serialized = convert_recognize_result_to_dict(result)
_LOGGER.debug("Sentence trigger: %s", sentence)
connection.send_message(
event_message(
message_id,
{
"data": {
"sentence": sentence,
"result": serialized,
"deviceId": device_id,
"responseId": message_id,
}
},
)
)
if response_type == ResponseType.DYNAMIC:
async with response_lock:
if message_id not in response_futures:
response_futures[message_id] = hass.loop.create_future()
try:
result = await asyncio.wait_for(
response_futures[message_id], response_timeout
)
_LOGGER.debug(
"Sentence response %s received with response: %s",
message_id,
result,
)
except TimeoutError:
_LOGGER.debug(
"Timeout reached for sentence response %s. Continuing...",
message_id,
)
# Remove the event from the dictionary after timeout
del response_futures[message_id]
else:
return result
return response
def remove_trigger() -> None:
"""Remove sentence trigger."""
async def _remove_future() -> None:
async with response_lock:
response_futures.pop(message_id, None)
hass.async_create_task(_remove_future())
_remove_trigger()
_LOGGER.info("Sentence trigger removed: %s", sentences)
try:
from homeassistant.components.conversation.agent_manager import ( # noqa: PLC0415
get_agent_manager,
)
from homeassistant.components.conversation.trigger import ( # noqa: PLC0415
TriggerDetails,
)
manager = get_agent_manager(hass)
if manager is None:
raise ValueError("Conversation integration not loaded") # noqa: TRY301
_remove_trigger = manager.register_trigger(
TriggerDetails(sentences, handle_trigger) # type: ignore[arg-type]
)
except ImportError:
# Fallback for Home Assistant versions before 2025.10.0
from homeassistant.components.conversation.default_agent import ( # noqa: PLC0415
DATA_DEFAULT_ENTITY, # type: ignore[import]
DefaultAgent,
)
default_agent = hass.data[DATA_DEFAULT_ENTITY]
if not isinstance(default_agent, DefaultAgent):
raise TypeError("default_agent is not an instance of DefaultAgent") # noqa: B904
_remove_trigger = default_agent.register_trigger(sentences, handle_trigger) # type: ignore[arg-type]
except ValueError as err:
connection.send_message(error_message(message_id, "value_error", str(err)))
return
except TypeError as err:
connection.send_message(error_message(message_id, "type_error", str(err)))
return
except RuntimeError as err:
connection.send_message(error_message(message_id, "runtime_error", str(err)))
return
if response_type == ResponseType.FIXED:
_LOGGER.info("Sentence trigger created: %s", sentences)
else:
_LOGGER.info(
"Sentence trigger created: %s with dynamic response #%s and "
"timeout of %s seconds",
sentences,
message_id,
response_timeout,
)
connection.subscriptions[msg[CONF_ID]] = remove_trigger
connection.send_message(result_message(message_id))
@require_admin
@websocket_command(
{
vol.Required(CONF_TYPE): "nodered/sentence_response",
vol.Required("response_id"): cv.positive_int,
vol.Required("response"): cv.string,
}
)
@async_response
async def websocket_sentence_response(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Send response to sentence trigger."""
message_id = msg[CONF_ID]
response_id = msg["response_id"]
response = msg["response"]
async with response_lock:
# Print the current response_futures keys (ids)
if response_id in response_futures:
# Set the message as the result
response_futures[response_id].set_result(response)
# Remove the event from the dictionary after dispatching
del response_futures[response_id]
_LOGGER.info("Sentence response received: %s", response)
connection.send_message(result_message(message_id))
else:
message = f"Sentence response not found for id: {response_id}"
_LOGGER.warning(message)
connection.send_message(
error_message(message_id, "sentence_response_not_found", message),
)
def convert_recognize_result_to_dict(result: Any) -> dict:
"""Serialize a RecognizeResult object into a JSON-serializable dictionary."""
def serialize(obj: Any) -> Any:
if isinstance(obj, Sentence):
# Custom serialization for Sentence
return {
"text": obj.text,
"pattern": (
obj.pattern.pattern
if hasattr(obj, "pattern") and obj.pattern is not None
else None
),
}
if hasattr(obj, "__dict__"):
# For objects with attributes, serialize attributes
return {key: serialize(value) for key, value in vars(obj).items()}
if isinstance(obj, list):
# Recursively handle lists
return [serialize(item) for item in obj]
if isinstance(obj, dict):
# Recursively handle dictionaries
return {key: serialize(value) for key, value in obj.items()}
if isinstance(obj, (int, float, str, type(None))):
# Primitive types are already serializable
return obj
# Fallback for non-serializable types
return str(obj)
return serialize(result)