mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add Conversation command to timers (#118325)
* Add Assist command to timers * Rename to conversation_command. Execute in timer code. * Make agent_id optional * Fix arg --------- Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
615a1eda51
commit
d223e1f2ac
@ -96,6 +96,7 @@ async def async_converse(
|
||||
conversation_id=conversation_id,
|
||||
device_id=device_id,
|
||||
language=language,
|
||||
agent_id=agent_id,
|
||||
)
|
||||
with async_conversation_trace() as trace:
|
||||
trace.add_event(
|
||||
|
@ -354,6 +354,7 @@ class DefaultAgent(ConversationEntity):
|
||||
language,
|
||||
assistant=DOMAIN,
|
||||
device_id=user_input.device_id,
|
||||
conversation_agent_id=user_input.agent_id,
|
||||
)
|
||||
except intent.MatchFailedError as match_error:
|
||||
# Intent was valid, but no entities matched the constraints.
|
||||
|
@ -188,6 +188,7 @@ async def websocket_hass_agent_debug(
|
||||
conversation_id=None,
|
||||
device_id=msg.get("device_id"),
|
||||
language=msg.get("language", hass.config.language),
|
||||
agent_id=None,
|
||||
)
|
||||
)
|
||||
for sentence in msg["sentences"]
|
||||
|
@ -27,6 +27,7 @@ class ConversationInput:
|
||||
conversation_id: str | None
|
||||
device_id: str | None
|
||||
language: str
|
||||
agent_id: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
@ -14,7 +14,7 @@ from typing import Any
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ID, ATTR_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import Context, HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
config_validation as cv,
|
||||
@ -78,6 +78,18 @@ class TimerInfo:
|
||||
floor_id: str | None = None
|
||||
"""Id of floor that the device's area belongs to."""
|
||||
|
||||
conversation_command: str | None = None
|
||||
"""Text of conversation command to execute when timer is finished.
|
||||
|
||||
This command must be in the language used to set the timer.
|
||||
"""
|
||||
|
||||
conversation_agent_id: str | None = None
|
||||
"""Id of the conversation agent used to set the timer.
|
||||
|
||||
This agent will be used to execute the conversation command.
|
||||
"""
|
||||
|
||||
@property
|
||||
def seconds_left(self) -> int:
|
||||
"""Return number of seconds left on the timer."""
|
||||
@ -207,6 +219,8 @@ class TimerManager:
|
||||
seconds: int | None,
|
||||
language: str,
|
||||
name: str | None = None,
|
||||
conversation_command: str | None = None,
|
||||
conversation_agent_id: str | None = None,
|
||||
) -> str:
|
||||
"""Start a timer."""
|
||||
if not self.is_timer_device(device_id):
|
||||
@ -235,6 +249,8 @@ class TimerManager:
|
||||
device_id=device_id,
|
||||
created_at=created_at,
|
||||
updated_at=created_at,
|
||||
conversation_command=conversation_command,
|
||||
conversation_agent_id=conversation_agent_id,
|
||||
)
|
||||
|
||||
# Fill in area/floor info
|
||||
@ -410,6 +426,23 @@ class TimerManager:
|
||||
timer.device_id,
|
||||
)
|
||||
|
||||
if timer.conversation_command:
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from homeassistant.components.conversation import async_converse
|
||||
|
||||
self.hass.async_create_background_task(
|
||||
async_converse(
|
||||
self.hass,
|
||||
timer.conversation_command,
|
||||
conversation_id=None,
|
||||
context=Context(),
|
||||
language=timer.language,
|
||||
agent_id=timer.conversation_agent_id,
|
||||
device_id=timer.device_id,
|
||||
),
|
||||
"timer assist command",
|
||||
)
|
||||
|
||||
def is_timer_device(self, device_id: str) -> bool:
|
||||
"""Return True if device has been registered to handle timer events."""
|
||||
return device_id in self.handlers
|
||||
@ -742,6 +775,7 @@ class StartTimerIntentHandler(intent.IntentHandler):
|
||||
slot_schema = {
|
||||
vol.Required(vol.Any("hours", "minutes", "seconds")): cv.positive_int,
|
||||
vol.Optional("name"): cv.string,
|
||||
vol.Optional("conversation_command"): cv.string,
|
||||
}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
@ -772,6 +806,10 @@ class StartTimerIntentHandler(intent.IntentHandler):
|
||||
if "seconds" in slots:
|
||||
seconds = int(slots["seconds"]["value"])
|
||||
|
||||
conversation_command: str | None = None
|
||||
if "conversation_command" in slots:
|
||||
conversation_command = slots["conversation_command"]["value"]
|
||||
|
||||
timer_manager.start_timer(
|
||||
intent_obj.device_id,
|
||||
hours,
|
||||
@ -779,6 +817,8 @@ class StartTimerIntentHandler(intent.IntentHandler):
|
||||
seconds,
|
||||
language=intent_obj.language,
|
||||
name=name,
|
||||
conversation_command=conversation_command,
|
||||
conversation_agent_id=intent_obj.conversation_agent_id,
|
||||
)
|
||||
|
||||
return intent_obj.create_response()
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Wyoming Protocol",
|
||||
"codeowners": ["@balloob", "@synesthesiam"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["assist_pipeline", "intent"],
|
||||
"dependencies": ["assist_pipeline", "intent", "conversation"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/wyoming",
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
|
@ -104,6 +104,7 @@ async def async_handle(
|
||||
language: str | None = None,
|
||||
assistant: str | None = None,
|
||||
device_id: str | None = None,
|
||||
conversation_agent_id: str | None = None,
|
||||
) -> IntentResponse:
|
||||
"""Handle an intent."""
|
||||
handler = hass.data.get(DATA_KEY, {}).get(intent_type)
|
||||
@ -127,6 +128,7 @@ async def async_handle(
|
||||
language=language,
|
||||
assistant=assistant,
|
||||
device_id=device_id,
|
||||
conversation_agent_id=conversation_agent_id,
|
||||
)
|
||||
|
||||
try:
|
||||
@ -1156,6 +1158,7 @@ class Intent:
|
||||
"category",
|
||||
"assistant",
|
||||
"device_id",
|
||||
"conversation_agent_id",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
@ -1170,6 +1173,7 @@ class Intent:
|
||||
category: IntentCategory | None = None,
|
||||
assistant: str | None = None,
|
||||
device_id: str | None = None,
|
||||
conversation_agent_id: str | None = None,
|
||||
) -> None:
|
||||
"""Initialize an intent."""
|
||||
self.hass = hass
|
||||
@ -1182,6 +1186,7 @@ class Intent:
|
||||
self.category = category
|
||||
self.assistant = assistant
|
||||
self.device_id = device_id
|
||||
self.conversation_agent_id = conversation_agent_id
|
||||
|
||||
@callback
|
||||
def create_response(self) -> IntentResponse:
|
||||
|
@ -927,6 +927,7 @@ async def test_non_default_response(hass: HomeAssistant, init_components) -> Non
|
||||
conversation_id=None,
|
||||
device_id=None,
|
||||
language=hass.config.language,
|
||||
agent_id=None,
|
||||
)
|
||||
)
|
||||
assert len(calls) == 1
|
||||
|
@ -555,6 +555,7 @@ async def test_trigger_with_device_id(hass: HomeAssistant) -> None:
|
||||
conversation_id=None,
|
||||
device_id="my_device",
|
||||
language=hass.config.language,
|
||||
agent_id=None,
|
||||
)
|
||||
)
|
||||
assert result.response.speech["plain"]["speech"] == "my_device"
|
||||
|
@ -1421,6 +1421,48 @@ def test_round_time() -> None:
|
||||
assert _round_time(0, 0, 35) == (0, 0, 30)
|
||||
|
||||
|
||||
async def test_start_timer_with_conversation_command(
|
||||
hass: HomeAssistant, init_components
|
||||
) -> None:
|
||||
"""Test starting a timer with an conversation command and having it finish."""
|
||||
device_id = "test_device"
|
||||
timer_name = "test timer"
|
||||
test_command = "turn on the lights"
|
||||
agent_id = "test_agent"
|
||||
finished_event = asyncio.Event()
|
||||
|
||||
@callback
|
||||
def handle_timer(event_type: TimerEventType, timer: TimerInfo) -> None:
|
||||
if event_type == TimerEventType.FINISHED:
|
||||
assert timer.conversation_command == test_command
|
||||
assert timer.conversation_agent_id == agent_id
|
||||
finished_event.set()
|
||||
|
||||
async_register_timer_handler(hass, device_id, handle_timer)
|
||||
|
||||
with patch("homeassistant.components.conversation.async_converse") as mock_converse:
|
||||
result = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
intent.INTENT_START_TIMER,
|
||||
{
|
||||
"name": {"value": timer_name},
|
||||
"seconds": {"value": 0},
|
||||
"conversation_command": {"value": test_command},
|
||||
},
|
||||
device_id=device_id,
|
||||
conversation_agent_id=agent_id,
|
||||
)
|
||||
|
||||
assert result.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
|
||||
async with asyncio.timeout(1):
|
||||
await finished_event.wait()
|
||||
|
||||
mock_converse.assert_called_once()
|
||||
assert mock_converse.call_args.args[1] == test_command
|
||||
|
||||
|
||||
async def test_pause_unpause_timer_disambiguate(
|
||||
hass: HomeAssistant, init_components
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user