diff --git a/CODEOWNERS b/CODEOWNERS index f35c492615d..74916740253 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -156,6 +156,7 @@ homeassistant/components/input_number/* @home-assistant/core homeassistant/components/input_select/* @home-assistant/core homeassistant/components/input_text/* @home-assistant/core homeassistant/components/integration/* @dgomes +homeassistant/components/intent/* @home-assistant/core homeassistant/components/ios/* @robbiet480 homeassistant/components/ipma/* @dgomes homeassistant/components/iqvia/* @bachya diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index ba8b211e65a..ec5868e86fe 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -68,7 +68,6 @@ async def async_setup(hass, config): DOMAIN, SERVICE_PROCESS, handle_service, schema=SERVICE_PROCESS_SCHEMA ) hass.http.register_view(ConversationProcessView()) - hass.http.register_view(ConversationHandleView()) hass.components.websocket_api.async_register_command(websocket_process) hass.components.websocket_api.async_register_command(websocket_get_agent_info) hass.components.websocket_api.async_register_command(websocket_set_onboarding) @@ -139,43 +138,6 @@ class ConversationProcessView(http.HomeAssistantView): return self.json(intent_result) -class ConversationHandleView(http.HomeAssistantView): - """View to handle intents from JSON.""" - - url = "/api/conversation/handle" - name = "api:conversation:handle" - - @RequestDataValidator( - vol.Schema( - { - vol.Required("name"): cv.string, - vol.Optional("data"): vol.Schema({cv.string: object}), - } - ) - ) - async def post(self, request, data): - """Handle intent with name/data.""" - hass = request.app["hass"] - - try: - intent_name = data["name"] - slots = { - key: {"value": value} for key, value in data.get("data", {}).items() - } - intent_result = await intent.async_handle( - hass, DOMAIN, intent_name, slots, "", self.context(request) - ) - except intent.IntentHandleError as err: - intent_result = intent.IntentResponse() - intent_result.async_set_speech(str(err)) - - if intent_result is None: - intent_result = intent.IntentResponse() - intent_result.async_set_speech("Sorry, I couldn't handle that") - - return self.json(intent_result) - - async def _get_agent(hass: core.HomeAssistant) -> AbstractConversationAgent: """Get the active conversation agent.""" agent = hass.data.get(DATA_AGENT) diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py new file mode 100644 index 00000000000..758b77ba108 --- /dev/null +++ b/homeassistant/components/intent/__init__.py @@ -0,0 +1,54 @@ +"""The Intent integration.""" +import voluptuous as vol + +from homeassistant.core import HomeAssistant +from homeassistant.components import http +from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.helpers import config_validation as cv, intent + +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Intent component.""" + hass.http.register_view(IntentHandleView()) + return True + + +class IntentHandleView(http.HomeAssistantView): + """View to handle intents from JSON.""" + + url = "/api/intent/handle" + name = "api:intent:handle" + + @RequestDataValidator( + vol.Schema( + { + vol.Required("name"): cv.string, + vol.Optional("data"): vol.Schema({cv.string: object}), + } + ) + ) + async def post(self, request, data): + """Handle intent with name/data.""" + hass = request.app["hass"] + + try: + intent_name = data["name"] + slots = { + key: {"value": value} for key, value in data.get("data", {}).items() + } + intent_result = await intent.async_handle( + hass, DOMAIN, intent_name, slots, "", self.context(request) + ) + except intent.IntentHandleError as err: + intent_result = intent.IntentResponse() + intent_result.async_set_speech(str(err)) + + if intent_result is None: + intent_result = intent.IntentResponse() + intent_result.async_set_speech("Sorry, I couldn't handle that") + + return self.json(intent_result) diff --git a/homeassistant/components/intent/const.py b/homeassistant/components/intent/const.py new file mode 100644 index 00000000000..61b97c20537 --- /dev/null +++ b/homeassistant/components/intent/const.py @@ -0,0 +1,3 @@ +"""Constants for the Intent integration.""" + +DOMAIN = "intent" diff --git a/homeassistant/components/intent/manifest.json b/homeassistant/components/intent/manifest.json new file mode 100644 index 00000000000..005abde47d6 --- /dev/null +++ b/homeassistant/components/intent/manifest.json @@ -0,0 +1,11 @@ +{ + "domain": "intent", + "name": "Intent", + "config_flow": false, + "documentation": "https://www.home-assistant.io/integrations/intent", + "requirements": [], + "ssdp": [], + "homekit": {}, + "dependencies": ["http"], + "codeowners": ["@home-assistant/core"] +} diff --git a/script/scaffold/templates/integration/integration/manifest.json b/script/scaffold/templates/integration/integration/manifest.json index 0bc54519ce9..a95991abef8 100644 --- a/script/scaffold/templates/integration/integration/manifest.json +++ b/script/scaffold/templates/integration/integration/manifest.json @@ -4,7 +4,8 @@ "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/NEW_DOMAIN", "requirements": [], - "ssdp": {}, + "ssdp": [], + "zeroconf": [], "homekit": {}, "dependencies": [], "codeowners": [] diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index 3982ed6f699..6d318deacdd 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -129,52 +129,6 @@ async def test_http_processing_intent(hass, hass_client, hass_admin_user): } -async def test_http_handle_intent(hass, hass_client, hass_admin_user): - """Test handle intent via HTTP API.""" - - class TestIntentHandler(intent.IntentHandler): - """Test Intent Handler.""" - - intent_type = "OrderBeer" - - async def async_handle(self, intent): - """Handle the intent.""" - assert intent.context.user_id == hass_admin_user.id - response = intent.create_response() - response.async_set_speech( - "I've ordered a {}!".format(intent.slots["type"]["value"]) - ) - response.async_set_card( - "Beer ordered", "You chose a {}.".format(intent.slots["type"]["value"]) - ) - return response - - intent.async_register(hass, TestIntentHandler()) - - result = await async_setup_component( - hass, - "conversation", - {"conversation": {"intents": {"OrderBeer": ["I would like the {type} beer"]}}}, - ) - assert result - - client = await hass_client() - resp = await client.post( - "/api/conversation/handle", - json={"name": "OrderBeer", "data": {"type": "Belgian"}}, - ) - - assert resp.status == 200 - data = await resp.json() - - assert data == { - "card": { - "simple": {"content": "You chose a Belgian.", "title": "Beer ordered"} - }, - "speech": {"plain": {"extra_data": None, "speech": "I've ordered a Belgian!"}}, - } - - @pytest.mark.parametrize("sentence", ("turn on kitchen", "turn kitchen on")) async def test_turn_on_intent(hass, sentence): """Test calling the turn on intent.""" diff --git a/tests/components/intent/__init__.py b/tests/components/intent/__init__.py new file mode 100644 index 00000000000..463f53d921c --- /dev/null +++ b/tests/components/intent/__init__.py @@ -0,0 +1 @@ +"""Tests for the Intent integration.""" diff --git a/tests/components/intent/test_init.py b/tests/components/intent/test_init.py new file mode 100644 index 00000000000..e0e5f44873d --- /dev/null +++ b/tests/components/intent/test_init.py @@ -0,0 +1,44 @@ +"""Tests for Intent component.""" +from homeassistant.setup import async_setup_component +from homeassistant.helpers import intent + + +async def test_http_handle_intent(hass, hass_client, hass_admin_user): + """Test handle intent via HTTP API.""" + + class TestIntentHandler(intent.IntentHandler): + """Test Intent Handler.""" + + intent_type = "OrderBeer" + + async def async_handle(self, intent): + """Handle the intent.""" + assert intent.context.user_id == hass_admin_user.id + response = intent.create_response() + response.async_set_speech( + "I've ordered a {}!".format(intent.slots["type"]["value"]) + ) + response.async_set_card( + "Beer ordered", "You chose a {}.".format(intent.slots["type"]["value"]) + ) + return response + + intent.async_register(hass, TestIntentHandler()) + + result = await async_setup_component(hass, "intent", {}) + assert result + + client = await hass_client() + resp = await client.post( + "/api/intent/handle", json={"name": "OrderBeer", "data": {"type": "Belgian"}} + ) + + assert resp.status == 200 + data = await resp.json() + + assert data == { + "card": { + "simple": {"content": "You chose a Belgian.", "title": "Beer ordered"} + }, + "speech": {"plain": {"extra_data": None, "speech": "I've ordered a Belgian!"}}, + }