Move intent registration to own integration (#29280)
* Move intent registration to own integration. * Lint
This commit is contained in:
parent
d91dd68b31
commit
d1aa0cea97
16 changed files with 389 additions and 289 deletions
|
@ -3,9 +3,12 @@ import logging
|
|||
import re
|
||||
from typing import Optional
|
||||
|
||||
from homeassistant import core
|
||||
from homeassistant.components.cover import INTENT_CLOSE_COVER, INTENT_OPEN_COVER
|
||||
from homeassistant.components.shopping_list import INTENT_ADD_ITEM, INTENT_LAST_ITEMS
|
||||
from homeassistant import core, setup
|
||||
from homeassistant.components.cover.intent import INTENT_CLOSE_COVER, INTENT_OPEN_COVER
|
||||
from homeassistant.components.shopping_list.intent import (
|
||||
INTENT_ADD_ITEM,
|
||||
INTENT_LAST_ITEMS,
|
||||
)
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import intent
|
||||
|
@ -58,6 +61,9 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
|
||||
async def async_initialize(self, config):
|
||||
"""Initialize the default agent."""
|
||||
if "intent" not in self.hass.config.components:
|
||||
await setup.async_setup_component(self.hass, "intent", {})
|
||||
|
||||
config = config.get(DOMAIN, {})
|
||||
intents = self.hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||
)
|
||||
from homeassistant.helpers.config_validation import ENTITY_SERVICE_SCHEMA
|
||||
from homeassistant.components import group
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.const import (
|
||||
SERVICE_OPEN_COVER,
|
||||
SERVICE_CLOSE_COVER,
|
||||
|
@ -83,8 +82,6 @@ ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
|
|||
ATTR_POSITION = "position"
|
||||
ATTR_TILT_POSITION = "tilt_position"
|
||||
|
||||
INTENT_OPEN_COVER = "HassOpenCover"
|
||||
INTENT_CLOSE_COVER = "HassCloseCover"
|
||||
|
||||
COVER_SET_COVER_POSITION_SCHEMA = ENTITY_SERVICE_SCHEMA.extend(
|
||||
{vol.Required(ATTR_POSITION): vol.All(vol.Coerce(int), vol.Range(min=0, max=100))}
|
||||
|
@ -158,17 +155,6 @@ async def async_setup(hass, config):
|
|||
SERVICE_TOGGLE_COVER_TILT, ENTITY_SERVICE_SCHEMA, "async_toggle_tilt"
|
||||
)
|
||||
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}"
|
||||
)
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}"
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
22
homeassistant/components/cover/intent.py
Normal file
22
homeassistant/components/cover/intent.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""Intents for the cover integration."""
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from . import DOMAIN, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER
|
||||
|
||||
INTENT_OPEN_COVER = "HassOpenCover"
|
||||
INTENT_CLOSE_COVER = "HassCloseCover"
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the cover intents."""
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}"
|
||||
)
|
||||
)
|
||||
hass.helpers.intent.async_register(
|
||||
intent.ServiceIntentHandler(
|
||||
INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}"
|
||||
)
|
||||
)
|
|
@ -1,22 +1,55 @@
|
|||
"""The Intent integration."""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import EVENT_COMPONENT_LOADED
|
||||
from homeassistant.setup import ATTR_COMPONENT
|
||||
from homeassistant.components import http
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.helpers import config_validation as cv, intent
|
||||
from homeassistant.loader import async_get_integration, IntegrationNotFound
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Intent component."""
|
||||
hass.http.register_view(IntentHandleView())
|
||||
|
||||
tasks = [_async_process_intent(hass, comp) for comp in hass.config.components]
|
||||
|
||||
async def async_component_loaded(event):
|
||||
"""Handle a new component loaded."""
|
||||
await _async_process_intent(hass, event.data[ATTR_COMPONENT])
|
||||
|
||||
hass.bus.async_listen(EVENT_COMPONENT_LOADED, async_component_loaded)
|
||||
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def _async_process_intent(hass: HomeAssistant, component_name: str):
|
||||
"""Process the intents of a component."""
|
||||
try:
|
||||
integration = await async_get_integration(hass, component_name)
|
||||
platform = integration.get_platform(DOMAIN)
|
||||
except (IntegrationNotFound, ImportError):
|
||||
return
|
||||
|
||||
try:
|
||||
await platform.async_setup_intents(hass)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Error setting up intents for %s", component_name)
|
||||
|
||||
|
||||
class IntentHandleView(http.HomeAssistantView):
|
||||
"""View to handle intents from JSON."""
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
|||
)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.loader import bind_hass
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
|
@ -141,8 +140,6 @@ PROFILE_SCHEMA = vol.Schema(
|
|||
vol.ExactSequence((str, cv.small_float, cv.small_float, cv.byte))
|
||||
)
|
||||
|
||||
INTENT_SET = "HassLightSet"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -196,65 +193,6 @@ def preprocess_turn_off(params):
|
|||
return (False, None) # Light should be turned on
|
||||
|
||||
|
||||
class SetIntentHandler(intent.IntentHandler):
|
||||
"""Handle set color intents."""
|
||||
|
||||
intent_type = INTENT_SET
|
||||
slot_schema = {
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Optional("color"): color_util.color_name_to_rgb,
|
||||
vol.Optional("brightness"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
}
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the hass intent."""
|
||||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
state = hass.helpers.intent.async_match_state(
|
||||
slots["name"]["value"],
|
||||
[state for state in hass.states.async_all() if state.domain == DOMAIN],
|
||||
)
|
||||
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
speech_parts = []
|
||||
|
||||
if "color" in slots:
|
||||
intent.async_test_feature(state, SUPPORT_COLOR, "changing colors")
|
||||
service_data[ATTR_RGB_COLOR] = slots["color"]["value"]
|
||||
# Use original passed in value of the color because we don't have
|
||||
# human readable names for that internally.
|
||||
speech_parts.append(
|
||||
"the color {}".format(intent_obj.slots["color"]["value"])
|
||||
)
|
||||
|
||||
if "brightness" in slots:
|
||||
intent.async_test_feature(state, SUPPORT_BRIGHTNESS, "changing brightness")
|
||||
service_data[ATTR_BRIGHTNESS_PCT] = slots["brightness"]["value"]
|
||||
speech_parts.append("{}% brightness".format(slots["brightness"]["value"]))
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_ON, service_data, context=intent_obj.context
|
||||
)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
|
||||
if not speech_parts: # No attributes changed
|
||||
speech = f"Turned on {state.name}"
|
||||
else:
|
||||
parts = [f"Changed {state.name} to"]
|
||||
for index, part in enumerate(speech_parts):
|
||||
if index == 0:
|
||||
parts.append(f" {part}")
|
||||
elif index != len(speech_parts) - 1:
|
||||
parts.append(f", {part}")
|
||||
else:
|
||||
parts.append(f" and {part}")
|
||||
speech = "".join(parts)
|
||||
|
||||
response.async_set_speech(speech)
|
||||
return response
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Expose light control via state machine and services."""
|
||||
component = hass.data[DOMAIN] = EntityComponent(
|
||||
|
@ -341,8 +279,6 @@ async def async_setup(hass, config):
|
|||
SERVICE_TOGGLE, LIGHT_TOGGLE_SCHEMA, "async_toggle"
|
||||
)
|
||||
|
||||
hass.helpers.intent.async_register(SetIntentHandler())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
|
84
homeassistant/components/light/intent.py
Normal file
84
homeassistant/components/light/intent.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
"""Intents for the light integration."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.util.color as color_util
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import (
|
||||
ATTR_ENTITY_ID,
|
||||
SUPPORT_COLOR,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
|
||||
|
||||
INTENT_SET = "HassLightSet"
|
||||
|
||||
|
||||
async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
"""Set up the light intents."""
|
||||
hass.helpers.intent.async_register(SetIntentHandler())
|
||||
|
||||
|
||||
class SetIntentHandler(intent.IntentHandler):
|
||||
"""Handle set color intents."""
|
||||
|
||||
intent_type = INTENT_SET
|
||||
slot_schema = {
|
||||
vol.Required("name"): cv.string,
|
||||
vol.Optional("color"): color_util.color_name_to_rgb,
|
||||
vol.Optional("brightness"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
}
|
||||
|
||||
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
|
||||
"""Handle the hass intent."""
|
||||
hass = intent_obj.hass
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
state = hass.helpers.intent.async_match_state(
|
||||
slots["name"]["value"],
|
||||
[state for state in hass.states.async_all() if state.domain == DOMAIN],
|
||||
)
|
||||
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
speech_parts = []
|
||||
|
||||
if "color" in slots:
|
||||
intent.async_test_feature(state, SUPPORT_COLOR, "changing colors")
|
||||
service_data[ATTR_RGB_COLOR] = slots["color"]["value"]
|
||||
# Use original passed in value of the color because we don't have
|
||||
# human readable names for that internally.
|
||||
speech_parts.append(
|
||||
"the color {}".format(intent_obj.slots["color"]["value"])
|
||||
)
|
||||
|
||||
if "brightness" in slots:
|
||||
intent.async_test_feature(state, SUPPORT_BRIGHTNESS, "changing brightness")
|
||||
service_data[ATTR_BRIGHTNESS_PCT] = slots["brightness"]["value"]
|
||||
speech_parts.append("{}% brightness".format(slots["brightness"]["value"]))
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, SERVICE_TURN_ON, service_data, context=intent_obj.context
|
||||
)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
|
||||
if not speech_parts: # No attributes changed
|
||||
speech = f"Turned on {state.name}"
|
||||
else:
|
||||
parts = [f"Changed {state.name} to"]
|
||||
for index, part in enumerate(speech_parts):
|
||||
if index == 0:
|
||||
parts.append(f" {part}")
|
||||
elif index != len(speech_parts) - 1:
|
||||
parts.append(f", {part}")
|
||||
else:
|
||||
parts.append(f" and {part}")
|
||||
speech = "".join(parts)
|
||||
|
||||
response.async_set_speech(speech)
|
||||
return response
|
|
@ -9,7 +9,6 @@ from homeassistant.const import HTTP_NOT_FOUND, HTTP_BAD_REQUEST
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.components import http
|
||||
from homeassistant.components.http.data_validator import RequestDataValidator
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
from homeassistant.components import websocket_api
|
||||
|
@ -20,8 +19,6 @@ DOMAIN = "shopping_list"
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: {}}, extra=vol.ALLOW_EXTRA)
|
||||
EVENT = "shopping_list_updated"
|
||||
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
||||
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
|
||||
ITEM_UPDATE_SCHEMA = vol.Schema({"complete": bool, ATTR_NAME: str})
|
||||
PERSISTENCE = ".shopping_list.json"
|
||||
|
||||
|
@ -86,9 +83,6 @@ def async_setup(hass, config):
|
|||
data = hass.data[DOMAIN] = ShoppingData(hass)
|
||||
yield from data.async_load()
|
||||
|
||||
intent.async_register(hass, AddItemIntent())
|
||||
intent.async_register(hass, ListTopItemsIntent())
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_ADD_ITEM, add_item_service, schema=SERVICE_ITEM_SCHEMA
|
||||
)
|
||||
|
@ -175,49 +169,6 @@ class ShoppingData:
|
|||
save_json(self.hass.config.path(PERSISTENCE), self.items)
|
||||
|
||||
|
||||
class AddItemIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_ADD_ITEM
|
||||
slot_schema = {"item": cv.string}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
item = slots["item"]["value"]
|
||||
intent_obj.hass.data[DOMAIN].async_add(item)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_speech(f"I've added {item} to your shopping list")
|
||||
intent_obj.hass.bus.async_fire(EVENT)
|
||||
return response
|
||||
|
||||
|
||||
class ListTopItemsIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_LAST_ITEMS
|
||||
slot_schema = {"item": cv.string}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
items = intent_obj.hass.data[DOMAIN].items[-5:]
|
||||
response = intent_obj.create_response()
|
||||
|
||||
if not items:
|
||||
response.async_set_speech("There are no items on your shopping list")
|
||||
else:
|
||||
response.async_set_speech(
|
||||
"These are the top {} items on your shopping list: {}".format(
|
||||
min(len(items), 5),
|
||||
", ".join(itm["name"] for itm in reversed(items)),
|
||||
)
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
class ShoppingListView(http.HomeAssistantView):
|
||||
"""View to retrieve shopping list content."""
|
||||
|
||||
|
|
55
homeassistant/components/shopping_list/intent.py
Normal file
55
homeassistant/components/shopping_list/intent.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""Intents for the Shopping List integration."""
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import DOMAIN, EVENT
|
||||
|
||||
INTENT_ADD_ITEM = "HassShoppingListAddItem"
|
||||
INTENT_LAST_ITEMS = "HassShoppingListLastItems"
|
||||
|
||||
|
||||
async def async_setup_intents(hass):
|
||||
"""Set up the Shopping List intents."""
|
||||
intent.async_register(hass, AddItemIntent())
|
||||
intent.async_register(hass, ListTopItemsIntent())
|
||||
|
||||
|
||||
class AddItemIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_ADD_ITEM
|
||||
slot_schema = {"item": cv.string}
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
slots = self.async_validate_slots(intent_obj.slots)
|
||||
item = slots["item"]["value"]
|
||||
intent_obj.hass.data[DOMAIN].async_add(item)
|
||||
|
||||
response = intent_obj.create_response()
|
||||
response.async_set_speech(f"I've added {item} to your shopping list")
|
||||
intent_obj.hass.bus.async_fire(EVENT)
|
||||
return response
|
||||
|
||||
|
||||
class ListTopItemsIntent(intent.IntentHandler):
|
||||
"""Handle AddItem intents."""
|
||||
|
||||
intent_type = INTENT_LAST_ITEMS
|
||||
slot_schema = {"item": cv.string}
|
||||
|
||||
async def async_handle(self, intent_obj):
|
||||
"""Handle the intent."""
|
||||
items = intent_obj.hass.data[DOMAIN].items[-5:]
|
||||
response = intent_obj.create_response()
|
||||
|
||||
if not items:
|
||||
response.async_set_speech("There are no items on your shopping list")
|
||||
else:
|
||||
response.async_set_speech(
|
||||
"These are the top {} items on your shopping list: {}".format(
|
||||
min(len(items), 5),
|
||||
", ".join(itm["name"] for itm in reversed(items)),
|
||||
)
|
||||
)
|
||||
return response
|
|
@ -1,11 +1,9 @@
|
|||
"""The tests for the Conversation component."""
|
||||
# pylint: disable=protected-access
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import DOMAIN as HASS_DOMAIN, Context
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.cover import SERVICE_OPEN_COVER
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
from tests.common import async_mock_intent, async_mock_service
|
||||
|
@ -153,32 +151,6 @@ async def test_turn_on_intent(hass, sentence):
|
|||
assert call.data == {"entity_id": "light.kitchen"}
|
||||
|
||||
|
||||
async def test_cover_intents_loading(hass):
|
||||
"""Test Cover Intents Loading."""
|
||||
with pytest.raises(intent.UnknownIntent):
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, "cover", {})
|
||||
assert result
|
||||
|
||||
hass.states.async_set("cover.garage_door", "closed")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Opened garage door"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "cover"
|
||||
assert call.service == "open_cover"
|
||||
assert call.data == {"entity_id": "cover.garage_door"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("sentence", ("turn off kitchen", "turn kitchen off"))
|
||||
async def test_turn_off_intent(hass, sentence):
|
||||
"""Test calling the turn on intent."""
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
"""The tests for the cover platform."""
|
||||
|
||||
from homeassistant.components.cover import SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER
|
||||
from homeassistant.components.cover import (
|
||||
SERVICE_OPEN_COVER,
|
||||
SERVICE_CLOSE_COVER,
|
||||
intent as cover_intent,
|
||||
)
|
||||
from homeassistant.helpers import intent
|
||||
import homeassistant.components as comps
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_open_cover_intent(hass):
|
||||
"""Test HassOpenCover intent."""
|
||||
result = await comps.cover.async_setup(hass, {})
|
||||
assert result
|
||||
await cover_intent.async_setup_intents(hass)
|
||||
|
||||
hass.states.async_set("cover.garage_door", "closed")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
|
||||
|
@ -29,8 +31,7 @@ async def test_open_cover_intent(hass):
|
|||
|
||||
async def test_close_cover_intent(hass):
|
||||
"""Test HassCloseCover intent."""
|
||||
result = await comps.cover.async_setup(hass, {})
|
||||
assert result
|
||||
await cover_intent.async_setup_intents(hass)
|
||||
|
||||
hass.states.async_set("cover.garage_door", "open")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_CLOSE_COVER)
|
|
@ -1,6 +1,11 @@
|
|||
"""Tests for Intent component."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.components.cover import SERVICE_OPEN_COVER
|
||||
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_http_handle_intent(hass, hass_client, hass_admin_user):
|
||||
|
@ -42,3 +47,30 @@ async def test_http_handle_intent(hass, hass_client, hass_admin_user):
|
|||
},
|
||||
"speech": {"plain": {"extra_data": None, "speech": "I've ordered a Belgian!"}},
|
||||
}
|
||||
|
||||
|
||||
async def test_cover_intents_loading(hass):
|
||||
"""Test Cover Intents Loading."""
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
with pytest.raises(intent.UnknownIntent):
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "cover", {})
|
||||
|
||||
hass.states.async_set("cover.garage_door", "closed")
|
||||
calls = async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
|
||||
|
||||
response = await intent.async_handle(
|
||||
hass, "test", "HassOpenCover", {"name": {"value": "garage door"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert response.speech["plain"]["speech"] == "Opened garage door"
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == "cover"
|
||||
assert call.service == "open_cover"
|
||||
assert call.data == {"entity_id": "cover.garage_door"}
|
||||
|
|
|
@ -18,13 +18,10 @@ from homeassistant.const import (
|
|||
SERVICE_TURN_ON,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TOGGLE,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
)
|
||||
from homeassistant.components import light
|
||||
from homeassistant.helpers.intent import IntentHandleError
|
||||
|
||||
from tests.common import (
|
||||
async_mock_service,
|
||||
mock_service,
|
||||
get_test_home_assistant,
|
||||
mock_storage,
|
||||
|
@ -433,87 +430,6 @@ class TestLight(unittest.TestCase):
|
|||
assert {light.ATTR_HS_COLOR: (50.353, 100), light.ATTR_BRIGHTNESS: 100} == data
|
||||
|
||||
|
||||
async def test_intent_set_color(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set(
|
||||
"light.hello_2", "off", {ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR}
|
||||
)
|
||||
hass.states.async_set("switch.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
hass.helpers.intent.async_register(light.SetIntentHandler())
|
||||
|
||||
result = await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
light.INTENT_SET,
|
||||
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result.speech["plain"]["speech"] == "Changed hello 2 to the color blue"
|
||||
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == light.DOMAIN
|
||||
assert call.service == SERVICE_TURN_ON
|
||||
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
|
||||
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
|
||||
|
||||
|
||||
async def test_intent_set_color_tests_feature(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set("light.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
hass.helpers.intent.async_register(light.SetIntentHandler())
|
||||
|
||||
try:
|
||||
await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
light.INTENT_SET,
|
||||
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
|
||||
)
|
||||
assert False, "handling intent should have raised"
|
||||
except IntentHandleError as err:
|
||||
assert str(err) == "Entity hello does not support changing colors"
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_intent_set_color_and_brightness(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set(
|
||||
"light.hello_2",
|
||||
"off",
|
||||
{ATTR_SUPPORTED_FEATURES: (light.SUPPORT_COLOR | light.SUPPORT_BRIGHTNESS)},
|
||||
)
|
||||
hass.states.async_set("switch.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
hass.helpers.intent.async_register(light.SetIntentHandler())
|
||||
|
||||
result = await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
light.INTENT_SET,
|
||||
{
|
||||
"name": {"value": "Hello"},
|
||||
"color": {"value": "blue"},
|
||||
"brightness": {"value": "20"},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
result.speech["plain"]["speech"]
|
||||
== "Changed hello 2 to the color blue and 20% brightness"
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == light.DOMAIN
|
||||
assert call.service == SERVICE_TURN_ON
|
||||
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
|
||||
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
|
||||
assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20
|
||||
|
||||
|
||||
async def test_light_context(hass, hass_admin_user):
|
||||
"""Test that light context works."""
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
|
|
88
tests/components/light/test_intent.py
Normal file
88
tests/components/light/test_intent.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
"""Tests for the light intents."""
|
||||
from homeassistant.helpers.intent import IntentHandleError
|
||||
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON, ATTR_ENTITY_ID
|
||||
from homeassistant.components import light
|
||||
from homeassistant.components.light import intent
|
||||
from tests.common import async_mock_service
|
||||
|
||||
|
||||
async def test_intent_set_color(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set(
|
||||
"light.hello_2", "off", {ATTR_SUPPORTED_FEATURES: light.SUPPORT_COLOR}
|
||||
)
|
||||
hass.states.async_set("switch.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
await intent.async_setup_intents(hass)
|
||||
|
||||
result = await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
intent.INTENT_SET,
|
||||
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result.speech["plain"]["speech"] == "Changed hello 2 to the color blue"
|
||||
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == light.DOMAIN
|
||||
assert call.service == SERVICE_TURN_ON
|
||||
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
|
||||
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
|
||||
|
||||
|
||||
async def test_intent_set_color_tests_feature(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set("light.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
await intent.async_setup_intents(hass)
|
||||
|
||||
try:
|
||||
await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
intent.INTENT_SET,
|
||||
{"name": {"value": "Hello"}, "color": {"value": "blue"}},
|
||||
)
|
||||
assert False, "handling intent should have raised"
|
||||
except IntentHandleError as err:
|
||||
assert str(err) == "Entity hello does not support changing colors"
|
||||
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_intent_set_color_and_brightness(hass):
|
||||
"""Test the set color intent."""
|
||||
hass.states.async_set(
|
||||
"light.hello_2",
|
||||
"off",
|
||||
{ATTR_SUPPORTED_FEATURES: (light.SUPPORT_COLOR | light.SUPPORT_BRIGHTNESS)},
|
||||
)
|
||||
hass.states.async_set("switch.hello", "off")
|
||||
calls = async_mock_service(hass, light.DOMAIN, light.SERVICE_TURN_ON)
|
||||
await intent.async_setup_intents(hass)
|
||||
|
||||
result = await hass.helpers.intent.async_handle(
|
||||
"test",
|
||||
intent.INTENT_SET,
|
||||
{
|
||||
"name": {"value": "Hello"},
|
||||
"color": {"value": "blue"},
|
||||
"brightness": {"value": "20"},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
result.speech["plain"]["speech"]
|
||||
== "Changed hello 2 to the color blue and 20% brightness"
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
call = calls[0]
|
||||
assert call.domain == light.DOMAIN
|
||||
assert call.service == SERVICE_TURN_ON
|
||||
assert call.data.get(ATTR_ENTITY_ID) == "light.hello_2"
|
||||
assert call.data.get(light.ATTR_RGB_COLOR) == (0, 0, 255)
|
||||
assert call.data.get(light.ATTR_BRIGHTNESS_PCT) == 20
|
23
tests/components/shopping_list/conftest.py
Normal file
23
tests/components/shopping_list/conftest.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Shopping list test helpers."""
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components.shopping_list import intent as sl_intent
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_shopping_list_io():
|
||||
"""Stub out the persistence."""
|
||||
with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch(
|
||||
"homeassistant.components.shopping_list." "ShoppingData.async_load"
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def sl_setup(hass):
|
||||
"""Set up the shopping list."""
|
||||
assert await async_setup_component(hass, "shopping_list", {})
|
||||
await sl_intent.async_setup_intents(hass)
|
|
@ -1,27 +1,13 @@
|
|||
"""Test shopping list component."""
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.bootstrap import async_setup_component
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.components.websocket_api.const import TYPE_RESULT
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_shopping_list_io():
|
||||
"""Stub out the persistence."""
|
||||
with patch("homeassistant.components.shopping_list.ShoppingData.save"), patch(
|
||||
"homeassistant.components.shopping_list." "ShoppingData.async_load"
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_add_item(hass):
|
||||
def test_add_item(hass, sl_setup):
|
||||
"""Test adding an item intent."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
response = yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -31,9 +17,8 @@ def test_add_item(hass):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_recent_items_intent(hass):
|
||||
def test_recent_items_intent(hass, sl_setup):
|
||||
"""Test recent items."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -54,9 +39,8 @@ def test_recent_items_intent(hass):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_get_all(hass, hass_client):
|
||||
def test_deprecated_api_get_all(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -77,9 +61,8 @@ def test_deprecated_api_get_all(hass, hass_client):
|
|||
assert not data[1]["complete"]
|
||||
|
||||
|
||||
async def test_ws_get_items(hass, hass_ws_client):
|
||||
async def test_ws_get_items(hass, hass_ws_client, sl_setup):
|
||||
"""Test get shopping_list items websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -106,9 +89,8 @@ async def test_ws_get_items(hass, hass_ws_client):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_update(hass, hass_client):
|
||||
def test_deprecated_api_update(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -142,9 +124,8 @@ def test_deprecated_api_update(hass, hass_client):
|
|||
assert wine == {"id": wine_id, "name": "wine", "complete": True}
|
||||
|
||||
|
||||
async def test_ws_update_item(hass, hass_ws_client):
|
||||
async def test_ws_update_item(hass, hass_ws_client, sl_setup):
|
||||
"""Test update shopping_list item websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
@ -186,9 +167,8 @@ async def test_ws_update_item(hass, hass_ws_client):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_api_update_fails(hass, hass_client):
|
||||
def test_api_update_fails(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -209,9 +189,8 @@ def test_api_update_fails(hass, hass_client):
|
|||
assert resp.status == 400
|
||||
|
||||
|
||||
async def test_ws_update_item_fail(hass, hass_ws_client):
|
||||
async def test_ws_update_item_fail(hass, hass_ws_client, sl_setup):
|
||||
"""Test failure of update shopping_list item websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
@ -234,9 +213,8 @@ async def test_ws_update_item_fail(hass, hass_ws_client):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_clear_completed(hass, hass_client):
|
||||
def test_deprecated_api_clear_completed(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
yield from intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
|
@ -265,9 +243,8 @@ def test_deprecated_api_clear_completed(hass, hass_client):
|
|||
assert items[0] == {"id": wine_id, "name": "wine", "complete": False}
|
||||
|
||||
|
||||
async def test_ws_clear_items(hass, hass_ws_client):
|
||||
async def test_ws_clear_items(hass, hass_ws_client, sl_setup):
|
||||
"""Test clearing shopping_list items websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
|
@ -296,9 +273,8 @@ async def test_ws_clear_items(hass, hass_ws_client):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_create(hass, hass_client):
|
||||
def test_deprecated_api_create(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post("/api/shopping_list/item", json={"name": "soda"})
|
||||
|
@ -315,9 +291,8 @@ def test_deprecated_api_create(hass, hass_client):
|
|||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_deprecated_api_create_fail(hass, hass_client):
|
||||
def test_deprecated_api_create_fail(hass, hass_client, sl_setup):
|
||||
"""Test the API."""
|
||||
yield from async_setup_component(hass, "shopping_list", {})
|
||||
|
||||
client = yield from hass_client()
|
||||
resp = yield from client.post("/api/shopping_list/item", json={"name": 1234})
|
||||
|
@ -326,9 +301,8 @@ def test_deprecated_api_create_fail(hass, hass_client):
|
|||
assert len(hass.data["shopping_list"].items) == 0
|
||||
|
||||
|
||||
async def test_ws_add_item(hass, hass_ws_client):
|
||||
async def test_ws_add_item(hass, hass_ws_client, sl_setup):
|
||||
"""Test adding shopping_list item websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json({"id": 5, "type": "shopping_list/items/add", "name": "soda"})
|
||||
msg = await client.receive_json()
|
||||
|
@ -342,9 +316,8 @@ async def test_ws_add_item(hass, hass_ws_client):
|
|||
assert items[0]["complete"] is False
|
||||
|
||||
|
||||
async def test_ws_add_item_fail(hass, hass_ws_client):
|
||||
async def test_ws_add_item_fail(hass, hass_ws_client, sl_setup):
|
||||
"""Test adding shopping_list item failure websocket command."""
|
||||
await async_setup_component(hass, "shopping_list", {})
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json({"id": 5, "type": "shopping_list/items/add", "name": 123})
|
||||
msg = await client.receive_json()
|
||||
|
|
22
tests/components/shopping_list/test_intent.py
Normal file
22
tests/components/shopping_list/test_intent.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""Test Shopping List intents."""
|
||||
from homeassistant.helpers import intent
|
||||
|
||||
|
||||
async def test_recent_items_intent(hass, sl_setup):
|
||||
"""Test recent items."""
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "beer"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "wine"}}
|
||||
)
|
||||
await intent.async_handle(
|
||||
hass, "test", "HassShoppingListAddItem", {"item": {"value": "soda"}}
|
||||
)
|
||||
|
||||
response = await intent.async_handle(hass, "test", "HassShoppingListLastItems")
|
||||
|
||||
assert (
|
||||
response.speech["plain"]["speech"]
|
||||
== "These are the top 3 items on your shopping list: soda, wine, beer"
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue